@@ -205,20 +205,30 @@ void PointerChoreographer::fadeMouseCursorOnKeyPress(const android::NotifyKeyArg
205205}
206206
207207NotifyMotionArgs 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
224234NotifyMotionArgs 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+
294342void 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
438486void 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+
505561void 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
8781032void 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
8941050void PointerChoreographer::PointerChoreographerDisplayInfoListener::setInitialDisplayInfosLocked (
0 commit comments