Track hovering pointers explicitly -- try 2
Update:
Compared to the first version of this CL that got reverted, this version
also adds Flags::FOREGROUND to the windows whenever appropriate.
Before this CL, hovering window was tracked separately inside
InputDispatcher. This window was getting updated in various places.
Inconsistent motion streams, like HOVER_ENTER->DOWN->UP->HOVER_EXIT were
possible.
In this CL, we track hovering pointers inside TouchedWindow. At all
times, the currently tracked pointer must always be in the touch state.
The hovering pointer is removed when HOVER_EXIT is received.
This CL also establishes the foundation for multi-device, multi-pointer
streams, by storing hovering pointers inside TouchedWindow per-device.
Eventually, we can look into separately creating touched targets from
updating the touch state. This approach is partially used in this CL.
TouchState is used to keep track of where the hovering pointer is
currently. The 'addHoveringWindowsLocked' function returns the
equivalent of InputTargets. Eventually, we can change this to return
InputTargets.
39d37cfb471c69f924e90f8e35afcff4ad4e6042
Bug: 211379801
Test: atest android.accessibilityservice.cts.AccessibilityEndToEndTest
Test: atest VirtualMouseTest
Test: m inputflinger_tests && adb sync data && adb shell -t /data/nativetest64/inputflinger_tests/inputflinger_tests
Change-Id: I0aa77bc9f680786b154312c4c936da2cf6efffa1
diff --git a/services/inputflinger/dispatcher/InputDispatcher.cpp b/services/inputflinger/dispatcher/InputDispatcher.cpp
index 906bb1b..37a451b 100644
--- a/services/inputflinger/dispatcher/InputDispatcher.cpp
+++ b/services/inputflinger/dispatcher/InputDispatcher.cpp
@@ -554,6 +554,68 @@
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);
+ if (canReceiveForegroundTouches(*newWindow->getInfo())) {
+ touchedWindow.targetFlags |= InputTarget::Flags::FOREGROUND;
+ }
+ out.push_back(touchedWindow);
+ }
+ return out;
+}
+
} // namespace
// --- InputDispatcher ---
@@ -2089,8 +2151,6 @@
// 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.
@@ -2123,7 +2183,7 @@
outInjectionResult = InputEventInjectionResult::FAILED;
return touchedWindows; // wrong device
}
- tempTouchState.reset();
+ tempTouchState.clearWindowsWithoutPointers();
tempTouchState.deviceId = entry.deviceId;
tempTouchState.source = entry.source;
isSplit = false;
@@ -2136,14 +2196,21 @@
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) {
@@ -2178,15 +2245,6 @@
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) {
@@ -2206,6 +2264,18 @@
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;
@@ -2225,7 +2295,9 @@
// 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);
@@ -2287,7 +2359,7 @@
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.
@@ -2362,36 +2434,11 @@
}
// 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
@@ -2449,10 +2496,13 @@
}
}
- // 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();
@@ -2473,14 +2523,16 @@
"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) {
@@ -2519,9 +2571,6 @@
mTouchStatesByDisplay.erase(displayId);
}
- // Update hover state.
- mLastHoverWindowHandle = newHoverWindowHandle;
-
return touchedWindows;
}
@@ -4824,14 +4873,6 @@
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);
@@ -5278,7 +5319,6 @@
mAnrTracker.clear();
mTouchStatesByDisplay.clear();
- mLastHoverWindowHandle.clear();
mReplacedKeys.clear();
}
@@ -6468,7 +6508,6 @@
synthesizeCancelationEventsForAllConnectionsLocked(options);
mTouchStatesByDisplay.clear();
- mLastHoverWindowHandle.clear();
}
// Wake up poll loop since there might be work to do.
mLooper->wake();