Skip to content

Commit a1343ba

Browse files
Arpit SinghAndroid (Google) Code Review
authored andcommitted
Merge "Enable cursor to transition across multiple displays" into main
2 parents 5cd14e1 + a5ba9f1 commit a1343ba

5 files changed

Lines changed: 217 additions & 43 deletions

File tree

services/inputflinger/PointerChoreographer.cpp

Lines changed: 177 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -103,6 +103,9 @@ std::unordered_set<ui::LogicalDisplayId> getPrivacySensitiveDisplaysFromWindowIn
103103

104104
// --- PointerChoreographer ---
105105

106+
const bool PointerChoreographer::IS_TOPOLOGY_AWARE =
107+
com::android::input::flags::connected_displays_cursor();
108+
106109
PointerChoreographer::PointerChoreographer(InputListenerInterface& inputListener,
107110
PointerChoreographerPolicyInterface& policy)
108111
: PointerChoreographer(
@@ -204,20 +207,30 @@ void PointerChoreographer::fadeMouseCursorOnKeyPress(const android::NotifyKeyArg
204207
}
205208

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

209-
if (isFromMouse(args)) {
210-
return processMouseEventLocked(args);
211-
} else if (isFromTouchpad(args)) {
212-
return processTouchpadEventLocked(args);
213-
} else if (isFromDrawingTablet(args)) {
214-
processDrawingTabletEventLocked(args);
215-
} else if (mStylusPointerIconEnabled && isStylusHoverEvent(args)) {
216-
processStylusHoverEventLocked(args);
217-
} else if (isFromSource(args.source, AINPUT_SOURCE_TOUCHSCREEN)) {
218-
processTouchscreenAndStylusEventLocked(args);
229+
if (pointerDisplayChange) {
230+
// pointer display may have changed if mouse crossed display boundary
231+
notifyPointerDisplayChange(pointerDisplayChange, mPolicy);
219232
}
220-
return args;
233+
return newArgs;
221234
}
222235

223236
NotifyMotionArgs PointerChoreographer::processMouseEventLocked(const NotifyMotionArgs& args) {
@@ -242,16 +255,10 @@ NotifyMotionArgs PointerChoreographer::processMouseEventLocked(const NotifyMotio
242255
pc.setPosition(args.xCursorPosition, args.yCursorPosition);
243256
} else {
244257
// This is a relative mouse, so move the cursor by the specified amount.
245-
const float deltaX = args.pointerCoords[0].getAxisValue(AMOTION_EVENT_AXIS_RELATIVE_X);
246-
const float deltaY = args.pointerCoords[0].getAxisValue(AMOTION_EVENT_AXIS_RELATIVE_Y);
247-
pc.move(deltaX, deltaY);
248-
const auto [x, y] = pc.getPosition();
249-
newArgs.pointerCoords[0].setAxisValue(AMOTION_EVENT_AXIS_X, x);
250-
newArgs.pointerCoords[0].setAxisValue(AMOTION_EVENT_AXIS_Y, y);
251-
newArgs.xCursorPosition = x;
252-
newArgs.yCursorPosition = y;
258+
processPointerDeviceMotionEventLocked(/*byref*/ newArgs, /*byref*/ pc);
253259
}
254-
if (canUnfadeOnDisplay(displayId)) {
260+
// Note displayId may have changed if the cursor moved to a different display
261+
if (canUnfadeOnDisplay(newArgs.displayId)) {
255262
pc.unfade(PointerControllerInterface::Transition::IMMEDIATE);
256263
}
257264
return newArgs;
@@ -265,24 +272,9 @@ NotifyMotionArgs PointerChoreographer::processTouchpadEventLocked(const NotifyMo
265272
newArgs.displayId = displayId;
266273
if (args.getPointerCount() == 1 && args.classification == MotionClassification::NONE) {
267274
// This is a movement of the mouse pointer.
268-
const float deltaX = args.pointerCoords[0].getAxisValue(AMOTION_EVENT_AXIS_RELATIVE_X);
269-
const float deltaY = args.pointerCoords[0].getAxisValue(AMOTION_EVENT_AXIS_RELATIVE_Y);
270-
pc.move(deltaX, deltaY);
271-
if (canUnfadeOnDisplay(displayId)) {
272-
pc.unfade(PointerControllerInterface::Transition::IMMEDIATE);
273-
}
274-
275-
const auto [x, y] = pc.getPosition();
276-
newArgs.pointerCoords[0].setAxisValue(AMOTION_EVENT_AXIS_X, x);
277-
newArgs.pointerCoords[0].setAxisValue(AMOTION_EVENT_AXIS_Y, y);
278-
newArgs.xCursorPosition = x;
279-
newArgs.yCursorPosition = y;
275+
processPointerDeviceMotionEventLocked(/*byref*/ newArgs, /*byref*/ pc);
280276
} else {
281277
// This is a trackpad gesture with fake finger(s) that should not move the mouse pointer.
282-
if (canUnfadeOnDisplay(displayId)) {
283-
pc.unfade(PointerControllerInterface::Transition::IMMEDIATE);
284-
}
285-
286278
const auto [x, y] = pc.getPosition();
287279
for (uint32_t i = 0; i < newArgs.getPointerCount(); i++) {
288280
newArgs.pointerCoords[i].setAxisValue(AMOTION_EVENT_AXIS_X,
@@ -293,9 +285,61 @@ NotifyMotionArgs PointerChoreographer::processTouchpadEventLocked(const NotifyMo
293285
newArgs.xCursorPosition = x;
294286
newArgs.yCursorPosition = y;
295287
}
288+
289+
// Note displayId may have changed if the cursor moved to a different display
290+
if (canUnfadeOnDisplay(newArgs.displayId)) {
291+
pc.unfade(PointerControllerInterface::Transition::IMMEDIATE);
292+
}
296293
return newArgs;
297294
}
298295

296+
void PointerChoreographer::processPointerDeviceMotionEventLocked(NotifyMotionArgs& newArgs,
297+
PointerControllerInterface& pc) {
298+
const float deltaX = newArgs.pointerCoords[0].getAxisValue(AMOTION_EVENT_AXIS_RELATIVE_X);
299+
const float deltaY = newArgs.pointerCoords[0].getAxisValue(AMOTION_EVENT_AXIS_RELATIVE_Y);
300+
vec2 unconsumedDelta = pc.move(deltaX, deltaY);
301+
if (IS_TOPOLOGY_AWARE && (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+
const auto [x, y] = pc.getPosition();
307+
newArgs.pointerCoords[0].setAxisValue(AMOTION_EVENT_AXIS_X, x);
308+
newArgs.pointerCoords[0].setAxisValue(AMOTION_EVENT_AXIS_Y, y);
309+
newArgs.xCursorPosition = x;
310+
newArgs.yCursorPosition = y;
311+
}
312+
313+
void PointerChoreographer::handleUnconsumedDeltaLocked(PointerControllerInterface& pc,
314+
const vec2& unconsumedDelta) {
315+
const ui::LogicalDisplayId sourceDisplayId = pc.getDisplayId();
316+
const auto& sourceViewport = *findViewportByIdLocked(sourceDisplayId);
317+
std::optional<AdjacentDisplay> destinationDisplay =
318+
findDestinationDisplayLocked(sourceViewport, unconsumedDelta);
319+
if (!destinationDisplay) {
320+
// no adjacent display
321+
return;
322+
}
323+
324+
const DisplayViewport* destinationViewport =
325+
findViewportByIdLocked(destinationDisplay->displayId);
326+
if (destinationViewport == nullptr) {
327+
// Topology is likely out of sync with viewport info, wait for them to be updated
328+
LOG(WARNING) << "Cannot find viewport for adjacent display "
329+
<< destinationDisplay->displayId << "of source display " << sourceDisplayId;
330+
return;
331+
}
332+
333+
mDefaultMouseDisplayId = destinationDisplay->displayId;
334+
auto pcNode = mMousePointersByDisplay.extract(sourceDisplayId);
335+
pcNode.key() = destinationDisplay->displayId;
336+
mMousePointersByDisplay.insert(std::move(pcNode));
337+
338+
// This will place cursor at the center of the target display for now
339+
// TODO (b/367660694) place the cursor at the appropriate position in destination display
340+
pc.setDisplayViewport(*destinationViewport);
341+
}
342+
299343
void PointerChoreographer::processDrawingTabletEventLocked(const android::NotifyMotionArgs& args) {
300344
if (args.displayId == ui::LogicalDisplayId::INVALID) {
301345
return;
@@ -441,7 +485,8 @@ void PointerChoreographer::processDeviceReset(const NotifyDeviceResetArgs& args)
441485
}
442486

443487
void PointerChoreographer::onControllerAddedOrRemovedLocked() {
444-
if (!com::android::input::flags::hide_pointer_indicators_for_secure_windows()) {
488+
if (!com::android::input::flags::hide_pointer_indicators_for_secure_windows() &&
489+
!IS_TOPOLOGY_AWARE) {
445490
return;
446491
}
447492
bool requireListener = !mTouchPointersByDevice.empty() || !mMousePointersByDisplay.empty() ||
@@ -674,6 +719,10 @@ PointerChoreographer::calculatePointerDisplayChangeToNotify() {
674719
}
675720

676721
void PointerChoreographer::setDefaultMouseDisplayId(ui::LogicalDisplayId displayId) {
722+
if (IS_TOPOLOGY_AWARE) {
723+
// default display will be set based on the topology
724+
return;
725+
}
677726
PointerDisplayChange pointerDisplayChange;
678727

679728
{ // acquire lock
@@ -887,6 +936,7 @@ void PointerChoreographer::PointerChoreographerDisplayInfoListener::onWindowInfo
887936
mPrivacySensitiveDisplays = std::move(newPrivacySensitiveDisplays);
888937
mPointerChoreographer->onPrivacySensitiveDisplaysChanged(mPrivacySensitiveDisplays);
889938
}
939+
mPointerChoreographer->populateFakeDisplayTopology(windowInfosUpdate.displayInfos);
890940
}
891941

892942
void PointerChoreographer::PointerChoreographerDisplayInfoListener::setInitialDisplayInfos(
@@ -907,4 +957,93 @@ void PointerChoreographer::PointerChoreographerDisplayInfoListener::
907957
mPointerChoreographer = nullptr;
908958
}
909959

960+
void PointerChoreographer::populateFakeDisplayTopology(
961+
const std::vector<gui::DisplayInfo>& displayInfos) {
962+
if (!IS_TOPOLOGY_AWARE) {
963+
return;
964+
}
965+
std::scoped_lock _lock(mLock);
966+
967+
if (displayInfos.size() == mTopology.size()) {
968+
bool displaysChanged = false;
969+
for (const auto& displayInfo : displayInfos) {
970+
if (mTopology.find(displayInfo.displayId) == mTopology.end()) {
971+
displaysChanged = true;
972+
break;
973+
}
974+
}
975+
976+
if (!displaysChanged) {
977+
return;
978+
}
979+
}
980+
981+
// create a fake topology assuming following order
982+
// default-display (top-edge) -> next-display (right-edge) -> next-display (right-edge) ...
983+
// ┌─────────┬─────────┐
984+
// │ next │ next 2 │ ...
985+
// ├─────────┼─────────┘
986+
// │ default │
987+
// └─────────┘
988+
mTopology.clear();
989+
990+
// treat default display as base, in real topology it should be the primary-display
991+
ui::LogicalDisplayId previousDisplay = ui::LogicalDisplayId::DEFAULT;
992+
for (const auto& displayInfo : displayInfos) {
993+
if (displayInfo.displayId == ui::LogicalDisplayId::DEFAULT) {
994+
continue;
995+
}
996+
if (previousDisplay == ui::LogicalDisplayId::DEFAULT) {
997+
mTopology[previousDisplay].push_back({displayInfo.displayId, DisplayPosition::TOP, 0});
998+
mTopology[displayInfo.displayId].push_back(
999+
{previousDisplay, DisplayPosition::BOTTOM, 0});
1000+
} else {
1001+
mTopology[previousDisplay].push_back(
1002+
{displayInfo.displayId, DisplayPosition::RIGHT, 0});
1003+
mTopology[displayInfo.displayId].push_back({previousDisplay, DisplayPosition::LEFT, 0});
1004+
}
1005+
previousDisplay = displayInfo.displayId;
1006+
}
1007+
1008+
// update default pointer display. In real topology it should be the primary-display
1009+
if (mTopology.find(mDefaultMouseDisplayId) == mTopology.end()) {
1010+
mDefaultMouseDisplayId = ui::LogicalDisplayId::DEFAULT;
1011+
}
1012+
}
1013+
1014+
std::optional<PointerChoreographer::AdjacentDisplay>
1015+
PointerChoreographer::findDestinationDisplayLocked(const DisplayViewport& sourceViewport,
1016+
const vec2& unconsumedDelta) const {
1017+
DisplayPosition sourceBoundary;
1018+
if (unconsumedDelta.x > 0) {
1019+
sourceBoundary = DisplayPosition::RIGHT;
1020+
} else if (unconsumedDelta.x < 0) {
1021+
sourceBoundary = DisplayPosition::LEFT;
1022+
} else if (unconsumedDelta.y > 0) {
1023+
sourceBoundary = DisplayPosition::BOTTOM;
1024+
} else {
1025+
sourceBoundary = DisplayPosition::TOP;
1026+
}
1027+
1028+
// Choreographer works in un-rotate coordinate space so we need to rotate boundary by viewport
1029+
// orientation to find the rotated boundary
1030+
constexpr int MOD = ftl::to_underlying(ui::Rotation::ftl_last) + 1;
1031+
sourceBoundary = static_cast<DisplayPosition>(
1032+
(ftl::to_underlying(sourceBoundary) + ftl::to_underlying(sourceViewport.orientation)) %
1033+
MOD);
1034+
1035+
if (mTopology.find(sourceViewport.displayId) == mTopology.end()) {
1036+
// Topology is likely out of sync with viewport info, wait for them to be updated
1037+
LOG(WARNING) << "Source display missing from topology " << sourceViewport.displayId;
1038+
return std::nullopt;
1039+
}
1040+
1041+
for (const auto& adjacentDisplay : mTopology.at(sourceViewport.displayId)) {
1042+
if (adjacentDisplay.position == sourceBoundary) {
1043+
return adjacentDisplay;
1044+
}
1045+
}
1046+
return std::nullopt;
1047+
}
1048+
9101049
} // namespace android

services/inputflinger/PointerChoreographer.h

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -137,13 +137,41 @@ class PointerChoreographer : public PointerChoreographerInterface {
137137
void processTouchscreenAndStylusEventLocked(const NotifyMotionArgs& args) REQUIRES(mLock);
138138
void processStylusHoverEventLocked(const NotifyMotionArgs& args) REQUIRES(mLock);
139139
void processDeviceReset(const NotifyDeviceResetArgs& args);
140+
void processPointerDeviceMotionEventLocked(NotifyMotionArgs& newArgs,
141+
PointerControllerInterface& pc) REQUIRES(mLock);
140142
void onControllerAddedOrRemovedLocked() REQUIRES(mLock);
141143
void onPrivacySensitiveDisplaysChangedLocked(
142144
const std::unordered_set<ui::LogicalDisplayId>& privacySensitiveDisplays)
143145
REQUIRES(mLock);
144146
void onPrivacySensitiveDisplaysChanged(
145147
const std::unordered_set<ui::LogicalDisplayId>& privacySensitiveDisplays);
146148

149+
void handleUnconsumedDeltaLocked(PointerControllerInterface& pc, const vec2& unconsumedDelta)
150+
REQUIRES(mLock);
151+
152+
// TODO(b/362719483) remove these when real topology is available
153+
enum class DisplayPosition : int32_t {
154+
RIGHT = 0,
155+
TOP = 1,
156+
LEFT = 2,
157+
BOTTOM = 3,
158+
ftl_last = BOTTOM,
159+
};
160+
161+
struct AdjacentDisplay {
162+
ui::LogicalDisplayId displayId;
163+
DisplayPosition position;
164+
float offsetPx;
165+
};
166+
void populateFakeDisplayTopology(const std::vector<gui::DisplayInfo>& displayInfos);
167+
168+
std::optional<AdjacentDisplay> findDestinationDisplayLocked(
169+
const DisplayViewport& sourceViewport, const vec2& unconsumedDelta) const
170+
REQUIRES(mLock);
171+
172+
std::unordered_map<ui::LogicalDisplayId, std::vector<AdjacentDisplay>> mTopology
173+
GUARDED_BY(mLock);
174+
147175
/* This listener keeps tracks of visible privacy sensitive displays and updates the
148176
* choreographer if there are any changes.
149177
*
@@ -211,6 +239,7 @@ class PointerChoreographer : public PointerChoreographerInterface {
211239
const WindowListenerUnregisterConsumer& unregisterListener);
212240

213241
private:
242+
const static bool IS_TOPOLOGY_AWARE;
214243
const WindowListenerRegisterConsumer mRegisterListener;
215244
const WindowListenerUnregisterConsumer mUnregisterListener;
216245
};

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 vec2 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: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -148,15 +148,17 @@ bool FakePointerController::isPointerShown() {
148148
return mIsPointerShown;
149149
}
150150

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

154154
mX += deltaX;
155155
if (mX < mMinX) mX = mMinX;
156156
if (mX > mMaxX) mX = mMaxX;
157157
mY += deltaY;
158158
if (mY < mMinY) mY = mMinY;
159159
if (mY > mMaxY) mY = mMaxY;
160+
161+
return {};
160162
}
161163

162164
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+
vec2 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)