1 /*
2 * Copyright 2023 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17 #define LOG_TAG "PointerChoreographer"
18
19 #include <android-base/logging.h>
20 #include <com_android_input_flags.h>
21 #if defined(__ANDROID__)
22 #include <gui/SurfaceComposerClient.h>
23 #endif
24 #include <input/Keyboard.h>
25 #include <input/PrintTools.h>
26 #include <unordered_set>
27
28 #include "PointerChoreographer.h"
29
30 #define INDENT " "
31
32 namespace android {
33
34 namespace {
35
isFromMouse(const NotifyMotionArgs & args)36 bool isFromMouse(const NotifyMotionArgs& args) {
37 return isFromSource(args.source, AINPUT_SOURCE_MOUSE) &&
38 args.pointerProperties[0].toolType == ToolType::MOUSE;
39 }
40
isFromTouchpad(const NotifyMotionArgs & args)41 bool isFromTouchpad(const NotifyMotionArgs& args) {
42 return isFromSource(args.source, AINPUT_SOURCE_MOUSE) &&
43 args.pointerProperties[0].toolType == ToolType::FINGER;
44 }
45
isFromDrawingTablet(const NotifyMotionArgs & args)46 bool isFromDrawingTablet(const NotifyMotionArgs& args) {
47 return isFromSource(args.source, AINPUT_SOURCE_MOUSE | AINPUT_SOURCE_STYLUS) &&
48 isStylusToolType(args.pointerProperties[0].toolType);
49 }
50
isHoverAction(int32_t action)51 bool isHoverAction(int32_t action) {
52 return action == AMOTION_EVENT_ACTION_HOVER_ENTER ||
53 action == AMOTION_EVENT_ACTION_HOVER_MOVE || action == AMOTION_EVENT_ACTION_HOVER_EXIT;
54 }
55
isStylusHoverEvent(const NotifyMotionArgs & args)56 bool isStylusHoverEvent(const NotifyMotionArgs& args) {
57 return isStylusEvent(args.source, args.pointerProperties) && isHoverAction(args.action);
58 }
59
isMouseOrTouchpad(uint32_t sources)60 bool isMouseOrTouchpad(uint32_t sources) {
61 // Check if this is a mouse or touchpad, but not a drawing tablet.
62 return isFromSource(sources, AINPUT_SOURCE_MOUSE_RELATIVE) ||
63 (isFromSource(sources, AINPUT_SOURCE_MOUSE) &&
64 !isFromSource(sources, AINPUT_SOURCE_STYLUS));
65 }
66
notifyPointerDisplayChange(std::optional<std::tuple<ui::LogicalDisplayId,vec2>> change,PointerChoreographerPolicyInterface & policy)67 inline void notifyPointerDisplayChange(std::optional<std::tuple<ui::LogicalDisplayId, vec2>> change,
68 PointerChoreographerPolicyInterface& policy) {
69 if (!change) {
70 return;
71 }
72 const auto& [displayId, cursorPosition] = *change;
73 policy.notifyPointerDisplayIdChanged(displayId, cursorPosition);
74 }
75
setIconForController(const std::variant<std::unique_ptr<SpriteIcon>,PointerIconStyle> & icon,PointerControllerInterface & controller)76 void setIconForController(const std::variant<std::unique_ptr<SpriteIcon>, PointerIconStyle>& icon,
77 PointerControllerInterface& controller) {
78 if (std::holds_alternative<std::unique_ptr<SpriteIcon>>(icon)) {
79 if (std::get<std::unique_ptr<SpriteIcon>>(icon) == nullptr) {
80 LOG(FATAL) << "SpriteIcon should not be null";
81 }
82 controller.setCustomPointerIcon(*std::get<std::unique_ptr<SpriteIcon>>(icon));
83 } else {
84 controller.updatePointerIcon(std::get<PointerIconStyle>(icon));
85 }
86 }
87
88 // filters and returns a set of privacy sensitive displays that are currently visible.
getPrivacySensitiveDisplaysFromWindowInfos(const std::vector<gui::WindowInfo> & windowInfos)89 std::unordered_set<ui::LogicalDisplayId> getPrivacySensitiveDisplaysFromWindowInfos(
90 const std::vector<gui::WindowInfo>& windowInfos) {
91 std::unordered_set<ui::LogicalDisplayId> privacySensitiveDisplays;
92 for (const auto& windowInfo : windowInfos) {
93 if (!windowInfo.inputConfig.test(gui::WindowInfo::InputConfig::NOT_VISIBLE) &&
94 windowInfo.inputConfig.test(gui::WindowInfo::InputConfig::SENSITIVE_FOR_PRIVACY)) {
95 privacySensitiveDisplays.insert(windowInfo.displayId);
96 }
97 }
98 return privacySensitiveDisplays;
99 }
100
101 } // namespace
102
103 // --- PointerChoreographer ---
104
PointerChoreographer(InputListenerInterface & inputListener,PointerChoreographerPolicyInterface & policy)105 PointerChoreographer::PointerChoreographer(InputListenerInterface& inputListener,
106 PointerChoreographerPolicyInterface& policy)
107 : PointerChoreographer(
108 inputListener, policy,
109 [](const sp<android::gui::WindowInfosListener>& listener) {
110 auto initialInfo = std::make_pair(std::vector<android::gui::WindowInfo>{},
111 std::vector<android::gui::DisplayInfo>{});
112 #if defined(__ANDROID__)
113 SurfaceComposerClient::getDefault()->addWindowInfosListener(listener,
114 &initialInfo);
115 #endif
116 return initialInfo.first;
117 },
__anon0aae248f0302(const sp<android::gui::WindowInfosListener>& listener) 118 [](const sp<android::gui::WindowInfosListener>& listener) {
119 #if defined(__ANDROID__)
120 SurfaceComposerClient::getDefault()->removeWindowInfosListener(listener);
121 #endif
122 }) {
123 }
124
PointerChoreographer(android::InputListenerInterface & listener,android::PointerChoreographerPolicyInterface & policy,const android::PointerChoreographer::WindowListenerRegisterConsumer & registerListener,const android::PointerChoreographer::WindowListenerUnregisterConsumer & unregisterListener)125 PointerChoreographer::PointerChoreographer(
126 android::InputListenerInterface& listener,
127 android::PointerChoreographerPolicyInterface& policy,
128 const android::PointerChoreographer::WindowListenerRegisterConsumer& registerListener,
129 const android::PointerChoreographer::WindowListenerUnregisterConsumer& unregisterListener)
130 : mTouchControllerConstructor([this]() {
131 return mPolicy.createPointerController(
132 PointerControllerInterface::ControllerType::TOUCH);
133 }),
134 mNextListener(listener),
135 mPolicy(policy),
136 mDefaultMouseDisplayId(ui::LogicalDisplayId::DEFAULT),
137 mNotifiedPointerDisplayId(ui::LogicalDisplayId::INVALID),
138 mShowTouchesEnabled(false),
139 mStylusPointerIconEnabled(false),
140 mCurrentFocusedDisplay(ui::LogicalDisplayId::DEFAULT),
141 mIsWindowInfoListenerRegistered(false),
142 mWindowInfoListener(sp<PointerChoreographerDisplayInfoListener>::make(this)),
143 mRegisterListener(registerListener),
144 mUnregisterListener(unregisterListener) {}
145
~PointerChoreographer()146 PointerChoreographer::~PointerChoreographer() {
147 if (mIsWindowInfoListenerRegistered) {
148 mUnregisterListener(mWindowInfoListener);
149 mIsWindowInfoListenerRegistered = false;
150 }
151 mWindowInfoListener->onPointerChoreographerDestroyed();
152 }
153
notifyInputDevicesChanged(const NotifyInputDevicesChangedArgs & args)154 void PointerChoreographer::notifyInputDevicesChanged(const NotifyInputDevicesChangedArgs& args) {
155 PointerDisplayChange pointerDisplayChange;
156
157 { // acquire lock
158 std::scoped_lock _l(getLock());
159
160 mInputDeviceInfos = args.inputDeviceInfos;
161 pointerDisplayChange = updatePointerControllersLocked();
162 } // release lock
163
164 notifyPointerDisplayChange(pointerDisplayChange, mPolicy);
165 mNextListener.notify(args);
166 }
167
notifyKey(const NotifyKeyArgs & args)168 void PointerChoreographer::notifyKey(const NotifyKeyArgs& args) {
169 fadeMouseCursorOnKeyPress(args);
170 mNextListener.notify(args);
171 }
172
notifyMotion(const NotifyMotionArgs & args)173 void PointerChoreographer::notifyMotion(const NotifyMotionArgs& args) {
174 NotifyMotionArgs newArgs = processMotion(args);
175
176 mNextListener.notify(newArgs);
177 }
178
fadeMouseCursorOnKeyPress(const android::NotifyKeyArgs & args)179 void PointerChoreographer::fadeMouseCursorOnKeyPress(const android::NotifyKeyArgs& args) {
180 if (args.action == AKEY_EVENT_ACTION_UP || isMetaKey(args.keyCode)) {
181 return;
182 }
183 // Meta state for these keys is ignored for dismissing cursor while typing
184 constexpr static int32_t ALLOW_FADING_META_STATE_MASK = AMETA_CAPS_LOCK_ON | AMETA_NUM_LOCK_ON |
185 AMETA_SCROLL_LOCK_ON | AMETA_SHIFT_LEFT_ON | AMETA_SHIFT_RIGHT_ON | AMETA_SHIFT_ON;
186 if (args.metaState & ~ALLOW_FADING_META_STATE_MASK) {
187 // Do not fade if any other meta state is active
188 return;
189 }
190 if (!mPolicy.isInputMethodConnectionActive()) {
191 return;
192 }
193
194 std::scoped_lock _l(getLock());
195 ui::LogicalDisplayId targetDisplay = args.displayId;
196 if (targetDisplay == ui::LogicalDisplayId::INVALID) {
197 targetDisplay = mCurrentFocusedDisplay;
198 }
199 auto it = mMousePointersByDisplay.find(targetDisplay);
200 if (it != mMousePointersByDisplay.end()) {
201 mPolicy.notifyMouseCursorFadedOnTyping();
202 it->second->fade(PointerControllerInterface::Transition::GRADUAL);
203 }
204 }
205
processMotion(const NotifyMotionArgs & args)206 NotifyMotionArgs PointerChoreographer::processMotion(const NotifyMotionArgs& args) {
207 NotifyMotionArgs newArgs(args);
208 PointerDisplayChange pointerDisplayChange;
209 { // acquire lock
210 std::scoped_lock _l(getLock());
211 if (isFromMouse(args)) {
212 newArgs = processMouseEventLocked(args);
213 pointerDisplayChange = calculatePointerDisplayChangeToNotify();
214 } else if (isFromTouchpad(args)) {
215 newArgs = processTouchpadEventLocked(args);
216 pointerDisplayChange = calculatePointerDisplayChangeToNotify();
217 } else if (isFromDrawingTablet(args)) {
218 processDrawingTabletEventLocked(args);
219 } else if (mStylusPointerIconEnabled && isStylusHoverEvent(args)) {
220 processStylusHoverEventLocked(args);
221 } else if (isFromSource(args.source, AINPUT_SOURCE_TOUCHSCREEN)) {
222 processTouchscreenAndStylusEventLocked(args);
223 }
224 } // release lock
225
226 if (pointerDisplayChange) {
227 // pointer display may have changed if mouse crossed display boundary
228 notifyPointerDisplayChange(pointerDisplayChange, mPolicy);
229 }
230 return newArgs;
231 }
232
processMouseEventLocked(const NotifyMotionArgs & args)233 NotifyMotionArgs PointerChoreographer::processMouseEventLocked(const NotifyMotionArgs& args) {
234 if (args.getPointerCount() != 1) {
235 LOG(FATAL) << "Only mouse events with a single pointer are currently supported: "
236 << args.dump();
237 }
238
239 mMouseDevices.emplace(args.deviceId);
240 auto [displayId, pc] = ensureMouseControllerLocked(args.displayId);
241 NotifyMotionArgs newArgs(args);
242 newArgs.displayId = displayId;
243
244 if (MotionEvent::isValidCursorPosition(args.xCursorPosition, args.yCursorPosition)) {
245 // This is an absolute mouse device that knows about the location of the cursor on the
246 // display, so set the cursor position to the specified location.
247 const auto position = pc.getPosition();
248 const float deltaX = args.xCursorPosition - position.x;
249 const float deltaY = args.yCursorPosition - position.y;
250 newArgs.pointerCoords[0].setAxisValue(AMOTION_EVENT_AXIS_RELATIVE_X, deltaX);
251 newArgs.pointerCoords[0].setAxisValue(AMOTION_EVENT_AXIS_RELATIVE_Y, deltaY);
252 pc.setPosition(args.xCursorPosition, args.yCursorPosition);
253 } else {
254 // This is a relative mouse, so move the cursor by the specified amount.
255 processPointerDeviceMotionEventLocked(/*byref*/ newArgs, /*byref*/ pc);
256 }
257 // Note displayId may have changed if the cursor moved to a different display
258 if (canUnfadeOnDisplay(newArgs.displayId)) {
259 pc.unfade(PointerControllerInterface::Transition::IMMEDIATE);
260 }
261 return newArgs;
262 }
263
processTouchpadEventLocked(const NotifyMotionArgs & args)264 NotifyMotionArgs PointerChoreographer::processTouchpadEventLocked(const NotifyMotionArgs& args) {
265 mMouseDevices.emplace(args.deviceId);
266 auto [displayId, pc] = ensureMouseControllerLocked(args.displayId);
267
268 NotifyMotionArgs newArgs(args);
269 newArgs.displayId = displayId;
270 if (args.getPointerCount() == 1 && args.classification == MotionClassification::NONE) {
271 // This is a movement of the mouse pointer.
272 processPointerDeviceMotionEventLocked(/*byref*/ newArgs, /*byref*/ pc);
273 } else {
274 // This is a trackpad gesture with fake finger(s) that should not move the mouse pointer.
275 const auto position = pc.getPosition();
276 for (uint32_t i = 0; i < newArgs.getPointerCount(); i++) {
277 newArgs.pointerCoords[i].setAxisValue(AMOTION_EVENT_AXIS_X,
278 args.pointerCoords[i].getX() + position.x);
279 newArgs.pointerCoords[i].setAxisValue(AMOTION_EVENT_AXIS_Y,
280 args.pointerCoords[i].getY() + position.y);
281 }
282 newArgs.xCursorPosition = position.x;
283 newArgs.yCursorPosition = position.y;
284 }
285
286 // Note displayId may have changed if the cursor moved to a different display
287 if (canUnfadeOnDisplay(newArgs.displayId)) {
288 pc.unfade(PointerControllerInterface::Transition::IMMEDIATE);
289 }
290 return newArgs;
291 }
292
processPointerDeviceMotionEventLocked(NotifyMotionArgs & newArgs,PointerControllerInterface & pc)293 void PointerChoreographer::processPointerDeviceMotionEventLocked(NotifyMotionArgs& newArgs,
294 PointerControllerInterface& pc) {
295 const float deltaX = newArgs.pointerCoords[0].getAxisValue(AMOTION_EVENT_AXIS_RELATIVE_X);
296 const float deltaY = newArgs.pointerCoords[0].getAxisValue(AMOTION_EVENT_AXIS_RELATIVE_Y);
297
298 vec2 unconsumedDelta = pc.move(deltaX, deltaY);
299 if (com::android::input::flags::connected_displays_cursor() &&
300 (std::abs(unconsumedDelta.x) > 0 || std::abs(unconsumedDelta.y) > 0)) {
301 handleUnconsumedDeltaLocked(pc, unconsumedDelta);
302 // pointer may have moved to a different viewport
303 newArgs.displayId = pc.getDisplayId();
304 }
305
306 const auto position = pc.getPosition();
307 newArgs.pointerCoords[0].setAxisValue(AMOTION_EVENT_AXIS_X, position.x);
308 newArgs.pointerCoords[0].setAxisValue(AMOTION_EVENT_AXIS_Y, position.y);
309 newArgs.xCursorPosition = position.x;
310 newArgs.yCursorPosition = position.y;
311 }
312
handleUnconsumedDeltaLocked(PointerControllerInterface & pc,const vec2 & unconsumedDelta)313 void PointerChoreographer::handleUnconsumedDeltaLocked(PointerControllerInterface& pc,
314 const vec2& unconsumedDelta) {
315 // Display topology is in rotated coordinate space and Pointer controller returns and expects
316 // values in the un-rotated coordinate space. So we need to transform delta and cursor position
317 // back to the rotated coordinate space to lookup adjacent display in the display topology.
318 const auto& sourceDisplayTransform = pc.getDisplayTransform();
319 const vec2 rotatedUnconsumedDelta =
320 transformWithoutTranslation(sourceDisplayTransform, unconsumedDelta);
321 const vec2 cursorPosition = pc.getPosition();
322 const vec2 rotatedCursorPosition = sourceDisplayTransform.transform(cursorPosition);
323
324 // To find out the boundary that cursor is crossing we are checking delta in x and y direction
325 // respectively. This prioritizes x direction over y.
326 // In practise, majority of cases we only have non-zero values in either x or y coordinates,
327 // except sometimes near the corners.
328 // In these cases this behaviour is not noticeable. We also do not apply unconsumed delta on
329 // the destination display for the same reason.
330 DisplayPosition sourceBoundary;
331 float cursorOffset = 0.0f;
332 if (rotatedUnconsumedDelta.x > 0) {
333 sourceBoundary = DisplayPosition::RIGHT;
334 cursorOffset = rotatedCursorPosition.y;
335 } else if (rotatedUnconsumedDelta.x < 0) {
336 sourceBoundary = DisplayPosition::LEFT;
337 cursorOffset = rotatedCursorPosition.y;
338 } else if (rotatedUnconsumedDelta.y > 0) {
339 sourceBoundary = DisplayPosition::BOTTOM;
340 cursorOffset = rotatedCursorPosition.x;
341 } else {
342 sourceBoundary = DisplayPosition::TOP;
343 cursorOffset = rotatedCursorPosition.x;
344 }
345
346 const ui::LogicalDisplayId sourceDisplayId = pc.getDisplayId();
347 std::optional<std::pair<const DisplayViewport*, float /*offset*/>> destination =
348 findDestinationDisplayLocked(sourceDisplayId, sourceBoundary, cursorOffset);
349 if (!destination.has_value()) {
350 // No matching adjacent display
351 return;
352 }
353
354 const DisplayViewport& destinationViewport = *destination->first;
355 const float destinationOffset = destination->second;
356 if (mMousePointersByDisplay.find(destinationViewport.displayId) !=
357 mMousePointersByDisplay.end()) {
358 LOG(FATAL) << "A cursor already exists on destination display"
359 << destinationViewport.displayId;
360 }
361 mDefaultMouseDisplayId = destinationViewport.displayId;
362 auto pcNode = mMousePointersByDisplay.extract(sourceDisplayId);
363 pcNode.key() = destinationViewport.displayId;
364 mMousePointersByDisplay.insert(std::move(pcNode));
365
366 // Before updating the viewport and moving the cursor to appropriate location in the destination
367 // viewport, we need to temporarily hide the cursor. This will prevent it from appearing at the
368 // center of the display in any intermediate frames.
369 pc.fade(PointerControllerInterface::Transition::IMMEDIATE);
370 pc.setDisplayViewport(destinationViewport);
371 vec2 destinationPosition =
372 calculateDestinationPosition(destinationViewport, cursorOffset - destinationOffset,
373 sourceBoundary);
374
375 // Transform position back to un-rotated coordinate space before sending it to controller
376 destinationPosition = pc.getDisplayTransform().inverse().transform(destinationPosition.x,
377 destinationPosition.y);
378 pc.setPosition(destinationPosition.x, destinationPosition.y);
379 pc.unfade(PointerControllerInterface::Transition::IMMEDIATE);
380 }
381
calculateDestinationPosition(const DisplayViewport & destinationViewport,float pointerOffset,DisplayPosition sourceBoundary)382 vec2 PointerChoreographer::calculateDestinationPosition(const DisplayViewport& destinationViewport,
383 float pointerOffset,
384 DisplayPosition sourceBoundary) {
385 // destination is opposite of the source boundary
386 switch (sourceBoundary) {
387 case DisplayPosition::RIGHT:
388 return {0, pointerOffset}; // left edge
389 case DisplayPosition::TOP:
390 return {pointerOffset, destinationViewport.logicalBottom}; // bottom edge
391 case DisplayPosition::LEFT:
392 return {destinationViewport.logicalRight, pointerOffset}; // right edge
393 case DisplayPosition::BOTTOM:
394 return {pointerOffset, 0}; // top edge
395 }
396 }
397
processDrawingTabletEventLocked(const android::NotifyMotionArgs & args)398 void PointerChoreographer::processDrawingTabletEventLocked(const android::NotifyMotionArgs& args) {
399 if (args.displayId == ui::LogicalDisplayId::INVALID) {
400 return;
401 }
402
403 if (args.getPointerCount() != 1) {
404 LOG(WARNING) << "Only drawing tablet events with a single pointer are currently supported: "
405 << args.dump();
406 }
407
408 // Use a mouse pointer controller for drawing tablets, or create one if it doesn't exist.
409 auto [it, controllerAdded] =
410 mDrawingTabletPointersByDevice.try_emplace(args.deviceId,
411 getMouseControllerConstructor(
412 args.displayId));
413 if (controllerAdded) {
414 onControllerAddedOrRemovedLocked();
415 }
416
417 PointerControllerInterface& pc = *it->second;
418
419 const float x = args.pointerCoords[0].getAxisValue(AMOTION_EVENT_AXIS_X);
420 const float y = args.pointerCoords[0].getAxisValue(AMOTION_EVENT_AXIS_Y);
421 pc.setPosition(x, y);
422 if (args.action == AMOTION_EVENT_ACTION_HOVER_EXIT) {
423 // TODO(b/315815559): Do not fade and reset the icon if the hover exit will be followed
424 // immediately by a DOWN event.
425 pc.fade(PointerControllerInterface::Transition::IMMEDIATE);
426 pc.updatePointerIcon(PointerIconStyle::TYPE_NOT_SPECIFIED);
427 } else if (canUnfadeOnDisplay(args.displayId)) {
428 pc.unfade(PointerControllerInterface::Transition::IMMEDIATE);
429 }
430 }
431
432 /**
433 * When screen is touched, fade the mouse pointer on that display. We only call fade for
434 * ACTION_DOWN events.This would allow both mouse and touch to be used at the same time if the
435 * mouse device keeps moving and unfades the cursor.
436 * For touch events, we do not need to populate the cursor position.
437 */
processTouchscreenAndStylusEventLocked(const NotifyMotionArgs & args)438 void PointerChoreographer::processTouchscreenAndStylusEventLocked(const NotifyMotionArgs& args) {
439 if (!args.displayId.isValid()) {
440 return;
441 }
442
443 if (const auto it = mMousePointersByDisplay.find(args.displayId);
444 it != mMousePointersByDisplay.end() && args.action == AMOTION_EVENT_ACTION_DOWN) {
445 it->second->fade(PointerControllerInterface::Transition::GRADUAL);
446 }
447
448 if (!mShowTouchesEnabled) {
449 return;
450 }
451
452 // Get the touch pointer controller for the device, or create one if it doesn't exist.
453 auto [it, controllerAdded] =
454 mTouchPointersByDevice.try_emplace(args.deviceId, mTouchControllerConstructor);
455 if (controllerAdded) {
456 onControllerAddedOrRemovedLocked();
457 }
458
459 PointerControllerInterface& pc = *it->second;
460
461 const PointerCoords* coords = args.pointerCoords.data();
462 const int32_t maskedAction = MotionEvent::getActionMasked(args.action);
463 const uint8_t actionIndex = MotionEvent::getActionIndex(args.action);
464 std::array<uint32_t, MAX_POINTER_ID + 1> idToIndex;
465 BitSet32 idBits;
466 if (maskedAction != AMOTION_EVENT_ACTION_UP && maskedAction != AMOTION_EVENT_ACTION_CANCEL &&
467 maskedAction != AMOTION_EVENT_ACTION_HOVER_EXIT) {
468 for (size_t i = 0; i < args.getPointerCount(); i++) {
469 if (maskedAction == AMOTION_EVENT_ACTION_POINTER_UP && actionIndex == i) {
470 continue;
471 }
472 uint32_t id = args.pointerProperties[i].id;
473 idToIndex[id] = i;
474 idBits.markBit(id);
475 }
476 }
477 // The PointerController already handles setting spots per-display, so
478 // we do not need to manually manage display changes for touch spots for now.
479 pc.setSpots(coords, idToIndex.cbegin(), idBits, args.displayId);
480 }
481
processStylusHoverEventLocked(const NotifyMotionArgs & args)482 void PointerChoreographer::processStylusHoverEventLocked(const NotifyMotionArgs& args) {
483 if (!args.displayId.isValid()) {
484 return;
485 }
486
487 if (args.getPointerCount() != 1) {
488 LOG(WARNING) << "Only stylus hover events with a single pointer are currently supported: "
489 << args.dump();
490 }
491
492 // Get the stylus pointer controller for the device, or create one if it doesn't exist.
493 auto [it, controllerAdded] =
494 mStylusPointersByDevice.try_emplace(args.deviceId,
495 getStylusControllerConstructor(args.displayId));
496 if (controllerAdded) {
497 onControllerAddedOrRemovedLocked();
498 }
499
500 PointerControllerInterface& pc = *it->second;
501
502 const float x = args.pointerCoords[0].getAxisValue(AMOTION_EVENT_AXIS_X);
503 const float y = args.pointerCoords[0].getAxisValue(AMOTION_EVENT_AXIS_Y);
504 pc.setPosition(x, y);
505 if (args.action == AMOTION_EVENT_ACTION_HOVER_EXIT) {
506 // TODO(b/315815559): Do not fade and reset the icon if the hover exit will be followed
507 // immediately by a DOWN event.
508 pc.fade(PointerControllerInterface::Transition::IMMEDIATE);
509 pc.updatePointerIcon(mShowTouchesEnabled ? PointerIconStyle::TYPE_SPOT_HOVER
510 : PointerIconStyle::TYPE_NOT_SPECIFIED);
511 } else if (canUnfadeOnDisplay(args.displayId)) {
512 pc.unfade(PointerControllerInterface::Transition::IMMEDIATE);
513 }
514 }
515
notifySwitch(const NotifySwitchArgs & args)516 void PointerChoreographer::notifySwitch(const NotifySwitchArgs& args) {
517 mNextListener.notify(args);
518 }
519
notifySensor(const NotifySensorArgs & args)520 void PointerChoreographer::notifySensor(const NotifySensorArgs& args) {
521 mNextListener.notify(args);
522 }
523
notifyVibratorState(const NotifyVibratorStateArgs & args)524 void PointerChoreographer::notifyVibratorState(const NotifyVibratorStateArgs& args) {
525 mNextListener.notify(args);
526 }
527
notifyDeviceReset(const NotifyDeviceResetArgs & args)528 void PointerChoreographer::notifyDeviceReset(const NotifyDeviceResetArgs& args) {
529 processDeviceReset(args);
530
531 mNextListener.notify(args);
532 }
533
processDeviceReset(const NotifyDeviceResetArgs & args)534 void PointerChoreographer::processDeviceReset(const NotifyDeviceResetArgs& args) {
535 std::scoped_lock _l(getLock());
536 mTouchPointersByDevice.erase(args.deviceId);
537 mStylusPointersByDevice.erase(args.deviceId);
538 mDrawingTabletPointersByDevice.erase(args.deviceId);
539 onControllerAddedOrRemovedLocked();
540 }
541
onControllerAddedOrRemovedLocked()542 void PointerChoreographer::onControllerAddedOrRemovedLocked() {
543 if (!com::android::input::flags::hide_pointer_indicators_for_secure_windows() &&
544 !com::android::input::flags::connected_displays_cursor()) {
545 return;
546 }
547 bool requireListener = !mTouchPointersByDevice.empty() || !mMousePointersByDisplay.empty() ||
548 !mDrawingTabletPointersByDevice.empty() || !mStylusPointersByDevice.empty();
549
550 // PointerChoreographer uses Listener's lock which is already held by caller
551 base::ScopedLockAssertion assumeLocked(mWindowInfoListener->mLock);
552
553 if (requireListener && !mIsWindowInfoListenerRegistered) {
554 mIsWindowInfoListenerRegistered = true;
555 mWindowInfoListener->setInitialDisplayInfosLocked(mRegisterListener(mWindowInfoListener));
556 onPrivacySensitiveDisplaysChangedLocked(
557 mWindowInfoListener->getPrivacySensitiveDisplaysLocked());
558 } else if (!requireListener && mIsWindowInfoListenerRegistered) {
559 mIsWindowInfoListenerRegistered = false;
560 mUnregisterListener(mWindowInfoListener);
561 } else if (requireListener) {
562 // controller may have been added to an existing privacy sensitive display, we need to
563 // update all controllers again
564 onPrivacySensitiveDisplaysChangedLocked(
565 mWindowInfoListener->getPrivacySensitiveDisplaysLocked());
566 }
567 }
568
onPrivacySensitiveDisplaysChangedLocked(const std::unordered_set<ui::LogicalDisplayId> & privacySensitiveDisplays)569 void PointerChoreographer::onPrivacySensitiveDisplaysChangedLocked(
570 const std::unordered_set<ui::LogicalDisplayId>& privacySensitiveDisplays) {
571 for (auto& [_, pc] : mTouchPointersByDevice) {
572 pc->clearSkipScreenshotFlags();
573 for (auto displayId : privacySensitiveDisplays) {
574 pc->setSkipScreenshotFlagForDisplay(displayId);
575 }
576 }
577
578 for (auto& [displayId, pc] : mMousePointersByDisplay) {
579 if (privacySensitiveDisplays.find(displayId) != privacySensitiveDisplays.end()) {
580 pc->setSkipScreenshotFlagForDisplay(displayId);
581 } else {
582 pc->clearSkipScreenshotFlags();
583 }
584 }
585
586 for (auto* pointerControllerByDevice :
587 {&mDrawingTabletPointersByDevice, &mStylusPointersByDevice}) {
588 for (auto& [_, pc] : *pointerControllerByDevice) {
589 auto displayId = pc->getDisplayId();
590 if (privacySensitiveDisplays.find(displayId) != privacySensitiveDisplays.end()) {
591 pc->setSkipScreenshotFlagForDisplay(displayId);
592 } else {
593 pc->clearSkipScreenshotFlags();
594 }
595 }
596 }
597 }
598
notifyPointerCaptureChanged(const NotifyPointerCaptureChangedArgs & args)599 void PointerChoreographer::notifyPointerCaptureChanged(
600 const NotifyPointerCaptureChangedArgs& args) {
601 if (args.request.isEnable()) {
602 std::scoped_lock _l(getLock());
603 for (const auto& [_, mousePointerController] : mMousePointersByDisplay) {
604 mousePointerController->fade(PointerControllerInterface::Transition::IMMEDIATE);
605 }
606 }
607 mNextListener.notify(args);
608 }
609
setDisplayTopology(const std::unordered_map<ui::LogicalDisplayId,std::vector<AdjacentDisplay>> & displayTopology)610 void PointerChoreographer::setDisplayTopology(
611 const std::unordered_map<ui::LogicalDisplayId, std::vector<AdjacentDisplay>>&
612 displayTopology) {
613 std::scoped_lock _l(getLock());
614 mTopology = displayTopology;
615 }
616
dump(std::string & dump)617 void PointerChoreographer::dump(std::string& dump) {
618 std::scoped_lock _l(getLock());
619
620 dump += "PointerChoreographer:\n";
621 dump += StringPrintf(INDENT "Show Touches Enabled: %s\n",
622 mShowTouchesEnabled ? "true" : "false");
623 dump += StringPrintf(INDENT "Stylus PointerIcon Enabled: %s\n",
624 mStylusPointerIconEnabled ? "true" : "false");
625
626 dump += INDENT "MousePointerControllers:\n";
627 for (const auto& [displayId, mousePointerController] : mMousePointersByDisplay) {
628 std::string pointerControllerDump = addLinePrefix(mousePointerController->dump(), INDENT);
629 dump += INDENT + displayId.toString() + " : " + pointerControllerDump;
630 }
631 dump += INDENT "TouchPointerControllers:\n";
632 for (const auto& [deviceId, touchPointerController] : mTouchPointersByDevice) {
633 std::string pointerControllerDump = addLinePrefix(touchPointerController->dump(), INDENT);
634 dump += INDENT + std::to_string(deviceId) + " : " + pointerControllerDump;
635 }
636 dump += INDENT "StylusPointerControllers:\n";
637 for (const auto& [deviceId, stylusPointerController] : mStylusPointersByDevice) {
638 std::string pointerControllerDump = addLinePrefix(stylusPointerController->dump(), INDENT);
639 dump += INDENT + std::to_string(deviceId) + " : " + pointerControllerDump;
640 }
641 dump += INDENT "DrawingTabletControllers:\n";
642 for (const auto& [deviceId, drawingTabletController] : mDrawingTabletPointersByDevice) {
643 std::string pointerControllerDump = addLinePrefix(drawingTabletController->dump(), INDENT);
644 dump += INDENT + std::to_string(deviceId) + " : " + pointerControllerDump;
645 }
646 dump += "\n";
647 }
648
findViewportByIdLocked(ui::LogicalDisplayId displayId) const649 const DisplayViewport* PointerChoreographer::findViewportByIdLocked(
650 ui::LogicalDisplayId displayId) const {
651 for (auto& viewport : mViewports) {
652 if (viewport.displayId == displayId) {
653 return &viewport;
654 }
655 }
656 return nullptr;
657 }
658
getTargetMouseDisplayLocked(ui::LogicalDisplayId associatedDisplayId) const659 ui::LogicalDisplayId PointerChoreographer::getTargetMouseDisplayLocked(
660 ui::LogicalDisplayId associatedDisplayId) const {
661 return associatedDisplayId.isValid() ? associatedDisplayId : mDefaultMouseDisplayId;
662 }
663
664 std::pair<ui::LogicalDisplayId, PointerControllerInterface&>
ensureMouseControllerLocked(ui::LogicalDisplayId associatedDisplayId)665 PointerChoreographer::ensureMouseControllerLocked(ui::LogicalDisplayId associatedDisplayId) {
666 const ui::LogicalDisplayId displayId = getTargetMouseDisplayLocked(associatedDisplayId);
667
668 auto it = mMousePointersByDisplay.find(displayId);
669 if (it == mMousePointersByDisplay.end()) {
670 it = mMousePointersByDisplay.emplace(displayId, getMouseControllerConstructor(displayId))
671 .first;
672 onControllerAddedOrRemovedLocked();
673 }
674
675 return {displayId, *it->second};
676 }
677
findInputDeviceLocked(DeviceId deviceId)678 InputDeviceInfo* PointerChoreographer::findInputDeviceLocked(DeviceId deviceId) {
679 auto it = std::find_if(mInputDeviceInfos.begin(), mInputDeviceInfos.end(),
680 [deviceId](const auto& info) { return info.getId() == deviceId; });
681 return it != mInputDeviceInfos.end() ? &(*it) : nullptr;
682 }
683
canUnfadeOnDisplay(ui::LogicalDisplayId displayId)684 bool PointerChoreographer::canUnfadeOnDisplay(ui::LogicalDisplayId displayId) {
685 return mDisplaysWithPointersHidden.find(displayId) == mDisplaysWithPointersHidden.end();
686 }
687
getLock() const688 std::mutex& PointerChoreographer::getLock() const {
689 return mWindowInfoListener->mLock;
690 }
691
updatePointerControllersLocked()692 PointerChoreographer::PointerDisplayChange PointerChoreographer::updatePointerControllersLocked() {
693 std::set<ui::LogicalDisplayId /*displayId*/> mouseDisplaysToKeep;
694 std::set<DeviceId> touchDevicesToKeep;
695 std::set<DeviceId> stylusDevicesToKeep;
696 std::set<DeviceId> drawingTabletDevicesToKeep;
697
698 // Mark the displayIds or deviceIds of PointerControllers currently needed, and create
699 // new PointerControllers if necessary.
700 for (const auto& info : mInputDeviceInfos) {
701 if (!info.isEnabled()) {
702 // If device is disabled, we should not keep it, and should not show pointer for
703 // disabled mouse device.
704 continue;
705 }
706 const uint32_t sources = info.getSources();
707 const bool isKnownMouse = mMouseDevices.count(info.getId()) != 0;
708
709 if (isMouseOrTouchpad(sources) || isKnownMouse) {
710 const ui::LogicalDisplayId displayId =
711 getTargetMouseDisplayLocked(info.getAssociatedDisplayId());
712 mouseDisplaysToKeep.insert(displayId);
713 // For mice, show the cursor immediately when the device is first connected or
714 // when it moves to a new display.
715 auto [mousePointerIt, isNewMousePointer] =
716 mMousePointersByDisplay.try_emplace(displayId,
717 getMouseControllerConstructor(displayId));
718 if (isNewMousePointer) {
719 onControllerAddedOrRemovedLocked();
720 }
721
722 mMouseDevices.emplace(info.getId());
723 if ((!isKnownMouse || isNewMousePointer) && canUnfadeOnDisplay(displayId)) {
724 mousePointerIt->second->unfade(PointerControllerInterface::Transition::IMMEDIATE);
725 }
726 }
727 if (isFromSource(sources, AINPUT_SOURCE_TOUCHSCREEN) && mShowTouchesEnabled &&
728 info.getAssociatedDisplayId().isValid()) {
729 touchDevicesToKeep.insert(info.getId());
730 }
731 if (isFromSource(sources, AINPUT_SOURCE_STYLUS) && mStylusPointerIconEnabled &&
732 info.getAssociatedDisplayId().isValid()) {
733 stylusDevicesToKeep.insert(info.getId());
734 }
735 if (isFromSource(sources, AINPUT_SOURCE_STYLUS | AINPUT_SOURCE_MOUSE) &&
736 info.getAssociatedDisplayId().isValid()) {
737 drawingTabletDevicesToKeep.insert(info.getId());
738 }
739 }
740
741 // Remove PointerControllers no longer needed.
742 std::erase_if(mMousePointersByDisplay, [&mouseDisplaysToKeep](const auto& pair) {
743 return mouseDisplaysToKeep.find(pair.first) == mouseDisplaysToKeep.end();
744 });
745 std::erase_if(mTouchPointersByDevice, [&touchDevicesToKeep](const auto& pair) {
746 return touchDevicesToKeep.find(pair.first) == touchDevicesToKeep.end();
747 });
748 std::erase_if(mStylusPointersByDevice, [&stylusDevicesToKeep](const auto& pair) {
749 return stylusDevicesToKeep.find(pair.first) == stylusDevicesToKeep.end();
750 });
751 std::erase_if(mDrawingTabletPointersByDevice, [&drawingTabletDevicesToKeep](const auto& pair) {
752 return drawingTabletDevicesToKeep.find(pair.first) == drawingTabletDevicesToKeep.end();
753 });
754 std::erase_if(mMouseDevices, [&](DeviceId id) REQUIRES(getLock()) {
755 return std::find_if(mInputDeviceInfos.begin(), mInputDeviceInfos.end(),
756 [id](const auto& info) { return info.getId() == id; }) ==
757 mInputDeviceInfos.end();
758 });
759
760 onControllerAddedOrRemovedLocked();
761
762 // Check if we need to notify the policy if there's a change on the pointer display ID.
763 return calculatePointerDisplayChangeToNotify();
764 }
765
766 PointerChoreographer::PointerDisplayChange
calculatePointerDisplayChangeToNotify()767 PointerChoreographer::calculatePointerDisplayChangeToNotify() {
768 ui::LogicalDisplayId displayIdToNotify = ui::LogicalDisplayId::INVALID;
769 vec2 cursorPosition = {0, 0};
770 if (const auto it = mMousePointersByDisplay.find(mDefaultMouseDisplayId);
771 it != mMousePointersByDisplay.end()) {
772 const auto& pointerController = it->second;
773 // Use the displayId from the pointerController, because it accurately reflects whether
774 // the viewport has been added for that display. Otherwise, we would have to check if
775 // the viewport exists separately.
776 displayIdToNotify = pointerController->getDisplayId();
777 cursorPosition = pointerController->getPosition();
778 }
779 if (mNotifiedPointerDisplayId == displayIdToNotify) {
780 return {};
781 }
782 mNotifiedPointerDisplayId = displayIdToNotify;
783 return {{displayIdToNotify, cursorPosition}};
784 }
785
setDefaultMouseDisplayId(ui::LogicalDisplayId displayId)786 void PointerChoreographer::setDefaultMouseDisplayId(ui::LogicalDisplayId displayId) {
787 PointerDisplayChange pointerDisplayChange;
788
789 { // acquire lock
790 std::scoped_lock _l(getLock());
791
792 mDefaultMouseDisplayId = displayId;
793 pointerDisplayChange = updatePointerControllersLocked();
794 } // release lock
795
796 notifyPointerDisplayChange(pointerDisplayChange, mPolicy);
797 }
798
setDisplayViewports(const std::vector<DisplayViewport> & viewports)799 void PointerChoreographer::setDisplayViewports(const std::vector<DisplayViewport>& viewports) {
800 PointerDisplayChange pointerDisplayChange;
801
802 { // acquire lock
803 std::scoped_lock _l(getLock());
804 for (const auto& viewport : viewports) {
805 const ui::LogicalDisplayId displayId = viewport.displayId;
806 if (const auto it = mMousePointersByDisplay.find(displayId);
807 it != mMousePointersByDisplay.end()) {
808 it->second->setDisplayViewport(viewport);
809 }
810 for (const auto& [deviceId, stylusPointerController] : mStylusPointersByDevice) {
811 const InputDeviceInfo* info = findInputDeviceLocked(deviceId);
812 if (info && info->getAssociatedDisplayId() == displayId) {
813 stylusPointerController->setDisplayViewport(viewport);
814 }
815 }
816 for (const auto& [deviceId, drawingTabletController] : mDrawingTabletPointersByDevice) {
817 const InputDeviceInfo* info = findInputDeviceLocked(deviceId);
818 if (info && info->getAssociatedDisplayId() == displayId) {
819 drawingTabletController->setDisplayViewport(viewport);
820 }
821 }
822 }
823 mViewports = viewports;
824 pointerDisplayChange = calculatePointerDisplayChangeToNotify();
825 } // release lock
826
827 notifyPointerDisplayChange(pointerDisplayChange, mPolicy);
828 }
829
getViewportForPointerDevice(ui::LogicalDisplayId associatedDisplayId)830 std::optional<DisplayViewport> PointerChoreographer::getViewportForPointerDevice(
831 ui::LogicalDisplayId associatedDisplayId) {
832 std::scoped_lock _l(getLock());
833 const ui::LogicalDisplayId resolvedDisplayId = getTargetMouseDisplayLocked(associatedDisplayId);
834 if (const auto viewport = findViewportByIdLocked(resolvedDisplayId); viewport) {
835 return *viewport;
836 }
837 return std::nullopt;
838 }
839
getMouseCursorPosition(ui::LogicalDisplayId displayId)840 vec2 PointerChoreographer::getMouseCursorPosition(ui::LogicalDisplayId displayId) {
841 std::scoped_lock _l(getLock());
842 const ui::LogicalDisplayId resolvedDisplayId = getTargetMouseDisplayLocked(displayId);
843 if (auto it = mMousePointersByDisplay.find(resolvedDisplayId);
844 it != mMousePointersByDisplay.end()) {
845 return it->second->getPosition();
846 }
847 return {AMOTION_EVENT_INVALID_CURSOR_POSITION, AMOTION_EVENT_INVALID_CURSOR_POSITION};
848 }
849
setShowTouchesEnabled(bool enabled)850 void PointerChoreographer::setShowTouchesEnabled(bool enabled) {
851 PointerDisplayChange pointerDisplayChange;
852
853 { // acquire lock
854 std::scoped_lock _l(getLock());
855 if (mShowTouchesEnabled == enabled) {
856 return;
857 }
858 mShowTouchesEnabled = enabled;
859 pointerDisplayChange = updatePointerControllersLocked();
860 } // release lock
861
862 notifyPointerDisplayChange(pointerDisplayChange, mPolicy);
863 }
864
setStylusPointerIconEnabled(bool enabled)865 void PointerChoreographer::setStylusPointerIconEnabled(bool enabled) {
866 PointerDisplayChange pointerDisplayChange;
867
868 { // acquire lock
869 std::scoped_lock _l(getLock());
870 if (mStylusPointerIconEnabled == enabled) {
871 return;
872 }
873 mStylusPointerIconEnabled = enabled;
874 pointerDisplayChange = updatePointerControllersLocked();
875 } // release lock
876
877 notifyPointerDisplayChange(pointerDisplayChange, mPolicy);
878 }
879
setPointerIcon(std::variant<std::unique_ptr<SpriteIcon>,PointerIconStyle> icon,ui::LogicalDisplayId displayId,DeviceId deviceId)880 bool PointerChoreographer::setPointerIcon(
881 std::variant<std::unique_ptr<SpriteIcon>, PointerIconStyle> icon,
882 ui::LogicalDisplayId displayId, DeviceId deviceId) {
883 std::scoped_lock _l(getLock());
884 if (deviceId < 0) {
885 LOG(WARNING) << "Invalid device id " << deviceId << ". Cannot set pointer icon.";
886 return false;
887 }
888 const InputDeviceInfo* info = findInputDeviceLocked(deviceId);
889 if (!info) {
890 LOG(WARNING) << "No input device info found for id " << deviceId
891 << ". Cannot set pointer icon.";
892 return false;
893 }
894 const uint32_t sources = info->getSources();
895
896 if (isFromSource(sources, AINPUT_SOURCE_STYLUS | AINPUT_SOURCE_MOUSE)) {
897 auto it = mDrawingTabletPointersByDevice.find(deviceId);
898 if (it != mDrawingTabletPointersByDevice.end()) {
899 setIconForController(icon, *it->second);
900 return true;
901 }
902 }
903 if (isFromSource(sources, AINPUT_SOURCE_STYLUS)) {
904 auto it = mStylusPointersByDevice.find(deviceId);
905 if (it != mStylusPointersByDevice.end()) {
906 if (mShowTouchesEnabled) {
907 // If an app doesn't override the icon for the hovering stylus, show the hover icon.
908 auto* style = std::get_if<PointerIconStyle>(&icon);
909 if (style != nullptr && *style == PointerIconStyle::TYPE_NOT_SPECIFIED) {
910 *style = PointerIconStyle::TYPE_SPOT_HOVER;
911 }
912 }
913 setIconForController(icon, *it->second);
914 return true;
915 }
916 }
917 if (isFromSource(sources, AINPUT_SOURCE_MOUSE)) {
918 auto it = mMousePointersByDisplay.find(displayId);
919 if (it != mMousePointersByDisplay.end()) {
920 setIconForController(icon, *it->second);
921 return true;
922 } else {
923 LOG(WARNING) << "No mouse pointer controller found for display " << displayId
924 << ", device " << deviceId << ".";
925 return false;
926 }
927 }
928 LOG(WARNING) << "Cannot set pointer icon for display " << displayId << ", device " << deviceId
929 << ".";
930 return false;
931 }
932
setPointerIconVisibility(ui::LogicalDisplayId displayId,bool visible)933 void PointerChoreographer::setPointerIconVisibility(ui::LogicalDisplayId displayId, bool visible) {
934 std::scoped_lock lock(getLock());
935 if (visible) {
936 mDisplaysWithPointersHidden.erase(displayId);
937 // We do not unfade the icons here, because we don't know when the last event happened.
938 return;
939 }
940
941 mDisplaysWithPointersHidden.emplace(displayId);
942
943 // Hide any icons that are currently visible on the display.
944 if (auto it = mMousePointersByDisplay.find(displayId); it != mMousePointersByDisplay.end()) {
945 const auto& [_, controller] = *it;
946 controller->fade(PointerControllerInterface::Transition::IMMEDIATE);
947 }
948 for (const auto& [_, controller] : mStylusPointersByDevice) {
949 if (controller->getDisplayId() == displayId) {
950 controller->fade(PointerControllerInterface::Transition::IMMEDIATE);
951 }
952 }
953 }
954
setFocusedDisplay(ui::LogicalDisplayId displayId)955 void PointerChoreographer::setFocusedDisplay(ui::LogicalDisplayId displayId) {
956 std::scoped_lock lock(getLock());
957 mCurrentFocusedDisplay = displayId;
958 }
959
getMouseControllerConstructor(ui::LogicalDisplayId displayId)960 PointerChoreographer::ControllerConstructor PointerChoreographer::getMouseControllerConstructor(
961 ui::LogicalDisplayId displayId) {
962 std::function<std::shared_ptr<PointerControllerInterface>()> ctor =
963 [this, displayId]() REQUIRES(getLock()) {
964 auto pc = mPolicy.createPointerController(
965 PointerControllerInterface::ControllerType::MOUSE);
966 if (const auto viewport = findViewportByIdLocked(displayId); viewport) {
967 pc->setDisplayViewport(*viewport);
968 }
969 return pc;
970 };
971 return ConstructorDelegate(std::move(ctor));
972 }
973
getStylusControllerConstructor(ui::LogicalDisplayId displayId)974 PointerChoreographer::ControllerConstructor PointerChoreographer::getStylusControllerConstructor(
975 ui::LogicalDisplayId displayId) {
976 std::function<std::shared_ptr<PointerControllerInterface>()> ctor =
977 [this, displayId]() REQUIRES(getLock()) {
978 auto pc = mPolicy.createPointerController(
979 PointerControllerInterface::ControllerType::STYLUS);
980 if (const auto viewport = findViewportByIdLocked(displayId); viewport) {
981 pc->setDisplayViewport(*viewport);
982 }
983 return pc;
984 };
985 return ConstructorDelegate(std::move(ctor));
986 }
987
populateFakeDisplayTopologyLocked(const std::vector<gui::DisplayInfo> & displayInfos)988 void PointerChoreographer::populateFakeDisplayTopologyLocked(
989 const std::vector<gui::DisplayInfo>& displayInfos) {
990 if (!com::android::input::flags::connected_displays_cursor()) {
991 return;
992 }
993
994 if (displayInfos.size() == mTopology.size()) {
995 bool displaysChanged = false;
996 for (const auto& displayInfo : displayInfos) {
997 if (mTopology.find(displayInfo.displayId) == mTopology.end()) {
998 displaysChanged = true;
999 break;
1000 }
1001 }
1002
1003 if (!displaysChanged) {
1004 return;
1005 }
1006 }
1007
1008 // create a fake topology assuming following order
1009 // default-display (top-edge) -> next-display (right-edge) -> next-display (right-edge) ...
1010 // This also adds a 100px offset on corresponding edge for better manual testing
1011 // ┌────────┐
1012 // │ next ├─────────┐
1013 // ┌─└───────┐┤ next 2 │ ...
1014 // │ default │└─────────┘
1015 // └─────────┘
1016 mTopology.clear();
1017
1018 // treat default display as base, in real topology it should be the primary-display
1019 ui::LogicalDisplayId previousDisplay = ui::LogicalDisplayId::DEFAULT;
1020 for (const auto& displayInfo : displayInfos) {
1021 if (displayInfo.displayId == ui::LogicalDisplayId::DEFAULT) {
1022 continue;
1023 }
1024 if (previousDisplay == ui::LogicalDisplayId::DEFAULT) {
1025 mTopology[previousDisplay].push_back(
1026 {displayInfo.displayId, DisplayPosition::TOP, 100});
1027 mTopology[displayInfo.displayId].push_back(
1028 {previousDisplay, DisplayPosition::BOTTOM, -100});
1029 } else {
1030 mTopology[previousDisplay].push_back(
1031 {displayInfo.displayId, DisplayPosition::RIGHT, 100});
1032 mTopology[displayInfo.displayId].push_back(
1033 {previousDisplay, DisplayPosition::LEFT, -100});
1034 }
1035 previousDisplay = displayInfo.displayId;
1036 }
1037
1038 // update default pointer display. In real topology it should be the primary-display
1039 if (mTopology.find(mDefaultMouseDisplayId) == mTopology.end()) {
1040 mDefaultMouseDisplayId = ui::LogicalDisplayId::DEFAULT;
1041 }
1042 }
1043
1044 std::optional<std::pair<const DisplayViewport*, float /*offset*/>>
findDestinationDisplayLocked(const ui::LogicalDisplayId sourceDisplayId,const DisplayPosition sourceBoundary,float cursorOffset) const1045 PointerChoreographer::findDestinationDisplayLocked(const ui::LogicalDisplayId sourceDisplayId,
1046 const DisplayPosition sourceBoundary,
1047 float cursorOffset) const {
1048 const auto& sourceNode = mTopology.find(sourceDisplayId);
1049 if (sourceNode == mTopology.end()) {
1050 // Topology is likely out of sync with viewport info, wait for it to be updated
1051 LOG(WARNING) << "Source display missing from topology " << sourceDisplayId;
1052 return std::nullopt;
1053 }
1054 for (const AdjacentDisplay& adjacentDisplay : sourceNode->second) {
1055 if (adjacentDisplay.position != sourceBoundary) {
1056 continue;
1057 }
1058 const DisplayViewport* destinationViewport =
1059 findViewportByIdLocked(adjacentDisplay.displayId);
1060 if (destinationViewport == nullptr) {
1061 // Topology is likely out of sync with viewport info, wait for them to be updated
1062 LOG(WARNING) << "Cannot find viewport for adjacent display "
1063 << adjacentDisplay.displayId << "of source display " << sourceDisplayId;
1064 continue;
1065 }
1066 // target position must be within target display boundary
1067 const int32_t edgeSize =
1068 sourceBoundary == DisplayPosition::TOP || sourceBoundary == DisplayPosition::BOTTOM
1069 ? (destinationViewport->logicalRight - destinationViewport->logicalLeft)
1070 : (destinationViewport->logicalBottom - destinationViewport->logicalTop);
1071 if (cursorOffset >= adjacentDisplay.offsetPx &&
1072 cursorOffset <= adjacentDisplay.offsetPx + edgeSize) {
1073 return std::make_pair(destinationViewport, adjacentDisplay.offsetPx);
1074 }
1075 }
1076 return std::nullopt;
1077 }
1078
1079 // --- PointerChoreographer::PointerChoreographerDisplayInfoListener ---
1080
onWindowInfosChanged(const gui::WindowInfosUpdate & windowInfosUpdate)1081 void PointerChoreographer::PointerChoreographerDisplayInfoListener::onWindowInfosChanged(
1082 const gui::WindowInfosUpdate& windowInfosUpdate) {
1083 std::scoped_lock _l(mLock);
1084 if (mPointerChoreographer == nullptr) {
1085 return;
1086 }
1087 auto newPrivacySensitiveDisplays =
1088 getPrivacySensitiveDisplaysFromWindowInfos(windowInfosUpdate.windowInfos);
1089
1090 // PointerChoreographer uses Listener's lock.
1091 base::ScopedLockAssertion assumeLocked(mPointerChoreographer->getLock());
1092 if (newPrivacySensitiveDisplays != mPrivacySensitiveDisplays) {
1093 mPrivacySensitiveDisplays = std::move(newPrivacySensitiveDisplays);
1094 mPointerChoreographer->onPrivacySensitiveDisplaysChangedLocked(mPrivacySensitiveDisplays);
1095 }
1096 mPointerChoreographer->populateFakeDisplayTopologyLocked(windowInfosUpdate.displayInfos);
1097 }
1098
setInitialDisplayInfosLocked(const std::vector<gui::WindowInfo> & windowInfos)1099 void PointerChoreographer::PointerChoreographerDisplayInfoListener::setInitialDisplayInfosLocked(
1100 const std::vector<gui::WindowInfo>& windowInfos) {
1101 mPrivacySensitiveDisplays = getPrivacySensitiveDisplaysFromWindowInfos(windowInfos);
1102 }
1103
1104 std::unordered_set<ui::LogicalDisplayId /*displayId*/>
getPrivacySensitiveDisplaysLocked()1105 PointerChoreographer::PointerChoreographerDisplayInfoListener::getPrivacySensitiveDisplaysLocked() {
1106 return mPrivacySensitiveDisplays;
1107 }
1108
1109 void PointerChoreographer::PointerChoreographerDisplayInfoListener::
onPointerChoreographerDestroyed()1110 onPointerChoreographerDestroyed() {
1111 std::scoped_lock _l(mLock);
1112 mPointerChoreographer = nullptr;
1113 }
1114
1115 } // namespace android
1116