Skip to content

Commit d66c39a

Browse files
Treehugger RobotGerrit Code Review
authored andcommitted
Merge "Generate HOVER_EXIT if touchable region changes" into main
2 parents 0c01454 + a8aaeb8 commit d66c39a

8 files changed

Lines changed: 185 additions & 19 deletions

File tree

services/inputflinger/dispatcher/CancelationOptions.h

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,8 @@ struct CancelationOptions {
3232
CANCEL_POINTER_EVENTS = 1,
3333
CANCEL_NON_POINTER_EVENTS = 2,
3434
CANCEL_FALLBACK_EVENTS = 3,
35-
ftl_last = CANCEL_FALLBACK_EVENTS,
35+
CANCEL_HOVER_EVENTS = 4,
36+
ftl_last = CANCEL_HOVER_EVENTS
3637
};
3738

3839
// The criterion to use to determine which events should be canceled.

services/inputflinger/dispatcher/InputDispatcher.cpp

Lines changed: 31 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -746,7 +746,8 @@ std::vector<TouchedWindow> getHoveringWindowsLocked(const TouchState* oldState,
746746
}
747747
touchedWindow.dispatchMode = InputTarget::DispatchMode::AS_IS;
748748
}
749-
touchedWindow.addHoveringPointer(entry.deviceId, pointer);
749+
const auto [x, y] = resolveTouchedPosition(entry);
750+
touchedWindow.addHoveringPointer(entry.deviceId, pointer, x, y);
750751
if (canReceiveForegroundTouches(*newWindow->getInfo())) {
751752
touchedWindow.targetFlags |= InputTarget::Flags::FOREGROUND;
752753
}
@@ -873,6 +874,8 @@ std::pair<bool /*cancelPointers*/, bool /*cancelNonPointers*/> expandCancellatio
873874
return {false, true};
874875
case CancelationOptions::Mode::CANCEL_FALLBACK_EVENTS:
875876
return {false, true};
877+
case CancelationOptions::Mode::CANCEL_HOVER_EVENTS:
878+
return {true, false};
876879
}
877880
}
878881

@@ -2511,7 +2514,8 @@ std::vector<InputTarget> InputDispatcher::findTouchedWindowTargetsLocked(
25112514

25122515
if (isHoverAction) {
25132516
// The "windowHandle" is the target of this hovering pointer.
2514-
tempTouchState.addHoveringPointerToWindow(windowHandle, entry.deviceId, pointer);
2517+
tempTouchState.addHoveringPointerToWindow(windowHandle, entry.deviceId, pointer, x,
2518+
y);
25152519
}
25162520

25172521
// Set target flags.
@@ -5437,6 +5441,31 @@ void InputDispatcher::setInputWindowsLocked(
54375441
}
54385442
}
54395443

5444+
// Check if the hovering should stop because the window is no longer eligible to receive it
5445+
// (for example, if the touchable region changed)
5446+
if (const auto& it = mTouchStatesByDisplay.find(displayId); it != mTouchStatesByDisplay.end()) {
5447+
TouchState& state = it->second;
5448+
for (TouchedWindow& touchedWindow : state.windows) {
5449+
std::vector<DeviceId> erasedDevices = touchedWindow.eraseHoveringPointersIf(
5450+
[this, displayId, &touchedWindow](const PointerProperties& properties, float x,
5451+
float y) REQUIRES(mLock) {
5452+
const bool isStylus = properties.toolType == ToolType::STYLUS;
5453+
const ui::Transform displayTransform = getTransformLocked(displayId);
5454+
const bool stillAcceptsTouch =
5455+
windowAcceptsTouchAt(*touchedWindow.windowHandle->getInfo(),
5456+
displayId, x, y, isStylus, displayTransform);
5457+
return !stillAcceptsTouch;
5458+
});
5459+
5460+
for (DeviceId deviceId : erasedDevices) {
5461+
CancelationOptions options(CancelationOptions::Mode::CANCEL_HOVER_EVENTS,
5462+
"WindowInfo changed", traceContext.getTracker());
5463+
options.deviceId = deviceId;
5464+
synthesizeCancelationEventsForWindowLocked(touchedWindow.windowHandle, options);
5465+
}
5466+
}
5467+
}
5468+
54405469
// Release information for windows that are no longer present.
54415470
// This ensures that unused input channels are released promptly.
54425471
// Otherwise, they might stick around until the window handle is destroyed

