Skip to content

Commit e33845c

Browse files
author
Arpit Singh
committed
Enable cursor to transition across multiple displays
This CL enables cursor to move between displays. It uses a fake topology that assumes all available displays are connected in the following order: default-display (top-edge) -> next-display (right-edge) -> next-display (right-edge) ... Test: atest inputflinger_tests Test: verify cursor can move between displays as expected Bug: 367659738 Bug: 367660694 Flag: com.android.input.flags.connected_displays_cursor Change-Id: I8b3b7c3c0e68dca1e7ce281cb7ff6efab263a500
1 parent 8a3f409 commit e33845c

6 files changed

Lines changed: 340 additions & 24 deletions

File tree

services/inputflinger/PointerChoreographer.cpp

Lines changed: 174 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -205,20 +205,30 @@ void PointerChoreographer::fadeMouseCursorOnKeyPress(const android::NotifyKeyArg
205205
}
206206

207207
NotifyMotionArgs PointerChoreographer::processMotion(const NotifyMotionArgs& args) {
208-
std::scoped_lock _l(getLock());
208+
NotifyMotionArgs newArgs(args);
209+
PointerDisplayChange pointerDisplayChange;
210+
{ // acquire lock
211+
std::scoped_lock _l(getLock());
212+
if (isFromMouse(args)) {
213+
newArgs = processMouseEventLocked(args);
214+
pointerDisplayChange = calculatePointerDisplayChangeToNotify();
215+
} else if (isFromTouchpad(args)) {
216+
newArgs = processTouchpadEventLocked(args);
217+
pointerDisplayChange = calculatePointerDisplayChangeToNotify();
218+
} else if (isFromDrawingTablet(args)) {
219+
processDrawingTabletEventLocked(args);
220+
} else if (mStylusPointerIconEnabled && isStylusHoverEvent(args)) {
221+
processStylusHoverEventLocked(args);
222+
} else if (isFromSource(args.source, AINPUT_SOURCE_TOUCHSCREEN)) {
223+
processTouchscreenAndStylusEventLocked(args);
224+
}
225+
} // release lock
209226

210-
if (isFromMouse(args)) {
211-
return processMouseEventLocked(args);
212-
} else if (isFromTouchpad(args)) {
213-
return processTouchpadEventLocked(args);
214-
} else if (isFromDrawingTablet(args)) {
215-
processDrawingTabletEventLocked(args);
216-
} else if (mStylusPointerIconEnabled && isStylusHoverEvent(args)) {
217-
processStylusHoverEventLocked(args);
218-
} else if (isFromSource(args.source, AINPUT_SOURCE_TOUCHSCREEN)) {
219-
processTouchscreenAndStylusEventLocked(args);
227+
if (pointerDisplayChange) {
228+
// pointer display may have changed if mouse crossed display boundary
229+
notifyPointerDisplayChange(pointerDisplayChange, mPolicy);
220230
}
221-
return args;
231+
return newArgs;
222232
}
223233

224234
NotifyMotionArgs PointerChoreographer::processMouseEventLocked(const NotifyMotionArgs& args) {
@@ -245,7 +255,8 @@ NotifyMotionArgs PointerChoreographer::processMouseEventLocked(const NotifyMotio
245255
// This is a relative mouse, so move the cursor by the specified amount.
246256
processPointerDeviceMotionEventLocked(/*byref*/ newArgs, /*byref*/ pc);
247257
}
248-
if (canUnfadeOnDisplay(displayId)) {
258+
// Note displayId may have changed if the cursor moved to a different display
259+
if (canUnfadeOnDisplay(newArgs.displayId)) {
249260
pc.unfade(PointerControllerInterface::Transition::IMMEDIATE);
250261
}
251262
return newArgs;
@@ -272,7 +283,9 @@ NotifyMotionArgs PointerChoreographer::processTouchpadEventLocked(const NotifyMo
272283
newArgs.xCursorPosition = x;
273284
newArgs.yCursorPosition = y;
274285
}
275-
if (canUnfadeOnDisplay(displayId)) {
286+
287+
// Note displayId may have changed if the cursor moved to a different display
288+
if (canUnfadeOnDisplay(newArgs.displayId)) {
276289
pc.unfade(PointerControllerInterface::Transition::IMMEDIATE);
277290
}
278291
return newArgs;
@@ -283,14 +296,49 @@ void PointerChoreographer::processPointerDeviceMotionEventLocked(NotifyMotionArg
283296
const float deltaX = newArgs.pointerCoords[0].getAxisValue(AMOTION_EVENT_AXIS_RELATIVE_X);
284297
const float deltaY = newArgs.pointerCoords[0].getAxisValue(AMOTION_EVENT_AXIS_RELATIVE_Y);
285298

286-
pc.move(deltaX, deltaY);
299+
FloatPoint unconsumedDelta = pc.move(deltaX, deltaY);
300+
if (com::android::input::flags::connected_displays_cursor() &&
301+
(std::abs(unconsumedDelta.x) > 0 || std::abs(unconsumedDelta.y) > 0)) {
302+
handleUnconsumedDeltaLocked(pc, unconsumedDelta);
303+
// pointer may have moved to a different viewport
304+
newArgs.displayId = pc.getDisplayId();
305+
}
306+
287307
const auto [x, y] = pc.getPosition();
288308
newArgs.pointerCoords[0].setAxisValue(AMOTION_EVENT_AXIS_X, x);
289309
newArgs.pointerCoords[0].setAxisValue(AMOTION_EVENT_AXIS_Y, y);
290310
newArgs.xCursorPosition = x;
291311
newArgs.yCursorPosition = y;
292312
}
293313

314+
void PointerChoreographer::handleUnconsumedDeltaLocked(PointerControllerInterface& pc,
315+
const FloatPoint& unconsumedDelta) {
316+
const ui::LogicalDisplayId sourceDisplayId = pc.getDisplayId();
317+
const auto& sourceViewport = *findViewportByIdLocked(sourceDisplayId);
318+
std::optional<const DisplayViewport*> destination =
319+
findDestinationDisplayLocked(sourceViewport, unconsumedDelta);
320+
if (!destination) {
321+
// no adjacent display
322+
return;
323+
}
324+
325+
const DisplayViewport* destinationViewport = *destination;
326+
327+
if (mMousePointersByDisplay.find(destinationViewport->displayId) !=
328+
mMousePointersByDisplay.end()) {
329+
LOG(FATAL) << "A cursor already exists on destination display"
330+
<< destinationViewport->displayId;
331+
}
332+
mDefaultMouseDisplayId = destinationViewport->displayId;
333+
auto pcNode = mMousePointersByDisplay.extract(sourceDisplayId);
334+
pcNode.key() = destinationViewport->displayId;
335+
mMousePointersByDisplay.insert(std::move(pcNode));
336+
337+
// This will place cursor at the center of the target display for now
338+
// TODO (b/367660694) place the cursor at the appropriate position in destination display
339+
pc.setDisplayViewport(*destinationViewport);
340+
}
341+
294342
void PointerChoreographer::processDrawingTabletEventLocked(const android::NotifyMotionArgs& args) {
295343
if (args.displayId == ui::LogicalDisplayId::INVALID) {
296344
return;
@@ -436,7 +484,8 @@ void PointerChoreographer::processDeviceReset(const NotifyDeviceResetArgs& args)
436484
}
437485

438486
void PointerChoreographer::onControllerAddedOrRemovedLocked() {
439-
if (!com::android::input::flags::hide_pointer_indicators_for_secure_windows()) {
487+
if (!com::android::input::flags::hide_pointer_indicators_for_secure_windows() &&
488+
!com::android::input::flags::connected_displays_cursor()) {
440489
return;
441490
}
442491
bool requireListener = !mTouchPointersByDevice.empty() || !mMousePointersByDisplay.empty() ||
@@ -502,6 +551,13 @@ void PointerChoreographer::notifyPointerCaptureChanged(
502551
mNextListener.notify(args);
503552
}
504553

554+
void PointerChoreographer::setDisplayTopology(
555+
const std::unordered_map<ui::LogicalDisplayId, std::vector<AdjacentDisplay>>&
556+
displayTopology) {
557+
std::scoped_lock _l(getLock());
558+
mTopology = displayTopology;
559+
}
560+
505561
void PointerChoreographer::dump(std::string& dump) {
506562
std::scoped_lock _l(getLock());
507563

@@ -873,6 +929,104 @@ PointerChoreographer::ControllerConstructor PointerChoreographer::getStylusContr
873929
return ConstructorDelegate(std::move(ctor));
874930
}
875931

932+
void PointerChoreographer::populateFakeDisplayTopologyLocked(
933+
const std::vector<gui::DisplayInfo>& displayInfos) {
934+
if (!com::android::input::flags::connected_displays_cursor()) {
935+
return;
936+
}
937+
938+
if (displayInfos.size() == mTopology.size()) {
939+
bool displaysChanged = false;
940+
for (const auto& displayInfo : displayInfos) {
941+
if (mTopology.find(displayInfo.displayId) == mTopology.end()) {
942+
displaysChanged = true;
943+
break;
944+
}
945+
}
946+
947+
if (!displaysChanged) {
948+
return;
949+
}
950+
}
951+
952+
// create a fake topology assuming following order
953+
// default-display (top-edge) -> next-display (right-edge) -> next-display (right-edge) ...
954+
// ┌─────────┬─────────┐
955+
// │ next │ next 2 │ ...
956+
// ├─────────┼─────────┘
957+
// │ default │
958+
// └─────────┘
959+
mTopology.clear();
960+
961+
// treat default display as base, in real topology it should be the primary-display
962+
ui::LogicalDisplayId previousDisplay = ui::LogicalDisplayId::DEFAULT;
963+
for (const auto& displayInfo : displayInfos) {
964+
if (displayInfo.displayId == ui::LogicalDisplayId::DEFAULT) {
965+
continue;
966+
}
967+
if (previousDisplay == ui::LogicalDisplayId::DEFAULT) {
968+
mTopology[previousDisplay].push_back({displayInfo.displayId, DisplayPosition::TOP, 0});
969+
mTopology[displayInfo.displayId].push_back(
970+
{previousDisplay, DisplayPosition::BOTTOM, 0});
971+
} else {
972+
mTopology[previousDisplay].push_back(
973+
{displayInfo.displayId, DisplayPosition::RIGHT, 0});
974+
mTopology[displayInfo.displayId].push_back({previousDisplay, DisplayPosition::LEFT, 0});
975+
}
976+
previousDisplay = displayInfo.displayId;
977+
}
978+
979+
// update default pointer display. In real topology it should be the primary-display
980+
if (mTopology.find(mDefaultMouseDisplayId) == mTopology.end()) {
981+
mDefaultMouseDisplayId = ui::LogicalDisplayId::DEFAULT;
982+
}
983+
}
984+
985+
std::optional<const DisplayViewport*> PointerChoreographer::findDestinationDisplayLocked(
986+
const DisplayViewport& sourceViewport, const FloatPoint& unconsumedDelta) const {
987+
DisplayPosition sourceBoundary;
988+
if (unconsumedDelta.x > 0) {
989+
sourceBoundary = DisplayPosition::RIGHT;
990+
} else if (unconsumedDelta.x < 0) {
991+
sourceBoundary = DisplayPosition::LEFT;
992+
} else if (unconsumedDelta.y > 0) {
993+
sourceBoundary = DisplayPosition::BOTTOM;
994+
} else {
995+
sourceBoundary = DisplayPosition::TOP;
996+
}
997+
998+
// Choreographer works in un-rotate coordinate space so we need to rotate boundary by viewport
999+
// orientation to find the rotated boundary
1000+
constexpr int MOD = ftl::to_underlying(ui::Rotation::ftl_last) + 1;
1001+
sourceBoundary = static_cast<DisplayPosition>(
1002+
(ftl::to_underlying(sourceBoundary) + ftl::to_underlying(sourceViewport.orientation)) %
1003+
MOD);
1004+
1005+
const auto& destination = mTopology.find(sourceViewport.displayId);
1006+
if (destination == mTopology.end()) {
1007+
// Topology is likely out of sync with viewport info, wait for it to be updated
1008+
LOG(WARNING) << "Source display missing from topology " << sourceViewport.displayId;
1009+
return std::nullopt;
1010+
}
1011+
1012+
for (const auto& adjacentDisplay : destination->second) {
1013+
if (adjacentDisplay.position != sourceBoundary) {
1014+
continue;
1015+
}
1016+
const DisplayViewport* destinationViewport =
1017+
findViewportByIdLocked(adjacentDisplay.displayId);
1018+
if (destinationViewport == nullptr) {
1019+
// Topology is likely out of sync with viewport info, wait for them to be updated
1020+
LOG(WARNING) << "Cannot find viewport for adjacent display "
1021+
<< adjacentDisplay.displayId << "of source display "
1022+
<< sourceViewport.displayId;
1023+
break;
1024+
}
1025+
return destinationViewport;
1026+
}
1027+
return std::nullopt;
1028+
}
1029+
8761030
// --- PointerChoreographer::PointerChoreographerDisplayInfoListener ---
8771031

8781032
void PointerChoreographer::PointerChoreographerDisplayInfoListener::onWindowInfosChanged(
@@ -883,12 +1037,14 @@ void PointerChoreographer::PointerChoreographerDisplayInfoListener::onWindowInfo
8831037
}
8841038
auto newPrivacySensitiveDisplays =
8851039
getPrivacySensitiveDisplaysFromWindowInfos(windowInfosUpdate.windowInfos);
1040+
1041+
// PointerChoreographer uses Listener's lock.
1042+
base::ScopedLockAssertion assumeLocked(mPointerChoreographer->getLock());
8861043
if (newPrivacySensitiveDisplays != mPrivacySensitiveDisplays) {
8871044
mPrivacySensitiveDisplays = std::move(newPrivacySensitiveDisplays);
888-
// PointerChoreographer uses Listener's lock.
889-
base::ScopedLockAssertion assumeLocked(mPointerChoreographer->getLock());
8901045
mPointerChoreographer->onPrivacySensitiveDisplaysChangedLocked(mPrivacySensitiveDisplays);
8911046
}
1047+
mPointerChoreographer->populateFakeDisplayTopologyLocked(windowInfosUpdate.displayInfos);
8921048
}
8931049

8941050
void PointerChoreographer::PointerChoreographerDisplayInfoListener::setInitialDisplayInfosLocked(

services/inputflinger/PointerChoreographer.h

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -113,6 +113,24 @@ class PointerChoreographer : public PointerChoreographerInterface {
113113
void notifyDeviceReset(const NotifyDeviceResetArgs& args) override;
114114
void notifyPointerCaptureChanged(const NotifyPointerCaptureChangedArgs& args) override;
115115

116+
// TODO(b/362719483) remove these when real topology is available
117+
enum class DisplayPosition : int32_t {
118+
RIGHT = 0,
119+
TOP = 1,
120+
LEFT = 2,
121+
BOTTOM = 3,
122+
ftl_last = BOTTOM,
123+
};
124+
125+
struct AdjacentDisplay {
126+
ui::LogicalDisplayId displayId;
127+
DisplayPosition position;
128+
float offsetPx;
129+
};
130+
void setDisplayTopology(
131+
const std::unordered_map<ui::LogicalDisplayId, std::vector<AdjacentDisplay>>&
132+
displayTopology);
133+
116134
void dump(std::string& dump) override;
117135

118136
private:
@@ -153,6 +171,19 @@ class PointerChoreographer : public PointerChoreographerInterface {
153171
const std::unordered_set<ui::LogicalDisplayId>& privacySensitiveDisplays)
154172
REQUIRES(getLock());
155173

174+
void handleUnconsumedDeltaLocked(PointerControllerInterface& pc,
175+
const FloatPoint& unconsumedDelta) REQUIRES(getLock());
176+
177+
void populateFakeDisplayTopologyLocked(const std::vector<gui::DisplayInfo>& displayInfos)
178+
REQUIRES(getLock());
179+
180+
std::optional<const DisplayViewport*> findDestinationDisplayLocked(
181+
const DisplayViewport& sourceViewport, const FloatPoint& unconsumedDelta) const
182+
REQUIRES(getLock());
183+
184+
std::unordered_map<ui::LogicalDisplayId, std::vector<AdjacentDisplay>> mTopology
185+
GUARDED_BY(getLock());
186+
156187
/* This listener keeps tracks of visible privacy sensitive displays and updates the
157188
* choreographer if there are any changes.
158189
*

services/inputflinger/include/PointerControllerInterface.h

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -72,8 +72,12 @@ class PointerControllerInterface {
7272
/* Dumps the state of the pointer controller. */
7373
virtual std::string dump() = 0;
7474

75-
/* Move the pointer. */
76-
virtual void move(float deltaX, float deltaY) = 0;
75+
/* Move the pointer and return unconsumed delta if the pointer has crossed the current
76+
* viewport bounds .
77+
*
78+
* Return value may be used to move pointer to corresponding adjacent display, if it exists in
79+
* the display-topology */
80+
[[nodiscard]] virtual FloatPoint move(float deltaX, float deltaY) = 0;
7781

7882
/* Sets the absolute location of the pointer. */
7983
virtual void setPosition(float x, float y) = 0;

services/inputflinger/tests/FakePointerController.cpp

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -148,15 +148,20 @@ bool FakePointerController::isPointerShown() {
148148
return mIsPointerShown;
149149
}
150150

151-
void FakePointerController::move(float deltaX, float deltaY) {
152-
if (!mEnabled) return;
151+
FloatPoint FakePointerController::move(float deltaX, float deltaY) {
152+
if (!mEnabled) return {0, 0};
153153

154154
mX += deltaX;
155+
mY += deltaY;
156+
157+
const FloatPoint position(mX, mY);
158+
155159
if (mX < mMinX) mX = mMinX;
156160
if (mX > mMaxX) mX = mMaxX;
157-
mY += deltaY;
158161
if (mY < mMinY) mY = mMinY;
159162
if (mY > mMaxY) mY = mMaxY;
163+
164+
return {position.x - mX, position.y - mY};
160165
}
161166

162167
void FakePointerController::fade(Transition) {

services/inputflinger/tests/FakePointerController.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -65,7 +65,7 @@ class FakePointerController : public PointerControllerInterface {
6565

6666
private:
6767
std::string dump() override { return ""; }
68-
void move(float deltaX, float deltaY) override;
68+
FloatPoint move(float deltaX, float deltaY) override;
6969
void unfade(Transition) override;
7070
void setPresentation(Presentation) override {}
7171
void setSpots(const PointerCoords*, const uint32_t*, BitSet32 spotIdBits,

0 commit comments

Comments
 (0)