diff options
| -rw-r--r-- | services/inputflinger/dispatcher/InputDispatcher.cpp | 33 | ||||
| -rw-r--r-- | services/inputflinger/dispatcher/InputDispatcher.h | 1 | ||||
| -rw-r--r-- | services/inputflinger/tests/InputDispatcher_test.cpp | 86 |
3 files changed, 109 insertions, 11 deletions
diff --git a/services/inputflinger/dispatcher/InputDispatcher.cpp b/services/inputflinger/dispatcher/InputDispatcher.cpp index ac736665bf..b9cb4ad8a9 100644 --- a/services/inputflinger/dispatcher/InputDispatcher.cpp +++ b/services/inputflinger/dispatcher/InputDispatcher.cpp @@ -107,6 +107,8 @@ constexpr int LOGTAG_INPUT_INTERACTION = 62000; constexpr int LOGTAG_INPUT_FOCUS = 62001; constexpr int LOGTAG_INPUT_CANCEL = 62003; +const ui::Transform kIdentityTransform; + inline nsecs_t now() { return systemTime(SYSTEM_TIME_MONOTONIC); } @@ -460,7 +462,7 @@ 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, int32_t displayId, float x, float y, - bool isStylus) { + bool isStylus, const ui::Transform& displayTransform) { const auto inputConfig = windowInfo.inputConfig; if (windowInfo.displayId != displayId || inputConfig.test(WindowInfo::InputConfig::NOT_VISIBLE)) { @@ -470,7 +472,17 @@ bool windowAcceptsTouchAt(const WindowInfo& windowInfo, int32_t displayId, float if (inputConfig.test(WindowInfo::InputConfig::NOT_TOUCHABLE) && !windowCanInterceptTouch) { return false; } - if (!windowInfo.touchableRegionContainsPoint(x, y)) { + + // 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; @@ -1072,7 +1084,8 @@ sp<WindowInfoHandle> InputDispatcher::findTouchedWindowAtLocked(int32_t displayI } const WindowInfo& info = *windowHandle->getInfo(); - if (!info.isSpy() && windowAcceptsTouchAt(info, displayId, x, y, isStylus)) { + if (!info.isSpy() && + windowAcceptsTouchAt(info, displayId, x, y, isStylus, getTransformLocked(displayId))) { return windowHandle; } @@ -1093,7 +1106,7 @@ std::vector<sp<WindowInfoHandle>> InputDispatcher::findTouchedSpyWindowsAtLocked for (const sp<WindowInfoHandle>& windowHandle : windowHandles) { const WindowInfo& info = *windowHandle->getInfo(); - if (!windowAcceptsTouchAt(info, displayId, x, y, isStylus)) { + if (!windowAcceptsTouchAt(info, displayId, x, y, isStylus, getTransformLocked(displayId))) { continue; } if (!info.isSpy()) { @@ -2120,7 +2133,8 @@ InputEventInjectionResult InputDispatcher::findTouchedWindowTargetsLocked( } if (newTouchedWindows.empty()) { - ALOGI("Dropping event because there is no touchable window at (%d, %d) on display %d.", + ALOGI("Dropping event because there is no touchable window at (%.1f, %.1f) on display " + "%d.", x, y, displayId); injectionResult = InputEventInjectionResult::FAILED; goto Failed; @@ -2154,7 +2168,8 @@ InputEventInjectionResult InputDispatcher::findTouchedWindowTargetsLocked( computeTouchOcclusionInfoLocked(windowHandle, x, y); if (!isTouchTrustedLocked(occlusionInfo)) { if (DEBUG_TOUCH_OCCLUSION) { - ALOGD("Stack of obscuring windows during untrusted touch (%d, %d):", x, y); + ALOGD("Stack of obscuring windows during untrusted touch (%.1f, %.1f):", x, + y); for (const auto& log : occlusionInfo.debugInfo) { ALOGD("%s", log.c_str()); } @@ -4583,6 +4598,12 @@ sp<WindowInfoHandle> InputDispatcher::getFocusedWindowHandleLocked(int displayId return getWindowHandleLocked(focusedToken, displayId); } +ui::Transform InputDispatcher::getTransformLocked(int32_t displayId) const { + auto displayInfoIt = mDisplayInfos.find(displayId); + return displayInfoIt != mDisplayInfos.end() ? displayInfoIt->second.transform + : kIdentityTransform; +} + bool InputDispatcher::hasResponsiveConnectionLocked(WindowInfoHandle& windowHandle) const { sp<Connection> connection = getConnectionLocked(windowHandle.getToken()); const bool noInputChannel = diff --git a/services/inputflinger/dispatcher/InputDispatcher.h b/services/inputflinger/dispatcher/InputDispatcher.h index c2277a8753..7769b9eabf 100644 --- a/services/inputflinger/dispatcher/InputDispatcher.h +++ b/services/inputflinger/dispatcher/InputDispatcher.h @@ -370,6 +370,7 @@ private: int32_t displayId) const REQUIRES(mLock); sp<android::gui::WindowInfoHandle> getWindowHandleLocked( const sp<IBinder>& windowHandleToken) const REQUIRES(mLock); + ui::Transform getTransformLocked(int32_t displayId) const REQUIRES(mLock); // Same function as above, but faster. Since displayId is provided, this avoids the need // to loop through all displays. diff --git a/services/inputflinger/tests/InputDispatcher_test.cpp b/services/inputflinger/tests/InputDispatcher_test.cpp index b2575fe025..6280cf7cb6 100644 --- a/services/inputflinger/tests/InputDispatcher_test.cpp +++ b/services/inputflinger/tests/InputDispatcher_test.cpp @@ -2493,8 +2493,7 @@ class InputDispatcherDisplayProjectionTest : public InputDispatcherTest { public: void SetUp() override { InputDispatcherTest::SetUp(); - mDisplayInfos.clear(); - mWindowInfos.clear(); + removeAllWindowsAndDisplays(); } void addDisplayInfo(int displayId, const ui::Transform& transform) { @@ -2510,6 +2509,11 @@ public: mDispatcher->onWindowInfosChanged(mWindowInfos, mDisplayInfos); } + void removeAllWindowsAndDisplays() { + mDisplayInfos.clear(); + mWindowInfos.clear(); + } + // Set up a test scenario where the display has a scaled projection and there are two windows // on the display. std::pair<sp<FakeWindowHandle>, sp<FakeWindowHandle>> setupScaledDisplayScenario() { @@ -2541,11 +2545,11 @@ private: std::vector<gui::WindowInfo> mWindowInfos; }; -TEST_F(InputDispatcherDisplayProjectionTest, HitTestsInDisplaySpace) { +TEST_F(InputDispatcherDisplayProjectionTest, HitTestCoordinateSpaceConsistency) { auto [firstWindow, secondWindow] = setupScaledDisplayScenario(); // Send down to the first window. The point is represented in the display space. The point is - // selected so that if the hit test was done with the transform applied to it, then it would - // end up in the incorrect window. + // selected so that if the hit test was performed with the point and the bounds being in + // different coordinate spaces, the event would end up in the incorrect window. NotifyMotionArgs downMotionArgs = generateMotionArgs(AMOTION_EVENT_ACTION_DOWN, AINPUT_SOURCE_TOUCHSCREEN, ADISPLAY_ID_DEFAULT, {PointF{75, 55}}); @@ -2620,6 +2624,78 @@ TEST_F(InputDispatcherDisplayProjectionTest, WindowGetsEventsInCorrectCoordinate EXPECT_EQ(80, event->getY(0)); } +/** Ensure consistent behavior of InputDispatcher in all orientations. */ +class InputDispatcherDisplayOrientationFixture + : public InputDispatcherDisplayProjectionTest, + public ::testing::WithParamInterface<ui::Rotation> {}; + +// This test verifies the touchable region of a window for all rotations of the display by tapping +// in different locations on the display, specifically points close to the four corners of a +// window. +TEST_P(InputDispatcherDisplayOrientationFixture, HitTestInDifferentOrientations) { + constexpr static int32_t displayWidth = 400; + constexpr static int32_t displayHeight = 800; + + std::shared_ptr<FakeApplicationHandle> application = std::make_shared<FakeApplicationHandle>(); + + const auto rotation = GetParam(); + + // Set up the display with the specified rotation. + const bool isRotated = rotation == ui::ROTATION_90 || rotation == ui::ROTATION_270; + const int32_t logicalDisplayWidth = isRotated ? displayHeight : displayWidth; + const int32_t logicalDisplayHeight = isRotated ? displayWidth : displayHeight; + const ui::Transform displayTransform(ui::Transform::toRotationFlags(rotation), + logicalDisplayWidth, logicalDisplayHeight); + addDisplayInfo(ADISPLAY_ID_DEFAULT, displayTransform); + + // Create a window with its bounds determined in the logical display. + const Rect frameInLogicalDisplay(100, 100, 200, 300); + const Rect frameInDisplay = displayTransform.inverse().transform(frameInLogicalDisplay); + sp<FakeWindowHandle> window = + sp<FakeWindowHandle>::make(application, mDispatcher, "Window", ADISPLAY_ID_DEFAULT); + window->setFrame(frameInDisplay, displayTransform); + addWindow(window); + + // The following points in logical display space should be inside the window. + static const std::array<vec2, 4> insidePoints{ + {{100, 100}, {199.99, 100}, {100, 299.99}, {199.99, 299.99}}}; + for (const auto pointInsideWindow : insidePoints) { + const vec2 p = displayTransform.inverse().transform(pointInsideWindow); + const PointF pointInDisplaySpace{p.x, p.y}; + const auto down = generateMotionArgs(AMOTION_EVENT_ACTION_DOWN, AINPUT_SOURCE_TOUCHSCREEN, + ADISPLAY_ID_DEFAULT, {pointInDisplaySpace}); + mDispatcher->notifyMotion(&down); + window->consumeMotionDown(); + + const auto up = generateMotionArgs(AMOTION_EVENT_ACTION_UP, AINPUT_SOURCE_TOUCHSCREEN, + ADISPLAY_ID_DEFAULT, {pointInDisplaySpace}); + mDispatcher->notifyMotion(&up); + window->consumeMotionUp(); + } + + // The following points in logical display space should be outside the window. + static const std::array<vec2, 5> outsidePoints{ + {{200, 100}, {100, 300}, {200, 300}, {100, 99.99}, {99.99, 100}}}; + for (const auto pointOutsideWindow : outsidePoints) { + const vec2 p = displayTransform.inverse().transform(pointOutsideWindow); + const PointF pointInDisplaySpace{p.x, p.y}; + const auto down = generateMotionArgs(AMOTION_EVENT_ACTION_DOWN, AINPUT_SOURCE_TOUCHSCREEN, + ADISPLAY_ID_DEFAULT, {pointInDisplaySpace}); + mDispatcher->notifyMotion(&down); + + const auto up = generateMotionArgs(AMOTION_EVENT_ACTION_UP, AINPUT_SOURCE_TOUCHSCREEN, + ADISPLAY_ID_DEFAULT, {pointInDisplaySpace}); + mDispatcher->notifyMotion(&up); + } + window->assertNoEvents(); +} + +// Run the precision tests for all rotations. +INSTANTIATE_TEST_SUITE_P(InputDispatcherDisplayOrientationTests, + InputDispatcherDisplayOrientationFixture, + ::testing::Values(ui::ROTATION_0, ui::ROTATION_90, ui::ROTATION_180, + ui::ROTATION_270)); + using TransferFunction = std::function<bool(const std::unique_ptr<InputDispatcher>& dispatcher, sp<IBinder>, sp<IBinder>)>; |