Loading services/inputflinger/dispatcher/InputDispatcher.cpp +37 −19 Original line number Diff line number Diff line Loading @@ -2239,7 +2239,9 @@ std::vector<InputTarget> InputDispatcher::findTouchedWindowTargetsLocked( const bool wasDown = oldState != nullptr && oldState->isDown(); const bool isDown = (maskedAction == AMOTION_EVENT_ACTION_DOWN) || (maskedAction == AMOTION_EVENT_ACTION_POINTER_DOWN && !wasDown); const bool newGesture = isDown || maskedAction == AMOTION_EVENT_ACTION_SCROLL || isHoverAction; const bool newGesture = isDown || maskedAction == AMOTION_EVENT_ACTION_SCROLL || maskedAction == AMOTION_EVENT_ACTION_HOVER_ENTER || maskedAction == AMOTION_EVENT_ACTION_HOVER_MOVE; 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 Loading Loading @@ -2427,7 +2429,7 @@ std::vector<InputTarget> InputDispatcher::findTouchedWindowTargetsLocked( /* Case 2: Pointer move, up, cancel or non-splittable pointer down. */ // If the pointer is not currently down, then ignore the event. if (!tempTouchState.isDown()) { if (!tempTouchState.isDown() && maskedAction != AMOTION_EVENT_ACTION_HOVER_EXIT) { LOG(INFO) << "Dropping event because the pointer is not down or we previously " "dropped the pointer down event in display " << displayId << ": " << entry.getDescription(); Loading @@ -2435,6 +2437,20 @@ std::vector<InputTarget> InputDispatcher::findTouchedWindowTargetsLocked( return {}; } // If the pointer is not currently hovering, then ignore the event. if (maskedAction == AMOTION_EVENT_ACTION_HOVER_EXIT) { const int32_t pointerId = entry.pointerProperties[0].id; if (oldState == nullptr || oldState->getWindowsWithHoveringPointer(entry.deviceId, pointerId).empty()) { LOG(INFO) << "Dropping event because the hovering pointer is not in any windows in " "display " << displayId << ": " << entry.getDescription(); outInjectionResult = InputEventInjectionResult::FAILED; return {}; } tempTouchState.removeHoveringPointer(entry.deviceId, pointerId); } addDragEventLocked(entry); // Check whether touches should slip outside of the current foreground window. Loading Loading @@ -2530,21 +2546,6 @@ std::vector<InputTarget> InputDispatcher::findTouchedWindowTargetsLocked( targets); } } // Ensure that we have at least one foreground window or at least one window that cannot be a // foreground target. If we only have windows that are not receiving foreground touches (e.g. we // only have windows getting ACTION_OUTSIDE), then drop the event, because there is no window // that is actually receiving the entire gesture. if (std::none_of(tempTouchState.windows.begin(), tempTouchState.windows.end(), [](const TouchedWindow& touchedWindow) { return !canReceiveForegroundTouches( *touchedWindow.windowHandle->getInfo()) || touchedWindow.targetFlags.test(InputTarget::Flags::FOREGROUND); })) { ALOGI("Dropping event because there is no touched window on display %d to receive it: %s", displayId, entry.getDescription().c_str()); outInjectionResult = InputEventInjectionResult::FAILED; return {}; } // Ensure that all touched windows are valid for injection. if (entry.injectionState != nullptr) { Loading Loading @@ -2586,7 +2587,7 @@ std::vector<InputTarget> InputDispatcher::findTouchedWindowTargetsLocked( } } // Success! Output targets from the touch state. // Output targets from the touch state. for (const TouchedWindow& touchedWindow : tempTouchState.windows) { if (touchedWindow.pointerIds.none() && !touchedWindow.hasHoveringPointers(entry.deviceId)) { // Windows with hovering pointers are getting persisted inside TouchState. Loading @@ -2598,6 +2599,23 @@ std::vector<InputTarget> InputDispatcher::findTouchedWindowTargetsLocked( targets); } if (targets.empty()) { LOG(INFO) << "Dropping event because no targets were found: " << entry.getDescription(); outInjectionResult = InputEventInjectionResult::FAILED; return {}; } // If we only have windows getting ACTION_OUTSIDE, then drop the event, because there is no // window that is actually receiving the entire gesture. if (std::all_of(targets.begin(), targets.end(), [](const InputTarget& target) { return target.flags.test(InputTarget::Flags::DISPATCH_AS_OUTSIDE); })) { LOG(INFO) << "Dropping event because all windows would just receive ACTION_OUTSIDE: " << entry.getDescription(); outInjectionResult = InputEventInjectionResult::FAILED; return {}; } outInjectionResult = InputEventInjectionResult::SUCCEEDED; // Drop the outside or hover touch windows since we will not care about them // in the next iteration. Loading Loading @@ -5483,7 +5501,7 @@ void InputDispatcher::logDispatchStateLocked() const { std::string line; while (std::getline(stream, line, '\n')) { ALOGD("%s", line.c_str()); ALOGI("%s", line.c_str()); } } Loading services/inputflinger/tests/InputDispatcher_test.cpp +114 −1 Original line number Diff line number Diff line Loading @@ -62,6 +62,7 @@ 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_MOVE = AMOTION_EVENT_ACTION_HOVER_MOVE; 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; Loading Loading @@ -2454,6 +2455,116 @@ TEST_F(InputDispatcherTest, HoverFromLeftToRightAndTap) { rightWindow->assertNoEvents(); } /** * Start hovering in a window. While this hover is still active, make another window appear on top. * The top, obstructing window has no input channel, so it's not supposed to receive input. * While the top window is present, the hovering is stopped. * Later, hovering gets resumed again. * Ensure that new hover gesture is handled correctly. * This test reproduces a crash where the HOVER_EXIT event wasn't getting dispatched correctly * to the window that's currently being hovered over. */ TEST_F(InputDispatcherTest, HoverWhileWindowAppears) { 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, 200, 200)); // Only a single window is present at first mDispatcher->setInputWindows({{ADISPLAY_ID_DEFAULT, {window}}}); // Start hovering in the window mDispatcher->notifyMotion(MotionArgsBuilder(ACTION_HOVER_ENTER, AINPUT_SOURCE_STYLUS) .pointer(PointerBuilder(0, ToolType::STYLUS).x(100).y(100)) .build()); window->consumeMotionEvent(WithMotionAction(ACTION_HOVER_ENTER)); // Now, an obscuring window appears! sp<FakeWindowHandle> obscuringWindow = sp<FakeWindowHandle>::make(application, mDispatcher, "Obscuring window", ADISPLAY_ID_DEFAULT, /*token=*/std::make_optional<sp<IBinder>>(nullptr)); obscuringWindow->setFrame(Rect(0, 0, 200, 200)); obscuringWindow->setTouchOcclusionMode(TouchOcclusionMode::BLOCK_UNTRUSTED); obscuringWindow->setOwnerInfo(SECONDARY_WINDOW_PID, SECONDARY_WINDOW_UID); obscuringWindow->setNoInputChannel(true); obscuringWindow->setFocusable(false); obscuringWindow->setAlpha(1.0); mDispatcher->setInputWindows({{ADISPLAY_ID_DEFAULT, {obscuringWindow, window}}}); // While this new obscuring window is present, the hovering is stopped mDispatcher->notifyMotion(MotionArgsBuilder(ACTION_HOVER_EXIT, AINPUT_SOURCE_STYLUS) .pointer(PointerBuilder(0, ToolType::STYLUS).x(100).y(100)) .build()); window->consumeMotionEvent(WithMotionAction(ACTION_HOVER_EXIT)); // Now the obscuring window goes away. mDispatcher->setInputWindows({{ADISPLAY_ID_DEFAULT, {window}}}); // And a new hover gesture starts. mDispatcher->notifyMotion(MotionArgsBuilder(ACTION_HOVER_ENTER, AINPUT_SOURCE_STYLUS) .pointer(PointerBuilder(0, ToolType::STYLUS).x(100).y(100)) .build()); window->consumeMotionEvent(WithMotionAction(ACTION_HOVER_ENTER)); } /** * Same test as 'HoverWhileWindowAppears' above, but here, we also send some HOVER_MOVE events to * the obscuring window. */ TEST_F(InputDispatcherTest, HoverMoveWhileWindowAppears) { 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, 200, 200)); // Only a single window is present at first mDispatcher->setInputWindows({{ADISPLAY_ID_DEFAULT, {window}}}); // Start hovering in the window mDispatcher->notifyMotion(MotionArgsBuilder(ACTION_HOVER_ENTER, AINPUT_SOURCE_STYLUS) .pointer(PointerBuilder(0, ToolType::STYLUS).x(100).y(100)) .build()); window->consumeMotionEvent(WithMotionAction(ACTION_HOVER_ENTER)); // Now, an obscuring window appears! sp<FakeWindowHandle> obscuringWindow = sp<FakeWindowHandle>::make(application, mDispatcher, "Obscuring window", ADISPLAY_ID_DEFAULT, /*token=*/std::make_optional<sp<IBinder>>(nullptr)); obscuringWindow->setFrame(Rect(0, 0, 200, 200)); obscuringWindow->setTouchOcclusionMode(TouchOcclusionMode::BLOCK_UNTRUSTED); obscuringWindow->setOwnerInfo(SECONDARY_WINDOW_PID, SECONDARY_WINDOW_UID); obscuringWindow->setNoInputChannel(true); obscuringWindow->setFocusable(false); obscuringWindow->setAlpha(1.0); mDispatcher->setInputWindows({{ADISPLAY_ID_DEFAULT, {obscuringWindow, window}}}); // While this new obscuring window is present, the hovering continues. The event can't go to the // bottom window due to obstructed touches, so it should generate HOVER_EXIT for that window. mDispatcher->notifyMotion(MotionArgsBuilder(ACTION_HOVER_MOVE, AINPUT_SOURCE_STYLUS) .pointer(PointerBuilder(0, ToolType::STYLUS).x(100).y(100)) .build()); obscuringWindow->assertNoEvents(); window->consumeMotionEvent(WithMotionAction(ACTION_HOVER_EXIT)); // Now the obscuring window goes away. mDispatcher->setInputWindows({{ADISPLAY_ID_DEFAULT, {window}}}); // Hovering continues in the same position. The hovering pointer re-enters the bottom window, // so it should generate a HOVER_ENTER mDispatcher->notifyMotion(MotionArgsBuilder(ACTION_HOVER_MOVE, AINPUT_SOURCE_STYLUS) .pointer(PointerBuilder(0, ToolType::STYLUS).x(100).y(100)) .build()); window->consumeMotionEvent(WithMotionAction(ACTION_HOVER_ENTER)); // Now the MOVE should be getting dispatched normally mDispatcher->notifyMotion(MotionArgsBuilder(ACTION_HOVER_MOVE, AINPUT_SOURCE_STYLUS) .pointer(PointerBuilder(0, ToolType::STYLUS).x(110).y(110)) .build()); window->consumeMotionEvent(WithMotionAction(ACTION_HOVER_MOVE)); } /** * 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 Loading Loading @@ -3446,7 +3557,9 @@ TEST_F(InputDispatcherTest, HoverEnterMouseClickAndHoverExit) { .build())); window->consumeMotionUp(ADISPLAY_ID_DEFAULT); ASSERT_EQ(InputEventInjectionResult::SUCCEEDED, // We already canceled the hovering implicitly by injecting the "DOWN" event without lifting the // hover first. Therefore, injection of HOVER_EXIT is inconsistent, and should fail. ASSERT_EQ(InputEventInjectionResult::FAILED, injectMotionEvent(mDispatcher, MotionEventBuilder(AMOTION_EVENT_ACTION_HOVER_EXIT, AINPUT_SOURCE_MOUSE) Loading Loading
services/inputflinger/dispatcher/InputDispatcher.cpp +37 −19 Original line number Diff line number Diff line Loading @@ -2239,7 +2239,9 @@ std::vector<InputTarget> InputDispatcher::findTouchedWindowTargetsLocked( const bool wasDown = oldState != nullptr && oldState->isDown(); const bool isDown = (maskedAction == AMOTION_EVENT_ACTION_DOWN) || (maskedAction == AMOTION_EVENT_ACTION_POINTER_DOWN && !wasDown); const bool newGesture = isDown || maskedAction == AMOTION_EVENT_ACTION_SCROLL || isHoverAction; const bool newGesture = isDown || maskedAction == AMOTION_EVENT_ACTION_SCROLL || maskedAction == AMOTION_EVENT_ACTION_HOVER_ENTER || maskedAction == AMOTION_EVENT_ACTION_HOVER_MOVE; 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 Loading Loading @@ -2427,7 +2429,7 @@ std::vector<InputTarget> InputDispatcher::findTouchedWindowTargetsLocked( /* Case 2: Pointer move, up, cancel or non-splittable pointer down. */ // If the pointer is not currently down, then ignore the event. if (!tempTouchState.isDown()) { if (!tempTouchState.isDown() && maskedAction != AMOTION_EVENT_ACTION_HOVER_EXIT) { LOG(INFO) << "Dropping event because the pointer is not down or we previously " "dropped the pointer down event in display " << displayId << ": " << entry.getDescription(); Loading @@ -2435,6 +2437,20 @@ std::vector<InputTarget> InputDispatcher::findTouchedWindowTargetsLocked( return {}; } // If the pointer is not currently hovering, then ignore the event. if (maskedAction == AMOTION_EVENT_ACTION_HOVER_EXIT) { const int32_t pointerId = entry.pointerProperties[0].id; if (oldState == nullptr || oldState->getWindowsWithHoveringPointer(entry.deviceId, pointerId).empty()) { LOG(INFO) << "Dropping event because the hovering pointer is not in any windows in " "display " << displayId << ": " << entry.getDescription(); outInjectionResult = InputEventInjectionResult::FAILED; return {}; } tempTouchState.removeHoveringPointer(entry.deviceId, pointerId); } addDragEventLocked(entry); // Check whether touches should slip outside of the current foreground window. Loading Loading @@ -2530,21 +2546,6 @@ std::vector<InputTarget> InputDispatcher::findTouchedWindowTargetsLocked( targets); } } // Ensure that we have at least one foreground window or at least one window that cannot be a // foreground target. If we only have windows that are not receiving foreground touches (e.g. we // only have windows getting ACTION_OUTSIDE), then drop the event, because there is no window // that is actually receiving the entire gesture. if (std::none_of(tempTouchState.windows.begin(), tempTouchState.windows.end(), [](const TouchedWindow& touchedWindow) { return !canReceiveForegroundTouches( *touchedWindow.windowHandle->getInfo()) || touchedWindow.targetFlags.test(InputTarget::Flags::FOREGROUND); })) { ALOGI("Dropping event because there is no touched window on display %d to receive it: %s", displayId, entry.getDescription().c_str()); outInjectionResult = InputEventInjectionResult::FAILED; return {}; } // Ensure that all touched windows are valid for injection. if (entry.injectionState != nullptr) { Loading Loading @@ -2586,7 +2587,7 @@ std::vector<InputTarget> InputDispatcher::findTouchedWindowTargetsLocked( } } // Success! Output targets from the touch state. // Output targets from the touch state. for (const TouchedWindow& touchedWindow : tempTouchState.windows) { if (touchedWindow.pointerIds.none() && !touchedWindow.hasHoveringPointers(entry.deviceId)) { // Windows with hovering pointers are getting persisted inside TouchState. Loading @@ -2598,6 +2599,23 @@ std::vector<InputTarget> InputDispatcher::findTouchedWindowTargetsLocked( targets); } if (targets.empty()) { LOG(INFO) << "Dropping event because no targets were found: " << entry.getDescription(); outInjectionResult = InputEventInjectionResult::FAILED; return {}; } // If we only have windows getting ACTION_OUTSIDE, then drop the event, because there is no // window that is actually receiving the entire gesture. if (std::all_of(targets.begin(), targets.end(), [](const InputTarget& target) { return target.flags.test(InputTarget::Flags::DISPATCH_AS_OUTSIDE); })) { LOG(INFO) << "Dropping event because all windows would just receive ACTION_OUTSIDE: " << entry.getDescription(); outInjectionResult = InputEventInjectionResult::FAILED; return {}; } outInjectionResult = InputEventInjectionResult::SUCCEEDED; // Drop the outside or hover touch windows since we will not care about them // in the next iteration. Loading Loading @@ -5483,7 +5501,7 @@ void InputDispatcher::logDispatchStateLocked() const { std::string line; while (std::getline(stream, line, '\n')) { ALOGD("%s", line.c_str()); ALOGI("%s", line.c_str()); } } Loading
services/inputflinger/tests/InputDispatcher_test.cpp +114 −1 Original line number Diff line number Diff line Loading @@ -62,6 +62,7 @@ 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_MOVE = AMOTION_EVENT_ACTION_HOVER_MOVE; 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; Loading Loading @@ -2454,6 +2455,116 @@ TEST_F(InputDispatcherTest, HoverFromLeftToRightAndTap) { rightWindow->assertNoEvents(); } /** * Start hovering in a window. While this hover is still active, make another window appear on top. * The top, obstructing window has no input channel, so it's not supposed to receive input. * While the top window is present, the hovering is stopped. * Later, hovering gets resumed again. * Ensure that new hover gesture is handled correctly. * This test reproduces a crash where the HOVER_EXIT event wasn't getting dispatched correctly * to the window that's currently being hovered over. */ TEST_F(InputDispatcherTest, HoverWhileWindowAppears) { 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, 200, 200)); // Only a single window is present at first mDispatcher->setInputWindows({{ADISPLAY_ID_DEFAULT, {window}}}); // Start hovering in the window mDispatcher->notifyMotion(MotionArgsBuilder(ACTION_HOVER_ENTER, AINPUT_SOURCE_STYLUS) .pointer(PointerBuilder(0, ToolType::STYLUS).x(100).y(100)) .build()); window->consumeMotionEvent(WithMotionAction(ACTION_HOVER_ENTER)); // Now, an obscuring window appears! sp<FakeWindowHandle> obscuringWindow = sp<FakeWindowHandle>::make(application, mDispatcher, "Obscuring window", ADISPLAY_ID_DEFAULT, /*token=*/std::make_optional<sp<IBinder>>(nullptr)); obscuringWindow->setFrame(Rect(0, 0, 200, 200)); obscuringWindow->setTouchOcclusionMode(TouchOcclusionMode::BLOCK_UNTRUSTED); obscuringWindow->setOwnerInfo(SECONDARY_WINDOW_PID, SECONDARY_WINDOW_UID); obscuringWindow->setNoInputChannel(true); obscuringWindow->setFocusable(false); obscuringWindow->setAlpha(1.0); mDispatcher->setInputWindows({{ADISPLAY_ID_DEFAULT, {obscuringWindow, window}}}); // While this new obscuring window is present, the hovering is stopped mDispatcher->notifyMotion(MotionArgsBuilder(ACTION_HOVER_EXIT, AINPUT_SOURCE_STYLUS) .pointer(PointerBuilder(0, ToolType::STYLUS).x(100).y(100)) .build()); window->consumeMotionEvent(WithMotionAction(ACTION_HOVER_EXIT)); // Now the obscuring window goes away. mDispatcher->setInputWindows({{ADISPLAY_ID_DEFAULT, {window}}}); // And a new hover gesture starts. mDispatcher->notifyMotion(MotionArgsBuilder(ACTION_HOVER_ENTER, AINPUT_SOURCE_STYLUS) .pointer(PointerBuilder(0, ToolType::STYLUS).x(100).y(100)) .build()); window->consumeMotionEvent(WithMotionAction(ACTION_HOVER_ENTER)); } /** * Same test as 'HoverWhileWindowAppears' above, but here, we also send some HOVER_MOVE events to * the obscuring window. */ TEST_F(InputDispatcherTest, HoverMoveWhileWindowAppears) { 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, 200, 200)); // Only a single window is present at first mDispatcher->setInputWindows({{ADISPLAY_ID_DEFAULT, {window}}}); // Start hovering in the window mDispatcher->notifyMotion(MotionArgsBuilder(ACTION_HOVER_ENTER, AINPUT_SOURCE_STYLUS) .pointer(PointerBuilder(0, ToolType::STYLUS).x(100).y(100)) .build()); window->consumeMotionEvent(WithMotionAction(ACTION_HOVER_ENTER)); // Now, an obscuring window appears! sp<FakeWindowHandle> obscuringWindow = sp<FakeWindowHandle>::make(application, mDispatcher, "Obscuring window", ADISPLAY_ID_DEFAULT, /*token=*/std::make_optional<sp<IBinder>>(nullptr)); obscuringWindow->setFrame(Rect(0, 0, 200, 200)); obscuringWindow->setTouchOcclusionMode(TouchOcclusionMode::BLOCK_UNTRUSTED); obscuringWindow->setOwnerInfo(SECONDARY_WINDOW_PID, SECONDARY_WINDOW_UID); obscuringWindow->setNoInputChannel(true); obscuringWindow->setFocusable(false); obscuringWindow->setAlpha(1.0); mDispatcher->setInputWindows({{ADISPLAY_ID_DEFAULT, {obscuringWindow, window}}}); // While this new obscuring window is present, the hovering continues. The event can't go to the // bottom window due to obstructed touches, so it should generate HOVER_EXIT for that window. mDispatcher->notifyMotion(MotionArgsBuilder(ACTION_HOVER_MOVE, AINPUT_SOURCE_STYLUS) .pointer(PointerBuilder(0, ToolType::STYLUS).x(100).y(100)) .build()); obscuringWindow->assertNoEvents(); window->consumeMotionEvent(WithMotionAction(ACTION_HOVER_EXIT)); // Now the obscuring window goes away. mDispatcher->setInputWindows({{ADISPLAY_ID_DEFAULT, {window}}}); // Hovering continues in the same position. The hovering pointer re-enters the bottom window, // so it should generate a HOVER_ENTER mDispatcher->notifyMotion(MotionArgsBuilder(ACTION_HOVER_MOVE, AINPUT_SOURCE_STYLUS) .pointer(PointerBuilder(0, ToolType::STYLUS).x(100).y(100)) .build()); window->consumeMotionEvent(WithMotionAction(ACTION_HOVER_ENTER)); // Now the MOVE should be getting dispatched normally mDispatcher->notifyMotion(MotionArgsBuilder(ACTION_HOVER_MOVE, AINPUT_SOURCE_STYLUS) .pointer(PointerBuilder(0, ToolType::STYLUS).x(110).y(110)) .build()); window->consumeMotionEvent(WithMotionAction(ACTION_HOVER_MOVE)); } /** * 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 Loading Loading @@ -3446,7 +3557,9 @@ TEST_F(InputDispatcherTest, HoverEnterMouseClickAndHoverExit) { .build())); window->consumeMotionUp(ADISPLAY_ID_DEFAULT); ASSERT_EQ(InputEventInjectionResult::SUCCEEDED, // We already canceled the hovering implicitly by injecting the "DOWN" event without lifting the // hover first. Therefore, injection of HOVER_EXIT is inconsistent, and should fail. ASSERT_EQ(InputEventInjectionResult::FAILED, injectMotionEvent(mDispatcher, MotionEventBuilder(AMOTION_EVENT_ACTION_HOVER_EXIT, AINPUT_SOURCE_MOUSE) Loading