[3/n CD Cursor] Enable cross-display mouse gestures
Enable mouse based gestures to continue across connected displays.
This Relands d6e83206dd69409c070c37fc7fb993bca0524c43
Bug: 367661487
Test: atest inputflinger_tests
Test: adb shell setprop persist.device_config.aconfig_flags.\
lse_desktop_experience.com.android.input.flags.\
connected_displays_cursor true && atest inputflinger_tests
Flag: com.android.input.flags.connected_displays_cursor
Change-Id: If674593f1cbe9169886674d4f9423d7f484771d8
diff --git a/services/inputflinger/dispatcher/InputDispatcher.cpp b/services/inputflinger/dispatcher/InputDispatcher.cpp
index bc2904e..f0a627c 100644
--- a/services/inputflinger/dispatcher/InputDispatcher.cpp
+++ b/services/inputflinger/dispatcher/InputDispatcher.cpp
@@ -165,8 +165,6 @@
constexpr int LOGTAG_INPUT_FOCUS = 62001;
constexpr int LOGTAG_INPUT_CANCEL = 62003;
-static const bool USE_TOPOLOGY = com::android::input::flags::connected_displays_cursor();
-
const ui::Transform kIdentityTransform;
inline nsecs_t now() {
@@ -2398,7 +2396,7 @@
// Copy current touch state into tempTouchState.
// This state will be used to update saved touch state at the end of this function.
// If no state for the specified display exists, then our initial state will be empty.
- const TouchState* oldState = getTouchStateForMotionEntry(entry);
+ const TouchState* oldState = getTouchStateForMotionEntry(entry, windowInfos);
TouchState tempTouchState;
if (oldState != nullptr) {
tempTouchState = *oldState;
@@ -2787,9 +2785,9 @@
if (maskedAction != AMOTION_EVENT_ACTION_SCROLL) {
if (displayId >= ui::LogicalDisplayId::DEFAULT) {
tempTouchState.clearWindowsWithoutPointers();
- saveTouchStateForMotionEntry(entry, std::move(tempTouchState));
+ saveTouchStateForMotionEntry(entry, std::move(tempTouchState), windowInfos);
} else {
- eraseTouchStateForMotionEntry(entry);
+ eraseTouchStateForMotionEntry(entry, windowInfos);
}
}
@@ -5182,6 +5180,14 @@
: getDisplayTransform(windowInfo.displayId);
}
+ui::LogicalDisplayId InputDispatcher::DispatcherWindowInfo::getPrimaryDisplayId(
+ ui::LogicalDisplayId displayId) const {
+ if (mTopology.graph.contains(displayId)) {
+ return mTopology.primaryDisplayId;
+ }
+ return displayId;
+}
+
std::string InputDispatcher::DispatcherWindowInfo::dumpDisplayAndWindowInfo() const {
std::string dump;
if (!mWindowHandlesByDisplay.empty()) {
@@ -7475,32 +7481,40 @@
void InputDispatcher::DispatcherTouchState::saveTouchStateForMotionEntry(
const android::inputdispatcher::MotionEntry& entry,
- android::inputdispatcher::TouchState&& touchState) {
+ android::inputdispatcher::TouchState&& touchState,
+ const DispatcherWindowInfo& windowInfos) {
if (touchState.windows.empty()) {
- eraseTouchStateForMotionEntry(entry);
+ eraseTouchStateForMotionEntry(entry, windowInfos);
return;
}
- if (USE_TOPOLOGY && isMouseOrTouchpad(entry.source)) {
- mCursorStateByDisplay[entry.displayId] = std::move(touchState);
+ if (com::android::input::flags::connected_displays_cursor() &&
+ isMouseOrTouchpad(entry.source)) {
+ mCursorStateByDisplay[windowInfos.getPrimaryDisplayId(entry.displayId)] =
+ std::move(touchState);
} else {
mTouchStatesByDisplay[entry.displayId] = std::move(touchState);
}
}
void InputDispatcher::DispatcherTouchState::eraseTouchStateForMotionEntry(
- const android::inputdispatcher::MotionEntry& entry) {
- if (USE_TOPOLOGY && isMouseOrTouchpad(entry.source)) {
- mCursorStateByDisplay.erase(entry.displayId);
+ const android::inputdispatcher::MotionEntry& entry,
+ const DispatcherWindowInfo& windowInfos) {
+ if (com::android::input::flags::connected_displays_cursor() &&
+ isMouseOrTouchpad(entry.source)) {
+ mCursorStateByDisplay.erase(windowInfos.getPrimaryDisplayId(entry.displayId));
} else {
mTouchStatesByDisplay.erase(entry.displayId);
}
}
const TouchState* InputDispatcher::DispatcherTouchState::getTouchStateForMotionEntry(
- const android::inputdispatcher::MotionEntry& entry) const {
- if (USE_TOPOLOGY && isMouseOrTouchpad(entry.source)) {
- auto touchStateIt = mCursorStateByDisplay.find(entry.displayId);
+ const android::inputdispatcher::MotionEntry& entry,
+ const DispatcherWindowInfo& windowInfos) const {
+ if (com::android::input::flags::connected_displays_cursor() &&
+ isMouseOrTouchpad(entry.source)) {
+ auto touchStateIt =
+ mCursorStateByDisplay.find(windowInfos.getPrimaryDisplayId(entry.displayId));
if (touchStateIt != mCursorStateByDisplay.end()) {
return &touchStateIt->second;
}
diff --git a/services/inputflinger/dispatcher/InputDispatcher.h b/services/inputflinger/dispatcher/InputDispatcher.h
index c2224de..d765a86 100644
--- a/services/inputflinger/dispatcher/InputDispatcher.h
+++ b/services/inputflinger/dispatcher/InputDispatcher.h
@@ -336,6 +336,10 @@
bool isTouchTrusted(const TouchOcclusionInfo& occlusionInfo) const;
+ // Returns topology's primary display if the display belongs to it, otherwise the
+ // same displayId.
+ ui::LogicalDisplayId getPrimaryDisplayId(ui::LogicalDisplayId displayId) const;
+
std::string dumpDisplayAndWindowInfo() const;
private:
@@ -466,12 +470,15 @@
ftl::Flags<InputTarget::Flags> newTargetFlags,
const DispatcherWindowInfo& windowInfos, const ConnectionManager& connections);
- void saveTouchStateForMotionEntry(const MotionEntry& entry, TouchState&& touchState);
+ void saveTouchStateForMotionEntry(const MotionEntry& entry, TouchState&& touchState,
+ const DispatcherWindowInfo& windowInfos);
- void eraseTouchStateForMotionEntry(const MotionEntry& entry);
+ void eraseTouchStateForMotionEntry(const MotionEntry& entry,
+ const DispatcherWindowInfo& windowInfos);
const TouchState* getTouchStateForMotionEntry(
- const android::inputdispatcher::MotionEntry& entry) const;
+ const android::inputdispatcher::MotionEntry& entry,
+ const DispatcherWindowInfo& windowInfos) const;
bool canWindowReceiveMotion(const sp<gui::WindowInfoHandle>& window,
const MotionEntry& motionEntry,
diff --git a/services/inputflinger/dispatcher/InputState.cpp b/services/inputflinger/dispatcher/InputState.cpp
index 9b5a79b..5abd65a 100644
--- a/services/inputflinger/dispatcher/InputState.cpp
+++ b/services/inputflinger/dispatcher/InputState.cpp
@@ -24,6 +24,17 @@
namespace android::inputdispatcher {
+namespace {
+
+bool isMouseOrTouchpad(uint32_t sources) {
+ // Check if this is a mouse or touchpad, but not a drawing tablet.
+ return isFromSource(sources, AINPUT_SOURCE_MOUSE_RELATIVE) ||
+ (isFromSource(sources, AINPUT_SOURCE_MOUSE) &&
+ !isFromSource(sources, AINPUT_SOURCE_STYLUS));
+}
+
+} // namespace
+
InputState::InputState(const IdGenerator& idGenerator) : mIdGenerator(idGenerator) {}
InputState::~InputState() {}
@@ -221,10 +232,15 @@
}
ssize_t InputState::findMotionMemento(const MotionEntry& entry, bool hovering) const {
+ // If we have connected displays a mouse can move between displays and displayId may change
+ // while a gesture is in-progress.
+ const bool skipDisplayCheck = com::android::input::flags::connected_displays_cursor() &&
+ isMouseOrTouchpad(entry.source);
for (size_t i = 0; i < mMotionMementos.size(); i++) {
const MotionMemento& memento = mMotionMementos[i];
if (memento.deviceId == entry.deviceId && memento.source == entry.source &&
- memento.displayId == entry.displayId && memento.hovering == hovering) {
+ memento.hovering == hovering &&
+ (skipDisplayCheck || memento.displayId == entry.displayId)) {
return i;
}
}
@@ -338,7 +354,10 @@
// would receive different events from each display. Since the TouchStates are per-display,
// it's unlikely that those two streams would be consistent with each other. Therefore,
// cancel the previous gesture if the display id changes.
- if (motionEntry.displayId != lastMemento.displayId) {
+ // Except when we have connected-displays where a mouse may move across display boundaries.
+ const bool skipDisplayCheck = (com::android::input::flags::connected_displays_cursor() &&
+ isMouseOrTouchpad(motionEntry.source));
+ if (!skipDisplayCheck && motionEntry.displayId != lastMemento.displayId) {
LOG(INFO) << "Canceling stream: last displayId was " << lastMemento.displayId
<< " and new event is " << motionEntry;
return true;
diff --git a/services/inputflinger/tests/InputDispatcher_test.cpp b/services/inputflinger/tests/InputDispatcher_test.cpp
index e0a4afb..0c72d76 100644
--- a/services/inputflinger/tests/InputDispatcher_test.cpp
+++ b/services/inputflinger/tests/InputDispatcher_test.cpp
@@ -15185,4 +15185,91 @@
INSTANTIATE_TEST_SUITE_P(WithAndWithoutTransfer, TransferOrDontTransferFixture, testing::Bool());
+class InputDispatcherConnectedDisplayTest : public InputDispatcherTest {
+ constexpr static int DENSITY_MEDIUM = 160;
+
+ const DisplayTopologyGraph
+ mTopology{.primaryDisplayId = DISPLAY_ID,
+ .graph = {{DISPLAY_ID,
+ {{SECOND_DISPLAY_ID, DisplayTopologyPosition::TOP, 0.0f}}},
+ {SECOND_DISPLAY_ID,
+ {{DISPLAY_ID, DisplayTopologyPosition::BOTTOM, 0.0f}}}},
+ .displaysDensity = {{DISPLAY_ID, DENSITY_MEDIUM},
+ {SECOND_DISPLAY_ID, DENSITY_MEDIUM}}};
+
+protected:
+ sp<FakeWindowHandle> mWindow;
+
+ void SetUp() override {
+ InputDispatcherTest::SetUp();
+ mDispatcher->setDisplayTopology(mTopology);
+ mWindow = sp<FakeWindowHandle>::make(std::make_shared<FakeApplicationHandle>(), mDispatcher,
+ "Window", DISPLAY_ID);
+ mWindow->setFrame({0, 0, 100, 100});
+
+ mDispatcher->onWindowInfosChanged({{*mWindow->getInfo()}, {}, 0, 0});
+ }
+};
+
+TEST_F(InputDispatcherConnectedDisplayTest, MultiDisplayMouseGesture) {
+ SCOPED_FLAG_OVERRIDE(connected_displays_cursor, true);
+
+ // pointer-down
+ mDispatcher->notifyMotion(MotionArgsBuilder(AMOTION_EVENT_ACTION_DOWN, AINPUT_SOURCE_MOUSE)
+ .displayId(DISPLAY_ID)
+ .buttonState(AMOTION_EVENT_BUTTON_PRIMARY)
+ .pointer(PointerBuilder(0, ToolType::MOUSE).x(60).y(60))
+ .build());
+ mWindow->consumeMotionEvent(
+ AllOf(WithMotionAction(ACTION_DOWN), WithDisplayId(DISPLAY_ID), WithRawCoords(60, 60)));
+
+ mDispatcher->notifyMotion(
+ MotionArgsBuilder(AMOTION_EVENT_ACTION_BUTTON_PRESS, AINPUT_SOURCE_MOUSE)
+ .displayId(DISPLAY_ID)
+ .buttonState(AMOTION_EVENT_BUTTON_PRIMARY)
+ .actionButton(AMOTION_EVENT_BUTTON_PRIMARY)
+ .pointer(PointerBuilder(0, ToolType::MOUSE).x(60).y(60))
+ .build());
+ mWindow->consumeMotionEvent(AllOf(WithMotionAction(AMOTION_EVENT_ACTION_BUTTON_PRESS),
+ WithDisplayId(DISPLAY_ID), WithRawCoords(60, 60)));
+
+ // pointer-move
+ mDispatcher->notifyMotion(MotionArgsBuilder(AMOTION_EVENT_ACTION_MOVE, AINPUT_SOURCE_MOUSE)
+ .displayId(DISPLAY_ID)
+ .buttonState(AMOTION_EVENT_BUTTON_PRIMARY)
+ .pointer(PointerBuilder(0, ToolType::MOUSE).x(60).y(60))
+ .build());
+ mWindow->consumeMotionEvent(AllOf(WithMotionAction(AMOTION_EVENT_ACTION_MOVE),
+ WithDisplayId(DISPLAY_ID), WithRawCoords(60, 60)));
+
+ // pointer-move with different display
+ // TODO (b/383092013): by default windows will not be topology aware and receive events as it
+ // was in the same display. This behaviour has not been implemented yet.
+ mDispatcher->notifyMotion(MotionArgsBuilder(AMOTION_EVENT_ACTION_MOVE, AINPUT_SOURCE_MOUSE)
+ .displayId(SECOND_DISPLAY_ID)
+ .buttonState(AMOTION_EVENT_BUTTON_PRIMARY)
+ .pointer(PointerBuilder(0, ToolType::MOUSE).x(70).y(70))
+ .build());
+ mWindow->consumeMotionEvent(AllOf(WithMotionAction(AMOTION_EVENT_ACTION_MOVE),
+ WithDisplayId(SECOND_DISPLAY_ID), WithRawCoords(70, 70)));
+
+ // pointer-up
+ mDispatcher->notifyMotion(
+ MotionArgsBuilder(AMOTION_EVENT_ACTION_BUTTON_RELEASE, AINPUT_SOURCE_MOUSE)
+ .displayId(SECOND_DISPLAY_ID)
+ .buttonState(0)
+ .actionButton(AMOTION_EVENT_BUTTON_PRIMARY)
+ .pointer(PointerBuilder(0, ToolType::MOUSE).x(70).y(70))
+ .build());
+ mWindow->consumeMotionEvent(AllOf(WithMotionAction(AMOTION_EVENT_ACTION_BUTTON_RELEASE),
+ WithDisplayId(SECOND_DISPLAY_ID), WithRawCoords(70, 70)));
+
+ mDispatcher->notifyMotion(MotionArgsBuilder(AMOTION_EVENT_ACTION_UP, AINPUT_SOURCE_MOUSE)
+ .displayId(SECOND_DISPLAY_ID)
+ .buttonState(0)
+ .pointer(PointerBuilder(0, ToolType::MOUSE).x(70).y(70))
+ .build());
+ mWindow->consumeMotionUp(SECOND_DISPLAY_ID);
+}
+
} // namespace android::inputdispatcher