Skip to content

Commit f0169ab

Browse files
author
Arpit Singh
committed
Set appropriate cursor position on viewport transition
This CL updates PointerChoreographer to set position of the cursor on the destination display when cursor crosses the boundary. Also updates the logic to lookup destination displays in topology to account for size and offset of the display. Test: presubmit and manual Bug: 367660694 Flag: com.android.input.flags.connected_displays_cursor Change-Id: I18e3bd925a44d541e34946494e7e1b9db0a2e786
1 parent e33845c commit f0169ab

6 files changed

Lines changed: 207 additions & 93 deletions

File tree

services/inputflinger/PointerChoreographer.cpp

Lines changed: 103 additions & 51 deletions
Original file line numberDiff line numberDiff line change
@@ -313,30 +313,89 @@ void PointerChoreographer::processPointerDeviceMotionEventLocked(NotifyMotionArg
313313

314314
void PointerChoreographer::handleUnconsumedDeltaLocked(PointerControllerInterface& pc,
315315
const FloatPoint& unconsumedDelta) {
316+
// Display topology is in rotated coordinate space and Pointer controller returns and expects
317+
// values in the un-rotated coordinate space. So we need to transform delta and cursor position
318+
// back to the rotated coordinate space to lookup adjacent display in the display topology.
319+
const auto& sourceDisplayTransform = pc.getDisplayTransform();
320+
const vec2 rotatedUnconsumedDelta =
321+
transformWithoutTranslation(sourceDisplayTransform,
322+
{unconsumedDelta.x, unconsumedDelta.y});
323+
const FloatPoint cursorPosition = pc.getPosition();
324+
const vec2 rotatedCursorPosition =
325+
sourceDisplayTransform.transform(cursorPosition.x, cursorPosition.y);
326+
327+
// To find out the boundary that cursor is crossing we are checking delta in x and y direction
328+
// respectively. This prioritizes x direction over y.
329+
// In practise, majority of cases we only have non-zero values in either x or y coordinates,
330+
// except sometimes near the corners.
331+
// In these cases this behaviour is not noticeable. We also do not apply unconsumed delta on
332+
// the destination display for the same reason.
333+
DisplayPosition sourceBoundary;
334+
float cursorOffset = 0.0f;
335+
if (rotatedUnconsumedDelta.x > 0) {
336+
sourceBoundary = DisplayPosition::RIGHT;
337+
cursorOffset = rotatedCursorPosition.y;
338+
} else if (rotatedUnconsumedDelta.x < 0) {
339+
sourceBoundary = DisplayPosition::LEFT;
340+
cursorOffset = rotatedCursorPosition.y;
341+
} else if (rotatedUnconsumedDelta.y > 0) {
342+
sourceBoundary = DisplayPosition::BOTTOM;
343+
cursorOffset = rotatedCursorPosition.x;
344+
} else {
345+
sourceBoundary = DisplayPosition::TOP;
346+
cursorOffset = rotatedCursorPosition.x;
347+
}
348+
316349
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
350+
std::optional<std::pair<const DisplayViewport*, float /*offset*/>> destination =
351+
findDestinationDisplayLocked(sourceDisplayId, sourceBoundary, cursorOffset);
352+
if (!destination.has_value()) {
353+
// No matching adjacent display
322354
return;
323355
}
324356

325-
const DisplayViewport* destinationViewport = *destination;
326-
327-
if (mMousePointersByDisplay.find(destinationViewport->displayId) !=
357+
const DisplayViewport& destinationViewport = *destination->first;
358+
const float destinationOffset = destination->second;
359+
if (mMousePointersByDisplay.find(destinationViewport.displayId) !=
328360
mMousePointersByDisplay.end()) {
329361
LOG(FATAL) << "A cursor already exists on destination display"
330-
<< destinationViewport->displayId;
362+
<< destinationViewport.displayId;
331363
}
332-
mDefaultMouseDisplayId = destinationViewport->displayId;
364+
mDefaultMouseDisplayId = destinationViewport.displayId;
333365
auto pcNode = mMousePointersByDisplay.extract(sourceDisplayId);
334-
pcNode.key() = destinationViewport->displayId;
366+
pcNode.key() = destinationViewport.displayId;
335367
mMousePointersByDisplay.insert(std::move(pcNode));
336368

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);
369+
// Before updating the viewport and moving the cursor to appropriate location in the destination
370+
// viewport, we need to temporarily hide the cursor. This will prevent it from appearing at the
371+
// center of the display in any intermediate frames.
372+
pc.fade(PointerControllerInterface::Transition::IMMEDIATE);
373+
pc.setDisplayViewport(destinationViewport);
374+
vec2 destinationPosition =
375+
calculateDestinationPosition(destinationViewport, cursorOffset - destinationOffset,
376+
sourceBoundary);
377+
378+
// Transform position back to un-rotated coordinate space before sending it to controller
379+
destinationPosition = pc.getDisplayTransform().inverse().transform(destinationPosition.x,
380+
destinationPosition.y);
381+
pc.setPosition(destinationPosition.x, destinationPosition.y);
382+
pc.unfade(PointerControllerInterface::Transition::IMMEDIATE);
383+
}
384+
385+
vec2 PointerChoreographer::calculateDestinationPosition(const DisplayViewport& destinationViewport,
386+
float pointerOffset,
387+
DisplayPosition sourceBoundary) {
388+
// destination is opposite of the source boundary
389+
switch (sourceBoundary) {
390+
case DisplayPosition::RIGHT:
391+
return {0, pointerOffset}; // left edge
392+
case DisplayPosition::TOP:
393+
return {pointerOffset, destinationViewport.logicalBottom}; // bottom edge
394+
case DisplayPosition::LEFT:
395+
return {destinationViewport.logicalRight, pointerOffset}; // right edge
396+
case DisplayPosition::BOTTOM:
397+
return {pointerOffset, 0}; // top edge
398+
}
340399
}
341400

342401
void PointerChoreographer::processDrawingTabletEventLocked(const android::NotifyMotionArgs& args) {
@@ -951,10 +1010,11 @@ void PointerChoreographer::populateFakeDisplayTopologyLocked(
9511010

9521011
// create a fake topology assuming following order
9531012
// default-display (top-edge) -> next-display (right-edge) -> next-display (right-edge) ...
954-
// ┌─────────┬─────────┐
955-
// │ next │ next 2 │ ...
956-
// ├─────────┼─────────┘
957-
// │ default │
1013+
// This also adds a 100px offset on corresponding edge for better manual testing
1014+
// ┌────────┐
1015+
// │ next ├─────────┐
1016+
// ┌─└───────┐┤ next 2 │ ...
1017+
// │ default │└─────────┘
9581018
// └─────────┘
9591019
mTopology.clear();
9601020

@@ -965,13 +1025,15 @@ void PointerChoreographer::populateFakeDisplayTopologyLocked(
9651025
continue;
9661026
}
9671027
if (previousDisplay == ui::LogicalDisplayId::DEFAULT) {
968-
mTopology[previousDisplay].push_back({displayInfo.displayId, DisplayPosition::TOP, 0});
1028+
mTopology[previousDisplay].push_back(
1029+
{displayInfo.displayId, DisplayPosition::TOP, 100});
9691030
mTopology[displayInfo.displayId].push_back(
970-
{previousDisplay, DisplayPosition::BOTTOM, 0});
1031+
{previousDisplay, DisplayPosition::BOTTOM, -100});
9711032
} else {
9721033
mTopology[previousDisplay].push_back(
973-
{displayInfo.displayId, DisplayPosition::RIGHT, 0});
974-
mTopology[displayInfo.displayId].push_back({previousDisplay, DisplayPosition::LEFT, 0});
1034+
{displayInfo.displayId, DisplayPosition::RIGHT, 100});
1035+
mTopology[displayInfo.displayId].push_back(
1036+
{previousDisplay, DisplayPosition::LEFT, -100});
9751037
}
9761038
previousDisplay = displayInfo.displayId;
9771039
}
@@ -982,34 +1044,17 @@ void PointerChoreographer::populateFakeDisplayTopologyLocked(
9821044
}
9831045
}
9841046

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()) {
1047+
std::optional<std::pair<const DisplayViewport*, float /*offset*/>>
1048+
PointerChoreographer::findDestinationDisplayLocked(const ui::LogicalDisplayId sourceDisplayId,
1049+
const DisplayPosition sourceBoundary,
1050+
float cursorOffset) const {
1051+
const auto& sourceNode = mTopology.find(sourceDisplayId);
1052+
if (sourceNode == mTopology.end()) {
10071053
// 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;
1054+
LOG(WARNING) << "Source display missing from topology " << sourceDisplayId;
10091055
return std::nullopt;
10101056
}
1011-
1012-
for (const auto& adjacentDisplay : destination->second) {
1057+
for (const AdjacentDisplay& adjacentDisplay : sourceNode->second) {
10131058
if (adjacentDisplay.position != sourceBoundary) {
10141059
continue;
10151060
}
@@ -1018,11 +1063,18 @@ std::optional<const DisplayViewport*> PointerChoreographer::findDestinationDispl
10181063
if (destinationViewport == nullptr) {
10191064
// Topology is likely out of sync with viewport info, wait for them to be updated
10201065
LOG(WARNING) << "Cannot find viewport for adjacent display "
1021-
<< adjacentDisplay.displayId << "of source display "
1022-
<< sourceViewport.displayId;
1023-
break;
1066+
<< adjacentDisplay.displayId << "of source display " << sourceDisplayId;
1067+
continue;
1068+
}
1069+
// target position must be within target display boundary
1070+
const int32_t edgeSize =
1071+
sourceBoundary == DisplayPosition::TOP || sourceBoundary == DisplayPosition::BOTTOM
1072+
? (destinationViewport->logicalRight - destinationViewport->logicalLeft)
1073+
: (destinationViewport->logicalBottom - destinationViewport->logicalTop);
1074+
if (cursorOffset >= adjacentDisplay.offsetPx &&
1075+
cursorOffset <= adjacentDisplay.offsetPx + edgeSize) {
1076+
return std::make_pair(destinationViewport, adjacentDisplay.offsetPx);
10241077
}
1025-
return destinationViewport;
10261078
}
10271079
return std::nullopt;
10281080
}

services/inputflinger/PointerChoreographer.h

Lines changed: 11 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -114,11 +114,11 @@ class PointerChoreographer : public PointerChoreographerInterface {
114114
void notifyPointerCaptureChanged(const NotifyPointerCaptureChangedArgs& args) override;
115115

116116
// 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,
117+
enum class DisplayPosition {
118+
RIGHT,
119+
TOP,
120+
LEFT,
121+
BOTTOM,
122122
ftl_last = BOTTOM,
123123
};
124124

@@ -177,9 +177,12 @@ class PointerChoreographer : public PointerChoreographerInterface {
177177
void populateFakeDisplayTopologyLocked(const std::vector<gui::DisplayInfo>& displayInfos)
178178
REQUIRES(getLock());
179179

180-
std::optional<const DisplayViewport*> findDestinationDisplayLocked(
181-
const DisplayViewport& sourceViewport, const FloatPoint& unconsumedDelta) const
182-
REQUIRES(getLock());
180+
std::optional<std::pair<const DisplayViewport*, float /*offset*/>> findDestinationDisplayLocked(
181+
const ui::LogicalDisplayId sourceDisplayId, const DisplayPosition sourceBoundary,
182+
float cursorOffset) const REQUIRES(getLock());
183+
184+
static vec2 calculateDestinationPosition(const DisplayViewport& destinationViewport,
185+
float pointerOffset, DisplayPosition sourceBoundary);
183186

184187
std::unordered_map<ui::LogicalDisplayId, std::vector<AdjacentDisplay>> mTopology
185188
GUARDED_BY(getLock());

services/inputflinger/include/PointerControllerInterface.h

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -73,7 +73,7 @@ class PointerControllerInterface {
7373
virtual std::string dump() = 0;
7474

7575
/* Move the pointer and return unconsumed delta if the pointer has crossed the current
76-
* viewport bounds .
76+
* viewport bounds.
7777
*
7878
* Return value may be used to move pointer to corresponding adjacent display, if it exists in
7979
* the display-topology */
@@ -149,6 +149,8 @@ class PointerControllerInterface {
149149

150150
/* Resets the flag to skip screenshot of the pointer indicators for all displays. */
151151
virtual void clearSkipScreenshotFlags() = 0;
152+
153+
virtual ui::Transform getDisplayTransform() const = 0;
152154
};
153155

154156
} // namespace android

services/inputflinger/tests/FakePointerController.cpp

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -195,4 +195,8 @@ void FakePointerController::clearSpots() {
195195
mSpotsByDisplay.clear();
196196
}
197197

198+
ui::Transform FakePointerController::getDisplayTransform() const {
199+
return ui::Transform();
200+
}
201+
198202
} // namespace android

services/inputflinger/tests/FakePointerController.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,7 @@ class FakePointerController : public PointerControllerInterface {
4848
void setSkipScreenshotFlagForDisplay(ui::LogicalDisplayId displayId) override;
4949
void clearSkipScreenshotFlags() override;
5050
void fade(Transition) override;
51+
ui::Transform getDisplayTransform() const override;
5152

5253
void assertViewportSet(ui::LogicalDisplayId displayId);
5354
void assertViewportNotSet();

0 commit comments

Comments
 (0)