services/inputflinger/dispatcher/InputState.cpp

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -638,6 +638,8 @@ bool InputState::shouldCancelMotion(const MotionMemento& memento,
638638
return memento.source & AINPUT_SOURCE_CLASS_POINTER;
639639
case CancelationOptions::Mode::CANCEL_NON_POINTER_EVENTS:
640640
return !(memento.source & AINPUT_SOURCE_CLASS_POINTER);
641+
case CancelationOptions::Mode::CANCEL_HOVER_EVENTS:
642+
return memento.hovering;
641643
default:
642644
return false;
643645
}

services/inputflinger/dispatcher/TouchState.cpp

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -112,17 +112,18 @@ android::base::Result<void> TouchState::addOrUpdateWindow(
112112
}
113113

114114
void TouchState::addHoveringPointerToWindow(const sp<WindowInfoHandle>& windowHandle,
115-
DeviceId deviceId, const PointerProperties& pointer) {
115+
DeviceId deviceId, const PointerProperties& pointer,
116+
float x, float y) {
116117
for (TouchedWindow& touchedWindow : windows) {
117118
if (touchedWindow.windowHandle == windowHandle) {
118-
touchedWindow.addHoveringPointer(deviceId, pointer);
119+
touchedWindow.addHoveringPointer(deviceId, pointer, x, y);
119120
return;
120121
}
121122
}
122123

123124
TouchedWindow touchedWindow;
124125
touchedWindow.windowHandle = windowHandle;
125-
touchedWindow.addHoveringPointer(deviceId, pointer);
126+
touchedWindow.addHoveringPointer(deviceId, pointer, x, y);
126127
windows.push_back(touchedWindow);
127128
}
128129

services/inputflinger/dispatcher/TouchState.h

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -49,7 +49,8 @@ struct TouchState {
4949
DeviceId deviceId, const std::vector<PointerProperties>& touchingPointers,
5050
std::optional<nsecs_t> firstDownTimeInTarget = std::nullopt);
5151
void addHoveringPointerToWindow(const sp<android::gui::WindowInfoHandle>& windowHandle,
52-
DeviceId deviceId, const PointerProperties& pointer);
52+
DeviceId deviceId, const PointerProperties& pointer, float x,
53+
float y);
5354
void removeHoveringPointer(DeviceId deviceId, int32_t pointerId);
5455
void clearHoveringPointers(DeviceId deviceId);
5556

services/inputflinger/dispatcher/TouchedWindow.cpp

Lines changed: 40 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,13 @@ bool hasPointerId(const std::vector<PointerProperties>& pointers, int32_t pointe
3636
}) != pointers.end();
3737
}
3838

39+
bool hasPointerId(const std::vector<TouchedWindow::HoveringPointer>& pointers, int32_t pointerId) {
40+
return std::find_if(pointers.begin(), pointers.end(),
41+
[&pointerId](const TouchedWindow::HoveringPointer& pointer) {
42+
return pointer.properties.id == pointerId;
43+
}) != pointers.end();
44+
}
45+
3946
} // namespace
4047

4148
bool TouchedWindow::hasHoveringPointers() const {
@@ -78,16 +85,18 @@ bool TouchedWindow::hasHoveringPointer(DeviceId deviceId, int32_t pointerId) con
7885
return hasPointerId(state.hoveringPointers, pointerId);
7986
}
8087

