Skip to content

Commit a8aaeb8

Browse files
committed
Generate HOVER_EXIT if touchable region changes
The dispatcher crashes whenever we try adding pointers to a target, but the target already had those pointers under a different mode. Only one dispatch mode is allowed for each event, per target. That's generally true, but there were instances where we ended up with multiple dispatch modes for the same event. For example, if a window changes bounds (for example, becomes hidden, or the touchable region shrinks), the pointer would no longer be inside that window. And if this window is also watching for outside touch, then an ACTION_DOWN event of the mouse would cause 2 things to happen: 1) This would cause HOVER_EXIT, since the pointer is no longer in that window 2) This would case ACTION_OUTSIDE, since the pointer is tapped outside of the window's bounds. This previously led to a crash in dispatcher because there would be two different dispatch modes for the same window for the same ACTION_DOWN event. Update being done here: When a window resizes, it's possible that the hovering pointer is no longer inside that window's bounds. We should generate a HOVER_EXIT event at that point; this will ensure that the next mouse event is still maintaining the contract that it's only dispatched with a single mode. Bug: 346121425 Test: TEST=inputflinger_tests; m $TEST && $ANDROID_HOST_OUT/nativetest64/$TEST/$TEST --gtest_filter="*MouseClickUnderShrinkingTrustedOverlay" Flag: EXEMPT bugfix Merged-In: Ia5e5ac0421b127231d36ed375538eaebfb8994ac Change-Id: Ia5e5ac0421b127231d36ed375538eaebfb8994ac
1 parent 0c01454 commit a8aaeb8

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)