Loading services/inputflinger/dispatcher/InputDispatcher.cpp +37 −35 Original line number Diff line number Diff line Loading @@ -578,34 +578,6 @@ bool isUserActivityEvent(const EventEntry& eventEntry) { } } // Returns true if the given window can accept pointer events at the given display location. bool windowAcceptsTouchAt(const WindowInfo& windowInfo, ui::LogicalDisplayId displayId, float x, float y, bool isStylus, const ui::Transform& displayTransform) { const auto inputConfig = windowInfo.inputConfig; if (windowInfo.displayId != displayId || inputConfig.test(WindowInfo::InputConfig::NOT_VISIBLE)) { return false; } const bool windowCanInterceptTouch = isStylus && windowInfo.interceptsStylus(); if (inputConfig.test(WindowInfo::InputConfig::NOT_TOUCHABLE) && !windowCanInterceptTouch) { return false; } // Window Manager works in the logical display coordinate space. When it specifies bounds for a // window as (l, t, r, b), the range of x in [l, r) and y in [t, b) are considered to be inside // the window. Points on the right and bottom edges should not be inside the window, so we need // to be careful about performing a hit test when the display is rotated, since the "right" and // "bottom" of the window will be different in the display (un-rotated) space compared to in the // logical display in which WM determined the bounds. Perform the hit test in the logical // display space to ensure these edges are considered correctly in all orientations. const auto touchableRegion = displayTransform.transform(windowInfo.touchableRegion); const auto p = displayTransform.transform(x, y); if (!touchableRegion.contains(std::floor(p.x), std::floor(p.y))) { return false; } return true; } // Returns true if the given window's frame can occlude pointer events at the given display // location. bool windowOccludesTouchAt(const WindowInfo& windowInfo, ui::LogicalDisplayId displayId, float x, Loading Loading @@ -1461,8 +1433,7 @@ sp<WindowInfoHandle> InputDispatcher::DispatcherWindowInfo::findTouchedWindowAt( } const WindowInfo& info = *windowHandle->getInfo(); if (!info.isSpy() && windowAcceptsTouchAt(info, displayId, x, y, isStylus, getDisplayTransform(displayId))) { if (!info.isSpy() && windowAcceptsTouchAt(info, displayId, x, y, isStylus)) { return windowHandle; } } Loading Loading @@ -1503,11 +1474,10 @@ std::vector<sp<WindowInfoHandle>> InputDispatcher::findTouchedSpyWindowsAt( const DispatcherWindowInfo& windowInfos) { // Traverse windows from front to back and gather the touched spy windows. std::vector<sp<WindowInfoHandle>> spyWindows; const ui::Transform displayTransform = windowInfos.getDisplayTransform(displayId); const auto& windowHandles = windowInfos.getWindowHandlesForDisplay(displayId); for (const sp<WindowInfoHandle>& windowHandle : windowHandles) { const WindowInfo& info = *windowHandle->getInfo(); if (!windowAcceptsTouchAt(info, displayId, x, y, isStylus, displayTransform)) { if (!windowInfos.windowAcceptsTouchAt(info, displayId, x, y, isStylus)) { // Skip if the pointer is outside of the window. continue; } Loading Loading @@ -5269,6 +5239,36 @@ ui::Transform InputDispatcher::DispatcherWindowInfo::getRawTransform( return getDisplayTransform(windowInfo.displayId); } bool InputDispatcher::DispatcherWindowInfo::windowAcceptsTouchAt(const gui::WindowInfo& windowInfo, ui::LogicalDisplayId displayId, float x, float y, bool isStylus) const { const auto inputConfig = windowInfo.inputConfig; if (windowInfo.displayId != displayId || inputConfig.test(WindowInfo::InputConfig::NOT_VISIBLE)) { return false; } const bool windowCanInterceptTouch = isStylus && windowInfo.interceptsStylus(); if (inputConfig.test(WindowInfo::InputConfig::NOT_TOUCHABLE) && !windowCanInterceptTouch) { return false; } // Window Manager works in the logical display coordinate space. When it specifies bounds for a // window as (l, t, r, b), the range of x in [l, r) and y in [t, b) are considered to be inside // the window. Points on the right and bottom edges should not be inside the window, so we need // to be careful about performing a hit test when the display is rotated, since the "right" and // "bottom" of the window will be different in the display (un-rotated) space compared to in the // logical display in which WM determined the bounds. Perform the hit test in the logical // display space to ensure these edges are considered correctly in all orientations. const ui::Transform& displayTransform = getDisplayTransform(windowInfo.displayId); const auto touchableRegion = displayTransform.transform(windowInfo.touchableRegion); const auto p = displayTransform.transform(x, y); if (!touchableRegion.contains(std::floor(p.x), std::floor(p.y))) { return false; } return true; } ui::LogicalDisplayId InputDispatcher::DispatcherWindowInfo::getPrimaryDisplayId( ui::LogicalDisplayId displayId) const { if (mTopology.graph.contains(displayId)) { Loading Loading @@ -5616,14 +5616,16 @@ InputDispatcher::DispatcherTouchState::updateHoveringStateFromWindowInfo( std::list<CancellationArgs> cancellations; // Check if the hovering should stop because the window is no longer eligible to receive it // (for example, if the touchable region changed) ui::Transform displayTransform = mWindowInfos.getDisplayTransform(displayId); for (TouchedWindow& touchedWindow : state.windows) { const gui::WindowInfo& windowInfo = *touchedWindow.windowHandle->getInfo(); std::vector<DeviceId> erasedDevices = touchedWindow.eraseHoveringPointersIf( [&](const PointerProperties& properties, float x, float y) { const bool isStylus = properties.toolType == ToolType::STYLUS; // The touchstate's displayId may be different from window's display on the // connected-displays, for this reason we use use window's displayId here. const bool stillAcceptsTouch = windowAcceptsTouchAt(*touchedWindow.windowHandle->getInfo(), displayId, x, y, isStylus, displayTransform); mWindowInfos.windowAcceptsTouchAt(windowInfo, windowInfo.displayId, x, y, isStylus); return !stillAcceptsTouch; }); Loading services/inputflinger/dispatcher/InputDispatcher.h +4 −0 Original line number Diff line number Diff line Loading @@ -342,6 +342,10 @@ private: sp<android::gui::WindowInfoHandle> findWallpaperWindowBelow( const sp<android::gui::WindowInfoHandle>& windowHandle) const; // Returns true if the given window can accept pointer events at the given display location. bool windowAcceptsTouchAt(const gui::WindowInfo& windowInfo, ui::LogicalDisplayId displayId, float x, float y, bool isStylus) const; bool isTouchTrusted(const TouchOcclusionInfo& occlusionInfo) const; // Returns topology's primary display if the display belongs to it, otherwise the Loading services/inputflinger/tests/InputDispatcher_test.cpp +45 −1 Original line number Diff line number Diff line Loading @@ -15488,6 +15488,7 @@ TEST_P(TransferOrDontTransferFixture, MouseAndTouchTransferSimultaneousMultiDevi INSTANTIATE_TEST_SUITE_P(WithAndWithoutTransfer, TransferOrDontTransferFixture, testing::Bool()); class InputDispatcherConnectedDisplayTest : public InputDispatcherDragTests { protected: constexpr static int DENSITY_MEDIUM = 160; const DisplayTopologyGraph mTopology = Loading @@ -15503,7 +15504,6 @@ class InputDispatcherConnectedDisplayTest : public InputDispatcherDragTests { {SECOND_DISPLAY_ID, DENSITY_MEDIUM}}) .value(); protected: void SetUp() override { addDisplay(DISPLAY_ID, ui::Transform()); addDisplay(SECOND_DISPLAY_ID, Loading Loading @@ -15674,6 +15674,50 @@ TEST_F(InputDispatcherConnectedDisplayTest, MultiDisplayMouseDragAndDropFromNonP mWindowOnSecondDisplay->assertNoEvents(); } /** * Test that touch state is maintained across windowInfo updates on the non-primary display. * * Create two windows, one on the primary display and another on the secondary display. * Start hovering on the window on the secondary display. * * Update the window info, and verify that the hover state is maintained, and no events are * generated. * * Remove the window on the secondary display, and verify that the window receives a HOVER_EXIT * event. */ TEST_F(InputDispatcherConnectedDisplayTest, NonPrimaryDisplayTouchStateIsMaintainedOnWindowInfoUpdate) { SCOPED_FLAG_OVERRIDE(connected_displays_cursor, true); sp<FakeWindowHandle> window0 = sp<FakeWindowHandle>::make(std::make_shared<FakeApplicationHandle>(), mDispatcher, "TestWindowOnPrimaryDisplay", mTopology.primaryDisplayId); window0->setFrame(Rect(0, 0, 500, 500)); sp<FakeWindowHandle> window1 = sp<FakeWindowHandle>::make(std::make_shared<FakeApplicationHandle>(), mDispatcher, "TestWindowOnNonPrimaryDisplay", SECOND_DISPLAY_ID); window1->setFrame(Rect(0, 0, 500, 500)); mDispatcher->onWindowInfosChanged({{*window0->getInfo(), *window1->getInfo()}, {}, 0, 0}); // Add hover state to window on display 1. mDispatcher->notifyMotion( MotionArgsBuilder(AMOTION_EVENT_ACTION_HOVER_ENTER, AINPUT_SOURCE_MOUSE) .displayId(SECOND_DISPLAY_ID) .pointer(PointerBuilder(MOUSE_POINTER_ID, ToolType::MOUSE).x(100).y(100)) .build()); window1->consumeMotionEvent(WithMotionAction(AMOTION_EVENT_ACTION_HOVER_ENTER)); // Sending same window info should not generate any events. mDispatcher->onWindowInfosChanged({{*window0->getInfo(), *window1->getInfo()}, {}, 0, 0}); window1->assertNoEvents(); // Remove the window now, it should receive hover_exit as usual. mDispatcher->onWindowInfosChanged({{}, {}, 0, 0}); window1->consumeMotionEvent(WithMotionAction(AMOTION_EVENT_ACTION_HOVER_EXIT)); window0->assertNoEvents(); } using InputDispatcherConnectedDisplayPointerInWindowTest = InputDispatcherConnectedDisplayTest; TEST_F(InputDispatcherConnectedDisplayPointerInWindowTest, MouseOnWindowOnPrimaryDisplay) { Loading
services/inputflinger/dispatcher/InputDispatcher.cpp +37 −35 Original line number Diff line number Diff line Loading @@ -578,34 +578,6 @@ bool isUserActivityEvent(const EventEntry& eventEntry) { } } // Returns true if the given window can accept pointer events at the given display location. bool windowAcceptsTouchAt(const WindowInfo& windowInfo, ui::LogicalDisplayId displayId, float x, float y, bool isStylus, const ui::Transform& displayTransform) { const auto inputConfig = windowInfo.inputConfig; if (windowInfo.displayId != displayId || inputConfig.test(WindowInfo::InputConfig::NOT_VISIBLE)) { return false; } const bool windowCanInterceptTouch = isStylus && windowInfo.interceptsStylus(); if (inputConfig.test(WindowInfo::InputConfig::NOT_TOUCHABLE) && !windowCanInterceptTouch) { return false; } // Window Manager works in the logical display coordinate space. When it specifies bounds for a // window as (l, t, r, b), the range of x in [l, r) and y in [t, b) are considered to be inside // the window. Points on the right and bottom edges should not be inside the window, so we need // to be careful about performing a hit test when the display is rotated, since the "right" and // "bottom" of the window will be different in the display (un-rotated) space compared to in the // logical display in which WM determined the bounds. Perform the hit test in the logical // display space to ensure these edges are considered correctly in all orientations. const auto touchableRegion = displayTransform.transform(windowInfo.touchableRegion); const auto p = displayTransform.transform(x, y); if (!touchableRegion.contains(std::floor(p.x), std::floor(p.y))) { return false; } return true; } // Returns true if the given window's frame can occlude pointer events at the given display // location. bool windowOccludesTouchAt(const WindowInfo& windowInfo, ui::LogicalDisplayId displayId, float x, Loading Loading @@ -1461,8 +1433,7 @@ sp<WindowInfoHandle> InputDispatcher::DispatcherWindowInfo::findTouchedWindowAt( } const WindowInfo& info = *windowHandle->getInfo(); if (!info.isSpy() && windowAcceptsTouchAt(info, displayId, x, y, isStylus, getDisplayTransform(displayId))) { if (!info.isSpy() && windowAcceptsTouchAt(info, displayId, x, y, isStylus)) { return windowHandle; } } Loading Loading @@ -1503,11 +1474,10 @@ std::vector<sp<WindowInfoHandle>> InputDispatcher::findTouchedSpyWindowsAt( const DispatcherWindowInfo& windowInfos) { // Traverse windows from front to back and gather the touched spy windows. std::vector<sp<WindowInfoHandle>> spyWindows; const ui::Transform displayTransform = windowInfos.getDisplayTransform(displayId); const auto& windowHandles = windowInfos.getWindowHandlesForDisplay(displayId); for (const sp<WindowInfoHandle>& windowHandle : windowHandles) { const WindowInfo& info = *windowHandle->getInfo(); if (!windowAcceptsTouchAt(info, displayId, x, y, isStylus, displayTransform)) { if (!windowInfos.windowAcceptsTouchAt(info, displayId, x, y, isStylus)) { // Skip if the pointer is outside of the window. continue; } Loading Loading @@ -5269,6 +5239,36 @@ ui::Transform InputDispatcher::DispatcherWindowInfo::getRawTransform( return getDisplayTransform(windowInfo.displayId); } bool InputDispatcher::DispatcherWindowInfo::windowAcceptsTouchAt(const gui::WindowInfo& windowInfo, ui::LogicalDisplayId displayId, float x, float y, bool isStylus) const { const auto inputConfig = windowInfo.inputConfig; if (windowInfo.displayId != displayId || inputConfig.test(WindowInfo::InputConfig::NOT_VISIBLE)) { return false; } const bool windowCanInterceptTouch = isStylus && windowInfo.interceptsStylus(); if (inputConfig.test(WindowInfo::InputConfig::NOT_TOUCHABLE) && !windowCanInterceptTouch) { return false; } // Window Manager works in the logical display coordinate space. When it specifies bounds for a // window as (l, t, r, b), the range of x in [l, r) and y in [t, b) are considered to be inside // the window. Points on the right and bottom edges should not be inside the window, so we need // to be careful about performing a hit test when the display is rotated, since the "right" and // "bottom" of the window will be different in the display (un-rotated) space compared to in the // logical display in which WM determined the bounds. Perform the hit test in the logical // display space to ensure these edges are considered correctly in all orientations. const ui::Transform& displayTransform = getDisplayTransform(windowInfo.displayId); const auto touchableRegion = displayTransform.transform(windowInfo.touchableRegion); const auto p = displayTransform.transform(x, y); if (!touchableRegion.contains(std::floor(p.x), std::floor(p.y))) { return false; } return true; } ui::LogicalDisplayId InputDispatcher::DispatcherWindowInfo::getPrimaryDisplayId( ui::LogicalDisplayId displayId) const { if (mTopology.graph.contains(displayId)) { Loading Loading @@ -5616,14 +5616,16 @@ InputDispatcher::DispatcherTouchState::updateHoveringStateFromWindowInfo( std::list<CancellationArgs> cancellations; // Check if the hovering should stop because the window is no longer eligible to receive it // (for example, if the touchable region changed) ui::Transform displayTransform = mWindowInfos.getDisplayTransform(displayId); for (TouchedWindow& touchedWindow : state.windows) { const gui::WindowInfo& windowInfo = *touchedWindow.windowHandle->getInfo(); std::vector<DeviceId> erasedDevices = touchedWindow.eraseHoveringPointersIf( [&](const PointerProperties& properties, float x, float y) { const bool isStylus = properties.toolType == ToolType::STYLUS; // The touchstate's displayId may be different from window's display on the // connected-displays, for this reason we use use window's displayId here. const bool stillAcceptsTouch = windowAcceptsTouchAt(*touchedWindow.windowHandle->getInfo(), displayId, x, y, isStylus, displayTransform); mWindowInfos.windowAcceptsTouchAt(windowInfo, windowInfo.displayId, x, y, isStylus); return !stillAcceptsTouch; }); Loading
services/inputflinger/dispatcher/InputDispatcher.h +4 −0 Original line number Diff line number Diff line Loading @@ -342,6 +342,10 @@ private: sp<android::gui::WindowInfoHandle> findWallpaperWindowBelow( const sp<android::gui::WindowInfoHandle>& windowHandle) const; // Returns true if the given window can accept pointer events at the given display location. bool windowAcceptsTouchAt(const gui::WindowInfo& windowInfo, ui::LogicalDisplayId displayId, float x, float y, bool isStylus) const; bool isTouchTrusted(const TouchOcclusionInfo& occlusionInfo) const; // Returns topology's primary display if the display belongs to it, otherwise the Loading
services/inputflinger/tests/InputDispatcher_test.cpp +45 −1 Original line number Diff line number Diff line Loading @@ -15488,6 +15488,7 @@ TEST_P(TransferOrDontTransferFixture, MouseAndTouchTransferSimultaneousMultiDevi INSTANTIATE_TEST_SUITE_P(WithAndWithoutTransfer, TransferOrDontTransferFixture, testing::Bool()); class InputDispatcherConnectedDisplayTest : public InputDispatcherDragTests { protected: constexpr static int DENSITY_MEDIUM = 160; const DisplayTopologyGraph mTopology = Loading @@ -15503,7 +15504,6 @@ class InputDispatcherConnectedDisplayTest : public InputDispatcherDragTests { {SECOND_DISPLAY_ID, DENSITY_MEDIUM}}) .value(); protected: void SetUp() override { addDisplay(DISPLAY_ID, ui::Transform()); addDisplay(SECOND_DISPLAY_ID, Loading Loading @@ -15674,6 +15674,50 @@ TEST_F(InputDispatcherConnectedDisplayTest, MultiDisplayMouseDragAndDropFromNonP mWindowOnSecondDisplay->assertNoEvents(); } /** * Test that touch state is maintained across windowInfo updates on the non-primary display. * * Create two windows, one on the primary display and another on the secondary display. * Start hovering on the window on the secondary display. * * Update the window info, and verify that the hover state is maintained, and no events are * generated. * * Remove the window on the secondary display, and verify that the window receives a HOVER_EXIT * event. */ TEST_F(InputDispatcherConnectedDisplayTest, NonPrimaryDisplayTouchStateIsMaintainedOnWindowInfoUpdate) { SCOPED_FLAG_OVERRIDE(connected_displays_cursor, true); sp<FakeWindowHandle> window0 = sp<FakeWindowHandle>::make(std::make_shared<FakeApplicationHandle>(), mDispatcher, "TestWindowOnPrimaryDisplay", mTopology.primaryDisplayId); window0->setFrame(Rect(0, 0, 500, 500)); sp<FakeWindowHandle> window1 = sp<FakeWindowHandle>::make(std::make_shared<FakeApplicationHandle>(), mDispatcher, "TestWindowOnNonPrimaryDisplay", SECOND_DISPLAY_ID); window1->setFrame(Rect(0, 0, 500, 500)); mDispatcher->onWindowInfosChanged({{*window0->getInfo(), *window1->getInfo()}, {}, 0, 0}); // Add hover state to window on display 1. mDispatcher->notifyMotion( MotionArgsBuilder(AMOTION_EVENT_ACTION_HOVER_ENTER, AINPUT_SOURCE_MOUSE) .displayId(SECOND_DISPLAY_ID) .pointer(PointerBuilder(MOUSE_POINTER_ID, ToolType::MOUSE).x(100).y(100)) .build()); window1->consumeMotionEvent(WithMotionAction(AMOTION_EVENT_ACTION_HOVER_ENTER)); // Sending same window info should not generate any events. mDispatcher->onWindowInfosChanged({{*window0->getInfo(), *window1->getInfo()}, {}, 0, 0}); window1->assertNoEvents(); // Remove the window now, it should receive hover_exit as usual. mDispatcher->onWindowInfosChanged({{}, {}, 0, 0}); window1->consumeMotionEvent(WithMotionAction(AMOTION_EVENT_ACTION_HOVER_EXIT)); window0->assertNoEvents(); } using InputDispatcherConnectedDisplayPointerInWindowTest = InputDispatcherConnectedDisplayTest; TEST_F(InputDispatcherConnectedDisplayPointerInWindowTest, MouseOnWindowOnPrimaryDisplay) {