diff options
-rw-r--r-- | include/input/PrintTools.h | 6 | ||||
-rw-r--r-- | services/inputflinger/dispatcher/InputDispatcher.cpp | 164 | ||||
-rw-r--r-- | services/inputflinger/dispatcher/InputDispatcher.h | 3 | ||||
-rw-r--r-- | services/inputflinger/dispatcher/TouchState.cpp | 55 | ||||
-rw-r--r-- | services/inputflinger/dispatcher/TouchState.h | 11 | ||||
-rw-r--r-- | services/inputflinger/dispatcher/TouchedWindow.cpp | 44 | ||||
-rw-r--r-- | services/inputflinger/dispatcher/TouchedWindow.h | 12 | ||||
-rw-r--r-- | services/inputflinger/tests/InputDispatcher_test.cpp | 44 |
8 files changed, 260 insertions, 79 deletions
diff --git a/include/input/PrintTools.h b/include/input/PrintTools.h index e24344b3f1..a20544b5fd 100644 --- a/include/input/PrintTools.h +++ b/include/input/PrintTools.h @@ -16,6 +16,7 @@ #pragma once +#include <bitset> #include <map> #include <optional> #include <set> @@ -23,6 +24,11 @@ namespace android { +template <size_t N> +std::string bitsetToString(const std::bitset<N>& bitset) { + return bitset.to_string(); +} + template <typename T> inline std::string constToString(const T& v) { return std::to_string(v); diff --git a/services/inputflinger/dispatcher/InputDispatcher.cpp b/services/inputflinger/dispatcher/InputDispatcher.cpp index 4aac377b42..4d3e6def00 100644 --- a/services/inputflinger/dispatcher/InputDispatcher.cpp +++ b/services/inputflinger/dispatcher/InputDispatcher.cpp @@ -554,6 +554,65 @@ std::optional<nsecs_t> getDownTime(const EventEntry& eventEntry) { return std::nullopt; } +/** + * Compare the old touch state to the new touch state, and generate the corresponding touched + * windows (== input targets). + * If a window had the hovering pointer, but now it doesn't, produce HOVER_EXIT for that window. + * If the pointer just entered the new window, produce HOVER_ENTER. + * For pointers remaining in the window, produce HOVER_MOVE. + */ +std::vector<TouchedWindow> getHoveringWindowsLocked(const TouchState* oldState, + const TouchState& newTouchState, + const MotionEntry& entry) { + std::vector<TouchedWindow> out; + const int32_t maskedAction = MotionEvent::getActionMasked(entry.action); + if (maskedAction != AMOTION_EVENT_ACTION_HOVER_ENTER && + maskedAction != AMOTION_EVENT_ACTION_HOVER_MOVE && + maskedAction != AMOTION_EVENT_ACTION_HOVER_EXIT) { + // Not a hover event - don't need to do anything + return out; + } + + // We should consider all hovering pointers here. But for now, just use the first one + const int32_t pointerId = entry.pointerProperties[0].id; + + std::set<sp<WindowInfoHandle>> oldWindows; + if (oldState != nullptr) { + oldWindows = oldState->getWindowsWithHoveringPointer(entry.deviceId, pointerId); + } + + std::set<sp<WindowInfoHandle>> newWindows = + newTouchState.getWindowsWithHoveringPointer(entry.deviceId, pointerId); + + // If the pointer is no longer in the new window set, send HOVER_EXIT. + for (const sp<WindowInfoHandle>& oldWindow : oldWindows) { + if (newWindows.find(oldWindow) == newWindows.end()) { + TouchedWindow touchedWindow; + touchedWindow.windowHandle = oldWindow; + touchedWindow.targetFlags = InputTarget::Flags::DISPATCH_AS_HOVER_EXIT; + touchedWindow.pointerIds.markBit(pointerId); + out.push_back(touchedWindow); + } + } + + for (const sp<WindowInfoHandle>& newWindow : newWindows) { + TouchedWindow touchedWindow; + touchedWindow.windowHandle = newWindow; + if (oldWindows.find(newWindow) == oldWindows.end()) { + // Any windows that have this pointer now, and didn't have it before, should get + // HOVER_ENTER + touchedWindow.targetFlags = InputTarget::Flags::DISPATCH_AS_HOVER_ENTER; + } else { + // This pointer was already sent to the window. Use ACTION_HOVER_MOVE. + LOG_ALWAYS_FATAL_IF(maskedAction != AMOTION_EVENT_ACTION_HOVER_MOVE); + touchedWindow.targetFlags = InputTarget::Flags::DISPATCH_AS_IS; + } + touchedWindow.pointerIds.markBit(pointerId); + out.push_back(touchedWindow); + } + return out; +} + } // namespace // --- InputDispatcher --- @@ -2083,8 +2142,6 @@ std::vector<TouchedWindow> InputDispatcher::findTouchedWindowTargetsLocked( // Update the touch state as needed based on the properties of the touch event. outInjectionResult = InputEventInjectionResult::PENDING; - sp<WindowInfoHandle> newHoverWindowHandle(mLastHoverWindowHandle); - sp<WindowInfoHandle> newTouchedWindowHandle; // Copy current touch state into tempTouchState. // This state will be used to update mTouchStatesByDisplay at the end of this function. @@ -2117,7 +2174,7 @@ std::vector<TouchedWindow> InputDispatcher::findTouchedWindowTargetsLocked( outInjectionResult = InputEventInjectionResult::FAILED; return touchedWindows; // wrong device } - tempTouchState.reset(); + tempTouchState.clearWindowsWithoutPointers(); tempTouchState.deviceId = entry.deviceId; tempTouchState.source = entry.source; isSplit = false; @@ -2130,14 +2187,21 @@ std::vector<TouchedWindow> InputDispatcher::findTouchedWindowTargetsLocked( return touchedWindows; // wrong device } + if (isHoverAction) { + // For hover actions, we will treat 'tempTouchState' as a new state, so let's erase + // all of the existing hovering pointers and recompute. + tempTouchState.clearHoveringPointers(); + } + if (newGesture || (isSplit && maskedAction == AMOTION_EVENT_ACTION_POINTER_DOWN)) { /* Case 1: New splittable pointer going down, or need target for hover or scroll. */ const auto [x, y] = resolveTouchedPosition(entry); const int32_t pointerIndex = getMotionEventActionPointerIndex(action); const bool isDown = maskedAction == AMOTION_EVENT_ACTION_DOWN; const bool isStylus = isPointerFromStylus(entry, pointerIndex); - newTouchedWindowHandle = findTouchedWindowAtLocked(displayId, x, y, &tempTouchState, - isStylus, isDown /*addOutsideTargets*/); + sp<WindowInfoHandle> newTouchedWindowHandle = + findTouchedWindowAtLocked(displayId, x, y, &tempTouchState, isStylus, + isDown /*addOutsideTargets*/); // Handle the case where we did not find a window. if (newTouchedWindowHandle == nullptr) { @@ -2172,15 +2236,6 @@ std::vector<TouchedWindow> InputDispatcher::findTouchedWindowTargetsLocked( isSplit = !isFromMouse; } - // Update hover state. - if (newTouchedWindowHandle != nullptr) { - if (maskedAction == AMOTION_EVENT_ACTION_HOVER_EXIT) { - newHoverWindowHandle = nullptr; - } else if (isHoverAction) { - newHoverWindowHandle = newTouchedWindowHandle; - } - } - std::vector<sp<WindowInfoHandle>> newTouchedWindows = findTouchedSpyWindowsAtLocked(displayId, x, y, isStylus); if (newTouchedWindowHandle != nullptr) { @@ -2200,6 +2255,18 @@ std::vector<TouchedWindow> InputDispatcher::findTouchedWindowTargetsLocked( continue; } + if (isHoverAction) { + const int32_t pointerId = entry.pointerProperties[0].id; + if (maskedAction == AMOTION_EVENT_ACTION_HOVER_EXIT) { + // Pointer left. Remove it + tempTouchState.removeHoveringPointer(entry.deviceId, pointerId); + } else { + // The "windowHandle" is the target of this hovering pointer. + tempTouchState.addHoveringPointerToWindow(windowHandle, entry.deviceId, + pointerId); + } + } + // Set target flags. ftl::Flags<InputTarget::Flags> targetFlags = InputTarget::Flags::DISPATCH_AS_IS; @@ -2219,7 +2286,9 @@ std::vector<TouchedWindow> InputDispatcher::findTouchedWindowTargetsLocked( // Update the temporary touch state. BitSet32 pointerIds; - pointerIds.markBit(entry.pointerProperties[pointerIndex].id); + if (!isHoverAction) { + pointerIds.markBit(entry.pointerProperties[pointerIndex].id); + } tempTouchState.addOrUpdateWindow(windowHandle, targetFlags, pointerIds, entry.eventTime); @@ -2255,7 +2324,7 @@ std::vector<TouchedWindow> InputDispatcher::findTouchedWindowTargetsLocked( const bool isStylus = isPointerFromStylus(entry, 0 /*pointerIndex*/); sp<WindowInfoHandle> oldTouchedWindowHandle = tempTouchState.getFirstForegroundWindowHandle(); - newTouchedWindowHandle = + sp<WindowInfoHandle> newTouchedWindowHandle = findTouchedWindowAtLocked(displayId, x, y, &tempTouchState, isStylus); // Verify targeted injection. @@ -2326,36 +2395,11 @@ std::vector<TouchedWindow> InputDispatcher::findTouchedWindowTargetsLocked( } // Update dispatching for hover enter and exit. - if (newHoverWindowHandle != mLastHoverWindowHandle) { - // Let the previous window know that the hover sequence is over, unless we already did - // it when dispatching it as is to newTouchedWindowHandle. - if (mLastHoverWindowHandle != nullptr && - (maskedAction != AMOTION_EVENT_ACTION_HOVER_EXIT || - mLastHoverWindowHandle != newTouchedWindowHandle)) { - if (DEBUG_HOVER) { - ALOGD("Sending hover exit event to window %s.", - mLastHoverWindowHandle->getName().c_str()); - } - tempTouchState.addOrUpdateWindow(mLastHoverWindowHandle, - InputTarget::Flags::DISPATCH_AS_HOVER_EXIT, - BitSet32(0)); - } - - // Let the new window know that the hover sequence is starting, unless we already did it - // when dispatching it as is to newTouchedWindowHandle. - if (newHoverWindowHandle != nullptr && - (maskedAction != AMOTION_EVENT_ACTION_HOVER_ENTER || - newHoverWindowHandle != newTouchedWindowHandle)) { - if (DEBUG_HOVER) { - ALOGD("Sending hover enter event to window %s.", - newHoverWindowHandle->getName().c_str()); - } - tempTouchState.addOrUpdateWindow(newHoverWindowHandle, - InputTarget::Flags::DISPATCH_AS_HOVER_ENTER, - BitSet32(0)); - } + { + std::vector<TouchedWindow> hoveringWindows = + getHoveringWindowsLocked(oldState, tempTouchState, entry); + touchedWindows.insert(touchedWindows.end(), hoveringWindows.begin(), hoveringWindows.end()); } - // 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 @@ -2445,10 +2489,13 @@ std::vector<TouchedWindow> InputDispatcher::findTouchedWindowTargetsLocked( } } - // Success! Output targets. - touchedWindows = tempTouchState.windows; - outInjectionResult = InputEventInjectionResult::SUCCEEDED; + // Success! Output targets for everything except hovers. + if (!isHoverAction) { + touchedWindows.insert(touchedWindows.end(), tempTouchState.windows.begin(), + tempTouchState.windows.end()); + } + outInjectionResult = InputEventInjectionResult::SUCCEEDED; // Drop the outside or hover touch windows since we will not care about them // in the next iteration. tempTouchState.filterNonAsIsTouchWindows(); @@ -2469,14 +2516,16 @@ Failed: "Conflicting pointer actions: Hover received while pointer was down."); *outConflictingPointerActions = true; } - tempTouchState.reset(); if (maskedAction == AMOTION_EVENT_ACTION_HOVER_ENTER || maskedAction == AMOTION_EVENT_ACTION_HOVER_MOVE) { tempTouchState.deviceId = entry.deviceId; tempTouchState.source = entry.source; } - } else if (maskedAction == AMOTION_EVENT_ACTION_UP || - maskedAction == AMOTION_EVENT_ACTION_CANCEL) { + } 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) { @@ -2515,9 +2564,6 @@ Failed: mTouchStatesByDisplay.erase(displayId); } - // Update hover state. - mLastHoverWindowHandle = newHoverWindowHandle; - return touchedWindows; } @@ -4809,14 +4855,6 @@ void InputDispatcher::setInputWindowsLocked( updateWindowHandlesForDisplayLocked(windowInfoHandles, displayId); const std::vector<sp<WindowInfoHandle>>& windowHandles = getWindowHandlesLocked(displayId); - if (mLastHoverWindowHandle) { - const WindowInfo* lastHoverWindowInfo = mLastHoverWindowHandle->getInfo(); - if (lastHoverWindowInfo->displayId == displayId && - std::find(windowHandles.begin(), windowHandles.end(), mLastHoverWindowHandle) == - windowHandles.end()) { - mLastHoverWindowHandle = nullptr; - } - } std::optional<FocusResolver::FocusChanges> changes = mFocusResolver.setInputWindows(displayId, windowHandles); @@ -5264,7 +5302,6 @@ void InputDispatcher::resetAndDropEverythingLocked(const char* reason) { mAnrTracker.clear(); mTouchStatesByDisplay.clear(); - mLastHoverWindowHandle.clear(); mReplacedKeys.clear(); } @@ -6454,7 +6491,6 @@ void InputDispatcher::cancelCurrentTouch() { synthesizeCancelationEventsForAllConnectionsLocked(options); mTouchStatesByDisplay.clear(); - mLastHoverWindowHandle.clear(); } // Wake up poll loop since there might be work to do. mLooper->wake(); diff --git a/services/inputflinger/dispatcher/InputDispatcher.h b/services/inputflinger/dispatcher/InputDispatcher.h index 5efb39e0f2..3c7ddfa67f 100644 --- a/services/inputflinger/dispatcher/InputDispatcher.h +++ b/services/inputflinger/dispatcher/InputDispatcher.h @@ -530,9 +530,6 @@ private: // prevent unneeded wakeups. AnrTracker mAnrTracker GUARDED_BY(mLock); - // Contains the last window which received a hover event. - sp<android::gui::WindowInfoHandle> mLastHoverWindowHandle GUARDED_BY(mLock); - void cancelEventsForAnrLocked(const sp<Connection>& connection) REQUIRES(mLock); // If a focused application changes, we should stop counting down the "no focused window" time, // because we will have no way of knowing when the previous application actually added a window. diff --git a/services/inputflinger/dispatcher/TouchState.cpp b/services/inputflinger/dispatcher/TouchState.cpp index c21af9e0b5..f120fc9919 100644 --- a/services/inputflinger/dispatcher/TouchState.cpp +++ b/services/inputflinger/dispatcher/TouchState.cpp @@ -31,10 +31,30 @@ void TouchState::reset() { *this = TouchState(); } +void TouchState::removeTouchedPointer(int32_t pointerId) { + for (TouchedWindow& touchedWindow : windows) { + touchedWindow.pointerIds.clearBit(pointerId); + } +} + +void TouchState::clearHoveringPointers() { + for (TouchedWindow& touchedWindow : windows) { + touchedWindow.clearHoveringPointers(); + } +} + +void TouchState::clearWindowsWithoutPointers() { + std::erase_if(windows, [](const TouchedWindow& w) { + return w.pointerIds.isEmpty() && !w.hasHoveringPointers(); + }); +} + void TouchState::addOrUpdateWindow(const sp<WindowInfoHandle>& windowHandle, ftl::Flags<InputTarget::Flags> targetFlags, BitSet32 pointerIds, std::optional<nsecs_t> eventTime) { for (TouchedWindow& touchedWindow : windows) { + // We do not compare windows by token here because two windows that share the same token + // may have a different transform if (touchedWindow.windowHandle == windowHandle) { touchedWindow.targetFlags |= targetFlags; if (targetFlags.test(InputTarget::Flags::DISPATCH_AS_SLIPPERY_EXIT)) { @@ -59,6 +79,21 @@ void TouchState::addOrUpdateWindow(const sp<WindowInfoHandle>& windowHandle, windows.push_back(touchedWindow); } +void TouchState::addHoveringPointerToWindow(const sp<WindowInfoHandle>& windowHandle, + int32_t hoveringDeviceId, int32_t hoveringPointerId) { + for (TouchedWindow& touchedWindow : windows) { + if (touchedWindow.windowHandle == windowHandle) { + touchedWindow.addHoveringPointer(hoveringDeviceId, hoveringPointerId); + return; + } + } + + TouchedWindow touchedWindow; + touchedWindow.windowHandle = windowHandle; + touchedWindow.addHoveringPointer(hoveringDeviceId, hoveringPointerId); + windows.push_back(touchedWindow); +} + void TouchState::removeWindowByToken(const sp<IBinder>& token) { for (size_t i = 0; i < windows.size(); i++) { if (windows[i].windowHandle->getToken() == token) { @@ -145,6 +180,26 @@ bool TouchState::isDown() const { [](const TouchedWindow& window) { return !window.pointerIds.isEmpty(); }); } +std::set<sp<WindowInfoHandle>> TouchState::getWindowsWithHoveringPointer(int32_t hoveringDeviceId, + int32_t pointerId) const { + std::set<sp<WindowInfoHandle>> out; + for (const TouchedWindow& window : windows) { + if (window.hasHoveringPointer(hoveringDeviceId, pointerId)) { + out.insert(window.windowHandle); + } + } + return out; +} + +void TouchState::removeHoveringPointer(int32_t hoveringDeviceId, int32_t hoveringPointerId) { + for (TouchedWindow& window : windows) { + window.removeHoveringPointer(hoveringDeviceId, hoveringPointerId); + } + std::erase_if(windows, [](const TouchedWindow& w) { + return w.pointerIds.isEmpty() && !w.hasHoveringPointers(); + }); +} + std::string TouchState::dump() const { std::string out; out += StringPrintf("deviceId=%d, source=%s\n", deviceId, diff --git a/services/inputflinger/dispatcher/TouchState.h b/services/inputflinger/dispatcher/TouchState.h index 77c1cdf50a..b75e6ef359 100644 --- a/services/inputflinger/dispatcher/TouchState.h +++ b/services/inputflinger/dispatcher/TouchState.h @@ -16,6 +16,7 @@ #pragma once +#include <set> #include "TouchedWindow.h" namespace android { @@ -39,9 +40,16 @@ struct TouchState { TouchState& operator=(const TouchState&) = default; void reset(); + void clearWindowsWithoutPointers(); + + void removeTouchedPointer(int32_t pointerId); void addOrUpdateWindow(const sp<android::gui::WindowInfoHandle>& windowHandle, ftl::Flags<InputTarget::Flags> targetFlags, BitSet32 pointerIds, std::optional<nsecs_t> eventTime = std::nullopt); + void addHoveringPointerToWindow(const sp<android::gui::WindowInfoHandle>& windowHandle, + int32_t deviceId, int32_t hoveringPointerId); + void removeHoveringPointer(int32_t deviceId, int32_t hoveringPointerId); + void clearHoveringPointers(); void removeWindowByToken(const sp<IBinder>& token); void filterNonAsIsTouchWindows(); @@ -56,6 +64,9 @@ struct TouchState { sp<android::gui::WindowInfoHandle> getWallpaperWindow() const; // Whether any of the windows are currently being touched bool isDown() const; + + std::set<sp<android::gui::WindowInfoHandle>> getWindowsWithHoveringPointer( + int32_t deviceId, int32_t pointerId) const; std::string dump() const; }; diff --git a/services/inputflinger/dispatcher/TouchedWindow.cpp b/services/inputflinger/dispatcher/TouchedWindow.cpp index af745988ad..3704edd575 100644 --- a/services/inputflinger/dispatcher/TouchedWindow.cpp +++ b/services/inputflinger/dispatcher/TouchedWindow.cpp @@ -25,11 +25,49 @@ namespace android { namespace inputdispatcher { +bool TouchedWindow::hasHoveringPointers() const { + return !mHoveringPointerIdsByDevice.empty(); +} + +void TouchedWindow::clearHoveringPointers() { + mHoveringPointerIdsByDevice.clear(); +} + +bool TouchedWindow::hasHoveringPointer(int32_t deviceId, int32_t pointerId) const { + auto it = mHoveringPointerIdsByDevice.find(deviceId); + if (it == mHoveringPointerIdsByDevice.end()) { + return false; + } + return it->second.test(pointerId); +} + +void TouchedWindow::addHoveringPointer(int32_t deviceId, int32_t pointerId) { + const auto [it, _] = mHoveringPointerIdsByDevice.insert({deviceId, {}}); + it->second.set(pointerId); +} + +void TouchedWindow::removeHoveringPointer(int32_t deviceId, int32_t pointerId) { + const auto it = mHoveringPointerIdsByDevice.find(deviceId); + if (it == mHoveringPointerIdsByDevice.end()) { + return; + } + it->second.set(pointerId, false); + + if (it->second.none()) { + mHoveringPointerIdsByDevice.erase(deviceId); + } +} + std::string TouchedWindow::dump() const { - return StringPrintf("name='%s', pointerIds=0x%0x, " - "targetFlags=%s, firstDownTimeInTarget=%s\n", + std::string out; + std::string hoveringPointers = + dumpMap(mHoveringPointerIdsByDevice, constToString, bitsetToString); + out += StringPrintf("name='%s', pointerIds=0x%0x, targetFlags=%s, firstDownTimeInTarget=%s, " + "mHoveringPointerIdsByDevice=%s\n", windowHandle->getName().c_str(), pointerIds.value, - targetFlags.string().c_str(), toString(firstDownTimeInTarget).c_str()); + targetFlags.string().c_str(), toString(firstDownTimeInTarget).c_str(), + hoveringPointers.c_str()); + return out; } } // namespace inputdispatcher diff --git a/services/inputflinger/dispatcher/TouchedWindow.h b/services/inputflinger/dispatcher/TouchedWindow.h index dd08323dd4..add6b6120e 100644 --- a/services/inputflinger/dispatcher/TouchedWindow.h +++ b/services/inputflinger/dispatcher/TouchedWindow.h @@ -17,7 +17,9 @@ #pragma once #include <gui/WindowInfo.h> +#include <input/Input.h> #include <utils/BitSet.h> +#include <bitset> #include "InputTarget.h" namespace android { @@ -33,7 +35,17 @@ struct TouchedWindow { // Time at which the first action down occurred on this window. // NOTE: This is not initialized in case of HOVER entry/exit and DISPATCH_AS_OUTSIDE scenario. std::optional<nsecs_t> firstDownTimeInTarget; + + bool hasHoveringPointers() const; + + bool hasHoveringPointer(int32_t deviceId, int32_t pointerId) const; + void addHoveringPointer(int32_t deviceId, int32_t pointerId); + void removeHoveringPointer(int32_t deviceId, int32_t pointerId); + void clearHoveringPointers(); std::string dump() const; + +private: + std::map<int32_t /*deviceId*/, std::bitset<MAX_POINTERS>> mHoveringPointerIdsByDevice; }; } // namespace inputdispatcher diff --git a/services/inputflinger/tests/InputDispatcher_test.cpp b/services/inputflinger/tests/InputDispatcher_test.cpp index 41c174a1f7..9881cd4c7c 100644 --- a/services/inputflinger/tests/InputDispatcher_test.cpp +++ b/services/inputflinger/tests/InputDispatcher_test.cpp @@ -2135,7 +2135,6 @@ TEST_F(InputDispatcherTest, HoverMoveEnterMouseClickAndHoverMoveExit) { .y(400)) .build())); windowRight->consumeMotionEvent(WithMotionAction(AMOTION_EVENT_ACTION_HOVER_ENTER)); - windowRight->consumeMotionEvent(WithMotionAction(AMOTION_EVENT_ACTION_HOVER_MOVE)); // Move cursor into left window ASSERT_EQ(InputEventInjectionResult::SUCCEEDED, @@ -2148,7 +2147,6 @@ TEST_F(InputDispatcherTest, HoverMoveEnterMouseClickAndHoverMoveExit) { .build())); windowRight->consumeMotionEvent(WithMotionAction(AMOTION_EVENT_ACTION_HOVER_EXIT)); windowLeft->consumeMotionEvent(WithMotionAction(AMOTION_EVENT_ACTION_HOVER_ENTER)); - windowLeft->consumeMotionEvent(WithMotionAction(AMOTION_EVENT_ACTION_HOVER_MOVE)); // Inject a series of mouse events for a mouse click ASSERT_EQ(InputEventInjectionResult::SUCCEEDED, @@ -2206,7 +2204,6 @@ TEST_F(InputDispatcherTest, HoverMoveEnterMouseClickAndHoverMoveExit) { .build())); windowLeft->consumeMotionEvent(WithMotionAction(AMOTION_EVENT_ACTION_HOVER_EXIT)); windowRight->consumeMotionEvent(WithMotionAction(AMOTION_EVENT_ACTION_HOVER_ENTER)); - windowRight->consumeMotionEvent(WithMotionAction(AMOTION_EVENT_ACTION_HOVER_MOVE)); // No more events windowLeft->assertNoEvents(); @@ -2268,7 +2265,6 @@ TEST_F(InputDispatcherTest, HoverEnterMouseClickAndHoverExit) { .y(400)) .build())); window->consumeMotionEvent(WithMotionAction(AMOTION_EVENT_ACTION_HOVER_ENTER)); - // Inject a series of mouse events for a mouse click ASSERT_EQ(InputEventInjectionResult::SUCCEEDED, injectMotionEvent(mDispatcher, @@ -2326,8 +2322,38 @@ TEST_F(InputDispatcherTest, HoverEnterMouseClickAndHoverExit) { } /** + * Hover over a window, and then remove that window. Make sure that HOVER_EXIT for that event + * is generated. + */ +TEST_F(InputDispatcherTest, HoverExitIsSentToRemovedWindow) { + 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, 1200, 800)); + + mDispatcher->setFocusedApplication(ADISPLAY_ID_DEFAULT, application); + + mDispatcher->setInputWindows({{ADISPLAY_ID_DEFAULT, {window}}}); + + ASSERT_EQ(InputEventInjectionResult::SUCCEEDED, + injectMotionEvent(mDispatcher, + MotionEventBuilder(AMOTION_EVENT_ACTION_HOVER_ENTER, + AINPUT_SOURCE_MOUSE) + .pointer(PointerBuilder(0, AMOTION_EVENT_TOOL_TYPE_MOUSE) + .x(300) + .y(400)) + .build())); + window->consumeMotionEvent(WithMotionAction(AMOTION_EVENT_ACTION_HOVER_ENTER)); + + // Remove the window, but keep the channel. + mDispatcher->setInputWindows({{ADISPLAY_ID_DEFAULT, {}}}); + window->consumeMotionEvent(WithMotionAction(AMOTION_EVENT_ACTION_HOVER_EXIT)); +} + +/** * Inject a mouse hover event followed by a tap from touchscreen. - * In the current implementation, the tap does not cause a HOVER_EXIT event. + * The tap causes a HOVER_EXIT event to be generated because the current event + * stream's source has been switched. */ TEST_F(InputDispatcherTest, MouseHoverAndTouchTap) { std::shared_ptr<FakeApplicationHandle> application = std::make_shared<FakeApplicationHandle>(); @@ -2347,15 +2373,16 @@ TEST_F(InputDispatcherTest, MouseHoverAndTouchTap) { ASSERT_NO_FATAL_FAILURE( window->consumeMotionEvent(AllOf(WithMotionAction(AMOTION_EVENT_ACTION_HOVER_ENTER), WithSource(AINPUT_SOURCE_MOUSE)))); - ASSERT_NO_FATAL_FAILURE( - window->consumeMotionEvent(AllOf(WithMotionAction(AMOTION_EVENT_ACTION_HOVER_MOVE), - WithSource(AINPUT_SOURCE_MOUSE)))); // Tap on the window motionArgs = generateMotionArgs(AMOTION_EVENT_ACTION_DOWN, AINPUT_SOURCE_TOUCHSCREEN, ADISPLAY_ID_DEFAULT, {{10, 10}}); mDispatcher->notifyMotion(&motionArgs); ASSERT_NO_FATAL_FAILURE( + window->consumeMotionEvent(AllOf(WithMotionAction(AMOTION_EVENT_ACTION_HOVER_EXIT), + WithSource(AINPUT_SOURCE_MOUSE)))); + + ASSERT_NO_FATAL_FAILURE( window->consumeMotionEvent(AllOf(WithMotionAction(AMOTION_EVENT_ACTION_DOWN), WithSource(AINPUT_SOURCE_TOUCHSCREEN)))); @@ -2393,7 +2420,6 @@ TEST_F(InputDispatcherTest, HoverEnterMoveRemoveWindowsInSecondDisplay) { .y(600)) .build())); windowDefaultDisplay->consumeMotionEvent(WithMotionAction(AMOTION_EVENT_ACTION_HOVER_ENTER)); - windowDefaultDisplay->consumeMotionEvent(WithMotionAction(AMOTION_EVENT_ACTION_HOVER_MOVE)); // Remove all windows in secondary display and check that no event happens on window in // primary display. |