81-
void TouchedWindow::addHoveringPointer(DeviceId deviceId, const PointerProperties& pointer) {
82-
std::vector<PointerProperties>& hoveringPointers = mDeviceStates[deviceId].hoveringPointers;
88+
void TouchedWindow::addHoveringPointer(DeviceId deviceId, const PointerProperties& properties,
89+
float x, float y) {
90+
std::vector<HoveringPointer>& hoveringPointers = mDeviceStates[deviceId].hoveringPointers;
8391
const size_t initialSize = hoveringPointers.size();
84-
std::erase_if(hoveringPointers, [&pointer](const PointerProperties& properties) {
85-
return properties.id == pointer.id;
92+
std::erase_if(hoveringPointers, [&properties](const HoveringPointer& pointer) {
93+
return pointer.properties.id == properties.id;
8694
});
8795
if (hoveringPointers.size() != initialSize) {
88-
LOG(ERROR) << __func__ << ": " << pointer << ", device " << deviceId << " was in " << *this;
96+
LOG(ERROR) << __func__ << ": " << properties << ", device " << deviceId << " was in "
97+
<< *this;
8998
}
90-
hoveringPointers.push_back(pointer);
99+
hoveringPointers.push_back({properties, x, y});
91100
}
92101

93102
Result<void> TouchedWindow::addTouchingPointers(DeviceId deviceId,
@@ -173,8 +182,8 @@ bool TouchedWindow::hasActiveStylus() const {
173182
return true;
174183
}
175184
}
176-
for (const PointerProperties& properties : state.hoveringPointers) {
177-
if (properties.toolType == ToolType::STYLUS) {
185+
for (const HoveringPointer& pointer : state.hoveringPointers) {
186+
if (pointer.properties.toolType == ToolType::STYLUS) {
178187
return true;
179188
}
180189
}
@@ -270,15 +279,31 @@ void TouchedWindow::removeHoveringPointer(DeviceId deviceId, int32_t pointerId)
270279
}
271280
DeviceState& state = stateIt->second;
272281

273-
std::erase_if(state.hoveringPointers, [&pointerId](const PointerProperties& properties) {
274-
return properties.id == pointerId;
282+
std::erase_if(state.hoveringPointers, [&pointerId](const HoveringPointer& pointer) {
283+
return pointer.properties.id == pointerId;
275284
});
276285

277286
if (!state.hasPointers()) {
278287
mDeviceStates.erase(stateIt);
279288
}
280289
}
281290

291+
std::vector<DeviceId> TouchedWindow::eraseHoveringPointersIf(
292+
std::function<bool(const PointerProperties&, float /*x*/, float /*y*/)> condition) {
293+
std::vector<DeviceId> erasedDevices;
294+
for (auto& [deviceId, state] : mDeviceStates) {
295+
std::erase_if(state.hoveringPointers, [&](const HoveringPointer& pointer) {
296+
if (condition(pointer.properties, pointer.x, pointer.y)) {
297+
erasedDevices.push_back(deviceId);
298+
return true;
299+
}
300+
return false;
301+
});
302+
}
303+
304+
return erasedDevices;
305+
}
306+
282307
void TouchedWindow::removeAllHoveringPointersForDevice(DeviceId deviceId) {
283308
const auto stateIt = mDeviceStates.find(deviceId);
284309
if (stateIt == mDeviceStates.end()) {
@@ -312,6 +337,11 @@ std::string TouchedWindow::dump() const {
312337
return out;
313338
}
314339

340+
std::ostream& operator<<(std::ostream& out, const TouchedWindow::HoveringPointer& pointer) {
341+
out << pointer.properties << " at (" << pointer.x << ", " << pointer.y << ")";
342+
return out;
343+
}
344+
315345
std::ostream& operator<<(std::ostream& out, const TouchedWindow& window) {
316346
out << window.dump();
317347
return out;

services/inputflinger/dispatcher/TouchedWindow.h

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,7 @@ struct TouchedWindow {
3838
bool hasHoveringPointers() const;
3939
bool hasHoveringPointers(DeviceId deviceId) const;
4040
bool hasHoveringPointer(DeviceId deviceId, int32_t pointerId) const;
41-
void addHoveringPointer(DeviceId deviceId, const PointerProperties& pointer);
41+
void addHoveringPointer(DeviceId deviceId, const PointerProperties& pointer, float x, float y);
4242
void removeHoveringPointer(DeviceId deviceId, int32_t pointerId);
4343

4444
// Touching
@@ -69,6 +69,15 @@ struct TouchedWindow {
6969
void clearHoveringPointers(DeviceId deviceId);
7070
std::string dump() const;
7171

72+
struct HoveringPointer {
73+
PointerProperties properties;
74+
float x;
75+
float y;
76+
};
77+
78+
std::vector<DeviceId> eraseHoveringPointersIf(
79+
std::function<bool(const PointerProperties&, float /*x*/, float /*y*/)> condition);
80+
7281
private:
7382
struct DeviceState {
7483
std::vector<PointerProperties> touchingPointers;
@@ -78,7 +87,7 @@ struct TouchedWindow {
7887
// NOTE: This is not initialized in case of HOVER entry/exit and DISPATCH_AS_OUTSIDE
7988
// scenario.
8089
std::optional<nsecs_t> downTimeInTarget;
81-
std::vector<PointerProperties> hoveringPointers;
90+
std::vector<HoveringPointer> hoveringPointers;
8291

8392
bool hasPointers() const { return !touchingPointers.empty() || !hoveringPointers.empty(); };
8493
};

services/inputflinger/tests/InputDispatcher_test.cpp

Lines changed: 93 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1923,6 +1923,99 @@ TEST_F(InputDispatcherTest, HoverMoveAndScroll) {
19231923
window->consumeMotionEvent(WithMotionAction(ACTION_SCROLL));
19241924
}
19251925

1926+
/**
1927+
* Two windows: a trusted overlay and a regular window underneath. Both windows are visible.
1928+
* Mouse is hovered, and the hover event should only go to the overlay.
1929+
* However, next, the touchable region of the trusted overlay shrinks. The mouse position hasn't
1930+
* changed, but the cursor would now end up hovering above the regular window underneatch.
1931+
* If the mouse is now clicked, this would generate an ACTION_DOWN event, which would go to the
1932+
* regular window. However, the trusted overlay is also watching for outside touch.
1933+
* The trusted overlay should get two events:
1934+
* 1) The ACTION_OUTSIDE event, since the click is now not inside its touchable region
1935+
* 2) The HOVER_EXIT event, since the mouse pointer is no longer hovering inside this window
1936+
*
1937+
* This test reproduces a crash where there is an overlap between dispatch modes for the trusted
1938+
* overlay touch target, since the event is causing both an ACTION_OUTSIDE, and as a HOVER_EXIT.
1939+
*/
1940+
TEST_F(InputDispatcherTest, MouseClickUnderShrinkingTrustedOverlay) {
1941+
std::shared_ptr<FakeApplicationHandle> app = std::make_shared<FakeApplicationHandle>();
1942+
sp<FakeWindowHandle> overlay = sp<FakeWindowHandle>::make(app, mDispatcher, "Trusted overlay",
1943+
ui::LogicalDisplayId::DEFAULT);
1944+
overlay->setTrustedOverlay(true);
1945+
overlay->setWatchOutsideTouch(true);
1946+
overlay->setFrame(Rect(0, 0, 200, 200));
1947+
1948+
sp<FakeWindowHandle> window = sp<FakeWindowHandle>::make(app, mDispatcher, "Regular window",
1949+
ui::LogicalDisplayId::DEFAULT);
1950+
window->setFrame(Rect(0, 0, 200, 200));
1951+
1952+
mDispatcher->onWindowInfosChanged({{*overlay->getInfo(), *window->getInfo()}, {}, 0, 0});
1953+
// Hover the mouse into the overlay
1954+
mDispatcher->notifyMotion(MotionArgsBuilder(ACTION_HOVER_MOVE, AINPUT_SOURCE_MOUSE)
1955+
.pointer(PointerBuilder(0, ToolType::MOUSE).x(100).y(110))
1956+
.build());
1957+
overlay->consumeMotionEvent(WithMotionAction(ACTION_HOVER_ENTER));
1958+
1959+
// Now, shrink the touchable region of the overlay! This will cause the cursor to suddenly have
1960+
// the regular window as the touch target
1961+
overlay->setTouchableRegion(Region({0, 0, 0, 0}));
1962+
mDispatcher->onWindowInfosChanged({{*overlay->getInfo(), *window->getInfo()}, {}, 0, 0});
1963+
1964+
// Now we can click with the mouse. The click should go into the regular window
1965+
mDispatcher->notifyMotion(MotionArgsBuilder(ACTION_DOWN, AINPUT_SOURCE_MOUSE)
1966+
.pointer(PointerBuilder(0, ToolType::MOUSE).x(100).y(110))
1967+
.build());
1968+
overlay->consumeMotionEvent(WithMotionAction(ACTION_HOVER_EXIT));
1969+
overlay->consumeMotionEvent(WithMotionAction(ACTION_OUTSIDE));
1970+
window->consumeMotionEvent(WithMotionAction(ACTION_DOWN));
1971+
}
1972+
1973+
/**
1974+
* Similar to above, but also has a spy on top that also catches the HOVER
1975+
* events. Also, instead of ACTION_DOWN, we are continuing to send the hovering
1976+
* stream to ensure that the spy receives hover events correctly.
1977+
*/
1978+
TEST_F(InputDispatcherTest, MouseClickUnderShrinkingTrustedOverlayWithSpy) {
1979+
std::shared_ptr<FakeApplicationHandle> app = std::make_shared<FakeApplicationHandle>();
1980+
sp<FakeWindowHandle> spyWindow =
1981+
sp<FakeWindowHandle>::make(app, mDispatcher, "Spy", ui::LogicalDisplayId::DEFAULT);
1982+
spyWindow->setFrame(Rect(0, 0, 200, 200));
1983+
spyWindow->setTrustedOverlay(true);
1984+
spyWindow->setSpy(true);
1985+
sp<FakeWindowHandle> overlay = sp<FakeWindowHandle>::make(app, mDispatcher, "Trusted overlay",
1986+
ui::LogicalDisplayId::DEFAULT);
1987+
overlay->setTrustedOverlay(true);
1988+
overlay->setWatchOutsideTouch(true);
1989+
overlay->setFrame(Rect(0, 0, 200, 200));
1990+
1991+
sp<FakeWindowHandle> window = sp<FakeWindowHandle>::make(app, mDispatcher, "Regular window",
1992+
ui::LogicalDisplayId::DEFAULT);
1993+
window->setFrame(Rect(0, 0, 200, 200));
1994+
1995+
mDispatcher->onWindowInfosChanged(
1996+
{{*spyWindow->getInfo(), *overlay->getInfo(), *window->getInfo()}, {}, 0, 0});
1997+
// Hover the mouse into the overlay
1998+
mDispatcher->notifyMotion(MotionArgsBuilder(ACTION_HOVER_MOVE, AINPUT_SOURCE_MOUSE)
1999+
.pointer(PointerBuilder(0, ToolType::MOUSE).x(100).y(110))
2000+
.build());
2001+
spyWindow->consumeMotionEvent(WithMotionAction(ACTION_HOVER_ENTER));
2002+
overlay->consumeMotionEvent(WithMotionAction(ACTION_HOVER_ENTER));
2003+
2004+
// Now, shrink the touchable region of the overlay! This will cause the cursor to suddenly have
2005+
// the regular window as the touch target
2006+
overlay->setTouchableRegion(Region({0, 0, 0, 0}));
2007+
mDispatcher->onWindowInfosChanged(
2008+
{{*spyWindow->getInfo(), *overlay->getInfo(), *window->getInfo()}, {}, 0, 0});
2009+
2010+
// Now we can click with the mouse. The click should go into the regular window
2011+
mDispatcher->notifyMotion(MotionArgsBuilder(ACTION_HOVER_MOVE, AINPUT_SOURCE_MOUSE)
2012+
.pointer(PointerBuilder(0, ToolType::MOUSE).x(110).y(110))
2013+
.build());
2014+
spyWindow->consumeMotionEvent(WithMotionAction(ACTION_HOVER_MOVE));
2015+
overlay->consumeMotionEvent(WithMotionAction(ACTION_HOVER_EXIT));
2016+
window->consumeMotionEvent(WithMotionAction(ACTION_HOVER_ENTER));
2017+
}
2018+
19262019
using InputDispatcherMultiDeviceTest = InputDispatcherTest;
19272020

19282021
/**

0 commit comments

Comments
 (0)