diff options
| author | 2023-11-10 15:48:19 +0000 | |
|---|---|---|
| committer | 2023-11-10 15:48:19 +0000 | |
| commit | 4093ea718f38caee82fa92999998db07b1835b2b (patch) | |
| tree | 6906b820fe279efb65f3a22e5a787d33f227d1d6 | |
| parent | f7ff43f0ee328bfd3ec3dc5e3e206c4994f3dc58 (diff) | |
| parent | 167887993440cedd0f11c19b27a250f0198fc593 (diff) | |
Merge changes from topics "pointer-icon-refactor-comments", "pointer-icon-refactor-stylus", "pointer-icon-refactor-touch" into main
* changes:
Address additional comments: Pointer icon refactor for touch/stylus
Pointer icon refactor for stylus
Pointer icon refactor for touch
Add unit test cases for PointerChoreographer
6 files changed, 860 insertions, 17 deletions
diff --git a/services/inputflinger/PointerChoreographer.cpp b/services/inputflinger/PointerChoreographer.cpp index 1092bdb90e..e529bddea5 100644 --- a/services/inputflinger/PointerChoreographer.cpp +++ b/services/inputflinger/PointerChoreographer.cpp @@ -31,16 +31,30 @@ bool isFromMouse(const NotifyMotionArgs& args) { args.pointerProperties[0].toolType == ToolType::MOUSE; } +bool isHoverAction(int32_t action) { + return action == AMOTION_EVENT_ACTION_HOVER_ENTER || + action == AMOTION_EVENT_ACTION_HOVER_MOVE || action == AMOTION_EVENT_ACTION_HOVER_EXIT; +} + +bool isStylusHoverEvent(const NotifyMotionArgs& args) { + return isStylusEvent(args.source, args.pointerProperties) && isHoverAction(args.action); +} } // namespace // --- PointerChoreographer --- PointerChoreographer::PointerChoreographer(InputListenerInterface& listener, PointerChoreographerPolicyInterface& policy) - : mNextListener(listener), + : mTouchControllerConstructor([this]() REQUIRES(mLock) { + return mPolicy.createPointerController( + PointerControllerInterface::ControllerType::TOUCH); + }), + mNextListener(listener), mPolicy(policy), mDefaultMouseDisplayId(ADISPLAY_ID_DEFAULT), - mNotifiedPointerDisplayId(ADISPLAY_ID_NONE) {} + mNotifiedPointerDisplayId(ADISPLAY_ID_NONE), + mShowTouchesEnabled(false), + mStylusPointerIconEnabled(false) {} void PointerChoreographer::notifyInputDevicesChanged(const NotifyInputDevicesChangedArgs& args) { std::scoped_lock _l(mLock); @@ -69,8 +83,10 @@ NotifyMotionArgs PointerChoreographer::processMotion(const NotifyMotionArgs& arg if (isFromMouse(args)) { return processMouseEventLocked(args); + } else if (mStylusPointerIconEnabled && isStylusHoverEvent(args)) { + processStylusHoverEventLocked(args); } else if (isFromSource(args.source, AINPUT_SOURCE_TOUCHSCREEN)) { - return processTouchscreenEventLocked(args); + processTouchscreenAndStylusEventLocked(args); } return args; } @@ -114,12 +130,70 @@ NotifyMotionArgs PointerChoreographer::processMouseEventLocked(const NotifyMotio * mouse device keeps moving and unfades the cursor. * For touch events, we do not need to populate the cursor position. */ -NotifyMotionArgs PointerChoreographer::processTouchscreenEventLocked(const NotifyMotionArgs& args) { +void PointerChoreographer::processTouchscreenAndStylusEventLocked(const NotifyMotionArgs& args) { + if (args.displayId == ADISPLAY_ID_NONE) { + return; + } + if (const auto it = mMousePointersByDisplay.find(args.displayId); it != mMousePointersByDisplay.end() && args.action == AMOTION_EVENT_ACTION_DOWN) { it->second->fade(PointerControllerInterface::Transition::GRADUAL); } - return args; + + if (!mShowTouchesEnabled) { + return; + } + + // Get the touch pointer controller for the device, or create one if it doesn't exist. + auto [it, _] = mTouchPointersByDevice.try_emplace(args.deviceId, mTouchControllerConstructor); + + PointerControllerInterface& pc = *it->second; + + const PointerCoords* coords = args.pointerCoords.data(); + const int32_t maskedAction = MotionEvent::getActionMasked(args.action); + const uint8_t actionIndex = MotionEvent::getActionIndex(args.action); + std::array<uint32_t, MAX_POINTER_ID + 1> idToIndex; + BitSet32 idBits; + if (maskedAction != AMOTION_EVENT_ACTION_UP && maskedAction != AMOTION_EVENT_ACTION_CANCEL) { + for (size_t i = 0; i < args.getPointerCount(); i++) { + if (maskedAction == AMOTION_EVENT_ACTION_POINTER_UP && actionIndex == i) { + continue; + } + uint32_t id = args.pointerProperties[i].id; + idToIndex[id] = i; + idBits.markBit(id); + } + } + // The PointerController already handles setting spots per-display, so + // we do not need to manually manage display changes for touch spots for now. + pc.setSpots(coords, idToIndex.cbegin(), idBits, args.displayId); +} + +void PointerChoreographer::processStylusHoverEventLocked(const NotifyMotionArgs& args) { + if (args.displayId == ADISPLAY_ID_NONE) { + return; + } + + if (args.getPointerCount() != 1) { + LOG(WARNING) << "Only stylus hover events with a single pointer are currently supported: " + << args.dump(); + } + + // Get the stylus pointer controller for the device, or create one if it doesn't exist. + auto [it, _] = + mStylusPointersByDevice.try_emplace(args.deviceId, + getStylusControllerConstructor(args.displayId)); + + PointerControllerInterface& pc = *it->second; + + const float x = args.pointerCoords[0].getAxisValue(AMOTION_EVENT_AXIS_X); + const float y = args.pointerCoords[0].getAxisValue(AMOTION_EVENT_AXIS_Y); + pc.setPosition(x, y); + if (args.action == AMOTION_EVENT_ACTION_HOVER_EXIT) { + pc.fade(PointerControllerInterface::Transition::IMMEDIATE); + } else { + pc.unfade(PointerControllerInterface::Transition::IMMEDIATE); + } } void PointerChoreographer::notifySwitch(const NotifySwitchArgs& args) { @@ -135,9 +209,17 @@ void PointerChoreographer::notifyVibratorState(const NotifyVibratorStateArgs& ar } void PointerChoreographer::notifyDeviceReset(const NotifyDeviceResetArgs& args) { + processDeviceReset(args); + mNextListener.notify(args); } +void PointerChoreographer::processDeviceReset(const NotifyDeviceResetArgs& args) { + std::scoped_lock _l(mLock); + mTouchPointersByDevice.erase(args.deviceId); + mStylusPointersByDevice.erase(args.deviceId); +} + void PointerChoreographer::notifyPointerCaptureChanged( const NotifyPointerCaptureChangedArgs& args) { if (args.request.enable) { @@ -153,12 +235,25 @@ void PointerChoreographer::dump(std::string& dump) { std::scoped_lock _l(mLock); dump += "PointerChoreographer:\n"; + dump += StringPrintf("show touches: %s\n", mShowTouchesEnabled ? "true" : "false"); + dump += StringPrintf("stylus pointer icon enabled: %s\n", + mStylusPointerIconEnabled ? "true" : "false"); dump += INDENT "MousePointerControllers:\n"; for (const auto& [displayId, mousePointerController] : mMousePointersByDisplay) { std::string pointerControllerDump = addLinePrefix(mousePointerController->dump(), INDENT); dump += INDENT + std::to_string(displayId) + " : " + pointerControllerDump; } + dump += INDENT "TouchPointerControllers:\n"; + for (const auto& [deviceId, touchPointerController] : mTouchPointersByDevice) { + std::string pointerControllerDump = addLinePrefix(touchPointerController->dump(), INDENT); + dump += INDENT + std::to_string(deviceId) + " : " + pointerControllerDump; + } + dump += INDENT "StylusPointerControllers:\n"; + for (const auto& [deviceId, stylusPointerController] : mStylusPointersByDevice) { + std::string pointerControllerDump = addLinePrefix(stylusPointerController->dump(), INDENT); + dump += INDENT + std::to_string(deviceId) + " : " + pointerControllerDump; + } dump += "\n"; } @@ -175,8 +270,16 @@ int32_t PointerChoreographer::getTargetMouseDisplayLocked(int32_t associatedDisp return associatedDisplayId == ADISPLAY_ID_NONE ? mDefaultMouseDisplayId : associatedDisplayId; } +InputDeviceInfo* PointerChoreographer::findInputDeviceLocked(DeviceId deviceId) { + auto it = std::find_if(mInputDeviceInfos.begin(), mInputDeviceInfos.end(), + [deviceId](const auto& info) { return info.getId() == deviceId; }); + return it != mInputDeviceInfos.end() ? &(*it) : nullptr; +} + void PointerChoreographer::updatePointerControllersLocked() { std::set<int32_t /*displayId*/> mouseDisplaysToKeep; + std::set<DeviceId> touchDevicesToKeep; + std::set<DeviceId> stylusDevicesToKeep; // Mark the displayIds or deviceIds of PointerControllers currently needed. for (const auto& info : mInputDeviceInfos) { @@ -187,17 +290,25 @@ void PointerChoreographer::updatePointerControllersLocked() { getTargetMouseDisplayLocked(info.getAssociatedDisplayId()); mouseDisplaysToKeep.insert(resolvedDisplayId); } + if (isFromSource(sources, AINPUT_SOURCE_TOUCHSCREEN) && mShowTouchesEnabled && + info.getAssociatedDisplayId() != ADISPLAY_ID_NONE) { + touchDevicesToKeep.insert(info.getId()); + } + if (isFromSource(sources, AINPUT_SOURCE_STYLUS) && mStylusPointerIconEnabled && + info.getAssociatedDisplayId() != ADISPLAY_ID_NONE) { + stylusDevicesToKeep.insert(info.getId()); + } } // Remove PointerControllers no longer needed. - // This has the side-effect of fading pointers or clearing spots before removal. std::erase_if(mMousePointersByDisplay, [&mouseDisplaysToKeep](const auto& pair) { - auto& [displayId, controller] = pair; - if (mouseDisplaysToKeep.find(displayId) == mouseDisplaysToKeep.end()) { - controller->fade(PointerControllerInterface::Transition::IMMEDIATE); - return true; - } - return false; + return mouseDisplaysToKeep.find(pair.first) == mouseDisplaysToKeep.end(); + }); + std::erase_if(mTouchPointersByDevice, [&touchDevicesToKeep](const auto& pair) { + return touchDevicesToKeep.find(pair.first) == touchDevicesToKeep.end(); + }); + std::erase_if(mStylusPointersByDevice, [&stylusDevicesToKeep](const auto& pair) { + return stylusDevicesToKeep.find(pair.first) == stylusDevicesToKeep.end(); }); // Notify the policy if there's a change on the pointer display ID. @@ -234,10 +345,17 @@ void PointerChoreographer::setDefaultMouseDisplayId(int32_t displayId) { void PointerChoreographer::setDisplayViewports(const std::vector<DisplayViewport>& viewports) { std::scoped_lock _l(mLock); for (const auto& viewport : viewports) { - if (const auto it = mMousePointersByDisplay.find(viewport.displayId); + const int32_t displayId = viewport.displayId; + if (const auto it = mMousePointersByDisplay.find(displayId); it != mMousePointersByDisplay.end()) { it->second->setDisplayViewport(viewport); } + for (const auto& [deviceId, stylusPointerController] : mStylusPointersByDevice) { + const InputDeviceInfo* info = findInputDeviceLocked(deviceId); + if (info && info->getAssociatedDisplayId() == displayId) { + stylusPointerController->setDisplayViewport(viewport); + } + } } mViewports = viewports; notifyPointerDisplayIdChangedLocked(); @@ -263,6 +381,24 @@ FloatPoint PointerChoreographer::getMouseCursorPosition(int32_t displayId) { return {AMOTION_EVENT_INVALID_CURSOR_POSITION, AMOTION_EVENT_INVALID_CURSOR_POSITION}; } +void PointerChoreographer::setShowTouchesEnabled(bool enabled) { + std::scoped_lock _l(mLock); + if (mShowTouchesEnabled == enabled) { + return; + } + mShowTouchesEnabled = enabled; + updatePointerControllersLocked(); +} + +void PointerChoreographer::setStylusPointerIconEnabled(bool enabled) { + std::scoped_lock _l(mLock); + if (mStylusPointerIconEnabled == enabled) { + return; + } + mStylusPointerIconEnabled = enabled; + updatePointerControllersLocked(); +} + PointerChoreographer::ControllerConstructor PointerChoreographer::getMouseControllerConstructor( int32_t displayId) { std::function<std::shared_ptr<PointerControllerInterface>()> ctor = @@ -277,4 +413,18 @@ PointerChoreographer::ControllerConstructor PointerChoreographer::getMouseContro return ConstructorDelegate(std::move(ctor)); } +PointerChoreographer::ControllerConstructor PointerChoreographer::getStylusControllerConstructor( + int32_t displayId) { + std::function<std::shared_ptr<PointerControllerInterface>()> ctor = + [this, displayId]() REQUIRES(mLock) { + auto pc = mPolicy.createPointerController( + PointerControllerInterface::ControllerType::STYLUS); + if (const auto viewport = findViewportByIdLocked(displayId); viewport) { + pc->setDisplayViewport(*viewport); + } + return pc; + }; + return ConstructorDelegate(std::move(ctor)); +} + } // namespace android diff --git a/services/inputflinger/PointerChoreographer.h b/services/inputflinger/PointerChoreographer.h index c1b900f8a5..26d2fef5c3 100644 --- a/services/inputflinger/PointerChoreographer.h +++ b/services/inputflinger/PointerChoreographer.h @@ -56,6 +56,8 @@ public: virtual std::optional<DisplayViewport> getViewportForPointerDevice( int32_t associatedDisplayId = ADISPLAY_ID_NONE) = 0; virtual FloatPoint getMouseCursorPosition(int32_t displayId) = 0; + virtual void setShowTouchesEnabled(bool enabled) = 0; + virtual void setStylusPointerIconEnabled(bool enabled) = 0; /** * This method may be called on any thread (usually by the input manager on a binder thread). */ @@ -73,6 +75,8 @@ public: std::optional<DisplayViewport> getViewportForPointerDevice( int32_t associatedDisplayId) override; FloatPoint getMouseCursorPosition(int32_t displayId) override; + void setShowTouchesEnabled(bool enabled) override; + void setStylusPointerIconEnabled(bool enabled) override; void notifyInputDevicesChanged(const NotifyInputDevicesChangedArgs& args) override; void notifyConfigurationChanged(const NotifyConfigurationChangedArgs& args) override; @@ -91,14 +95,19 @@ private: void notifyPointerDisplayIdChangedLocked() REQUIRES(mLock); const DisplayViewport* findViewportByIdLocked(int32_t displayId) const REQUIRES(mLock); int32_t getTargetMouseDisplayLocked(int32_t associatedDisplayId) const REQUIRES(mLock); + InputDeviceInfo* findInputDeviceLocked(DeviceId deviceId) REQUIRES(mLock); NotifyMotionArgs processMotion(const NotifyMotionArgs& args); NotifyMotionArgs processMouseEventLocked(const NotifyMotionArgs& args) REQUIRES(mLock); - NotifyMotionArgs processTouchscreenEventLocked(const NotifyMotionArgs& args) REQUIRES(mLock); + void processTouchscreenAndStylusEventLocked(const NotifyMotionArgs& args) REQUIRES(mLock); + void processStylusHoverEventLocked(const NotifyMotionArgs& args) REQUIRES(mLock); + void processDeviceReset(const NotifyDeviceResetArgs& args); using ControllerConstructor = ConstructorDelegate<std::function<std::shared_ptr<PointerControllerInterface>()>>; + ControllerConstructor mTouchControllerConstructor GUARDED_BY(mLock); ControllerConstructor getMouseControllerConstructor(int32_t displayId) REQUIRES(mLock); + ControllerConstructor getStylusControllerConstructor(int32_t displayId) REQUIRES(mLock); std::mutex mLock; @@ -107,11 +116,17 @@ private: std::map<int32_t, std::shared_ptr<PointerControllerInterface>> mMousePointersByDisplay GUARDED_BY(mLock); + std::map<DeviceId, std::shared_ptr<PointerControllerInterface>> mTouchPointersByDevice + GUARDED_BY(mLock); + std::map<DeviceId, std::shared_ptr<PointerControllerInterface>> mStylusPointersByDevice + GUARDED_BY(mLock); int32_t mDefaultMouseDisplayId GUARDED_BY(mLock); int32_t mNotifiedPointerDisplayId GUARDED_BY(mLock); std::vector<InputDeviceInfo> mInputDeviceInfos GUARDED_BY(mLock); std::vector<DisplayViewport> mViewports GUARDED_BY(mLock); + bool mShowTouchesEnabled GUARDED_BY(mLock); + bool mStylusPointerIconEnabled GUARDED_BY(mLock); }; } // namespace android diff --git a/services/inputflinger/include/PointerControllerInterface.h b/services/inputflinger/include/PointerControllerInterface.h index 8837b25378..ef74a55860 100644 --- a/services/inputflinger/include/PointerControllerInterface.h +++ b/services/inputflinger/include/PointerControllerInterface.h @@ -48,8 +48,8 @@ struct FloatPoint { */ class PointerControllerInterface { protected: - PointerControllerInterface() { } - virtual ~PointerControllerInterface() { } + PointerControllerInterface() {} + virtual ~PointerControllerInterface() {} public: /** @@ -63,6 +63,10 @@ public: LEGACY, // Represents a single mouse pointer. MOUSE, + // Represents multiple touch spots. + TOUCH, + // Represents a single stylus pointer. + STYLUS, }; /* Dumps the state of the pointer controller. */ @@ -121,7 +125,7 @@ public: * pressed (not hovering). */ virtual void setSpots(const PointerCoords* spotCoords, const uint32_t* spotIdToIndex, - BitSet32 spotIdBits, int32_t displayId) = 0; + BitSet32 spotIdBits, int32_t displayId) = 0; /* Removes all spots. */ virtual void clearSpots() = 0; diff --git a/services/inputflinger/tests/FakePointerController.cpp b/services/inputflinger/tests/FakePointerController.cpp index ca517f32c9..5475594113 100644 --- a/services/inputflinger/tests/FakePointerController.cpp +++ b/services/inputflinger/tests/FakePointerController.cpp @@ -47,6 +47,8 @@ int32_t FakePointerController::getDisplayId() const { void FakePointerController::setDisplayViewport(const DisplayViewport& viewport) { mDisplayId = viewport.displayId; + setBounds(viewport.logicalLeft, viewport.logicalTop, viewport.logicalRight - 1, + viewport.logicalBottom - 1); } void FakePointerController::assertPosition(float x, float y) { @@ -55,6 +57,12 @@ void FakePointerController::assertPosition(float x, float y) { ASSERT_NEAR(y, actualY, 1); } +void FakePointerController::assertSpotCount(int32_t displayId, int32_t count) { + auto it = mSpotsByDisplay.find(displayId); + ASSERT_TRUE(it != mSpotsByDisplay.end()) << "Spots not found for display " << displayId; + ASSERT_EQ(static_cast<size_t>(count), it->second.size()); +} + bool FakePointerController::isPointerShown() { return mIsPointerShown; } diff --git a/services/inputflinger/tests/FakePointerController.h b/services/inputflinger/tests/FakePointerController.h index c75f6ed7e5..d7e40b3cc1 100644 --- a/services/inputflinger/tests/FakePointerController.h +++ b/services/inputflinger/tests/FakePointerController.h @@ -37,6 +37,7 @@ public: void setDisplayViewport(const DisplayViewport& viewport) override; void assertPosition(float x, float y); + void assertSpotCount(int32_t displayId, int32_t count); bool isPointerShown(); private: diff --git a/services/inputflinger/tests/PointerChoreographer_test.cpp b/services/inputflinger/tests/PointerChoreographer_test.cpp index da2e205ca5..68f58571f5 100644 --- a/services/inputflinger/tests/PointerChoreographer_test.cpp +++ b/services/inputflinger/tests/PointerChoreographer_test.cpp @@ -27,6 +27,7 @@ namespace android { using ControllerType = PointerControllerInterface::ControllerType; +using testing::AllOf; namespace { @@ -37,12 +38,18 @@ template <typename... V> Visitor(V...) -> Visitor<V...>; constexpr int32_t DEVICE_ID = 3; +constexpr int32_t SECOND_DEVICE_ID = DEVICE_ID + 1; constexpr int32_t DISPLAY_ID = 5; constexpr int32_t ANOTHER_DISPLAY_ID = 10; +constexpr int32_t DISPLAY_WIDTH = 480; +constexpr int32_t DISPLAY_HEIGHT = 800; const auto MOUSE_POINTER = PointerBuilder(/*id=*/0, ToolType::MOUSE) .axis(AMOTION_EVENT_AXIS_RELATIVE_X, 10) .axis(AMOTION_EVENT_AXIS_RELATIVE_Y, 20); +const auto FIRST_TOUCH_POINTER = PointerBuilder(/*id=*/0, ToolType::FINGER).x(100).y(200); +const auto SECOND_TOUCH_POINTER = PointerBuilder(/*id=*/1, ToolType::FINGER).x(200).y(300); +const auto STYLUS_POINTER = PointerBuilder(/*id=*/0, ToolType::STYLUS).x(100).y(200); static InputDeviceInfo generateTestDeviceInfo(int32_t deviceId, uint32_t source, int32_t associatedDisplayId) { @@ -60,6 +67,8 @@ static std::vector<DisplayViewport> createViewports(std::vector<int32_t> display for (auto displayId : displayIds) { DisplayViewport viewport; viewport.displayId = displayId; + viewport.logicalRight = DISPLAY_WIDTH; + viewport.logicalBottom = DISPLAY_HEIGHT; viewports.push_back(viewport); } return viewports; @@ -115,6 +124,7 @@ private: EXPECT_FALSE(mLastCreatedController.has_value()) << "More than one PointerController created at a time"; std::shared_ptr<FakePointerController> pc = std::make_shared<FakePointerController>(); + EXPECT_FALSE(pc->isPointerShown()); mLastCreatedController = {type, pc}; return pc; } @@ -385,4 +395,659 @@ TEST_F(PointerChoreographerTest, WhenDefaultMouseDisplayChangesCallsNotifyPointe assertPointerDisplayIdNotified(ANOTHER_DISPLAY_ID); } +TEST_F(PointerChoreographerTest, MouseMovesPointerAndReturnsNewArgs) { + mChoreographer.setDisplayViewports(createViewports({DISPLAY_ID})); + mChoreographer.setDefaultMouseDisplayId(DISPLAY_ID); + mChoreographer.notifyInputDevicesChanged( + {/*id=*/0, {generateTestDeviceInfo(DEVICE_ID, AINPUT_SOURCE_MOUSE, ADISPLAY_ID_NONE)}}); + mChoreographer.notifyMotion( + MotionArgsBuilder(AMOTION_EVENT_ACTION_HOVER_MOVE, AINPUT_SOURCE_MOUSE) + .pointer(MOUSE_POINTER) + .deviceId(DEVICE_ID) + .displayId(ADISPLAY_ID_NONE) + .build()); + mTestListener.assertNotifyMotionWasCalled(WithMotionAction(AMOTION_EVENT_ACTION_HOVER_MOVE)); + auto pc = assertPointerControllerCreated(ControllerType::MOUSE); + ASSERT_EQ(DISPLAY_ID, pc->getDisplayId()); + + // Set bounds and initial position of the PointerController. + pc->setPosition(100, 200); + + // Make NotifyMotionArgs and notify Choreographer. + mChoreographer.notifyMotion( + MotionArgsBuilder(AMOTION_EVENT_ACTION_HOVER_MOVE, AINPUT_SOURCE_MOUSE) + .pointer(MOUSE_POINTER) + .deviceId(DEVICE_ID) + .displayId(ADISPLAY_ID_NONE) + .build()); + + // Check that the PointerController updated the position and the pointer is shown. + pc->assertPosition(110, 220); + ASSERT_TRUE(pc->isPointerShown()); + + // Check that x-y cooridnates, displayId and cursor position are correctly updated. + mTestListener.assertNotifyMotionWasCalled( + AllOf(WithCoords(110, 220), WithDisplayId(DISPLAY_ID), WithCursorPosition(110, 220))); +} + +TEST_F(PointerChoreographerTest, + AssociatedMouseMovesPointerOnAssociatedDisplayAndDoesNotMovePointerOnDefaultDisplay) { + // Add two displays and set one to default. + mChoreographer.setDisplayViewports(createViewports({DISPLAY_ID, ANOTHER_DISPLAY_ID})); + mChoreographer.setDefaultMouseDisplayId(DISPLAY_ID); + + // Add two devices, one unassociated and the other associated with non-default mouse display. + mChoreographer.notifyInputDevicesChanged( + {/*id=*/0, + {generateTestDeviceInfo(DEVICE_ID, AINPUT_SOURCE_MOUSE, ADISPLAY_ID_NONE), + generateTestDeviceInfo(SECOND_DEVICE_ID, AINPUT_SOURCE_MOUSE, ANOTHER_DISPLAY_ID)}}); + mChoreographer.notifyMotion( + MotionArgsBuilder(AMOTION_EVENT_ACTION_HOVER_MOVE, AINPUT_SOURCE_MOUSE) + .pointer(MOUSE_POINTER) + .deviceId(DEVICE_ID) + .displayId(ADISPLAY_ID_NONE) + .build()); + mTestListener.assertNotifyMotionWasCalled(WithMotionAction(AMOTION_EVENT_ACTION_HOVER_MOVE)); + auto unassociatedMousePc = assertPointerControllerCreated(ControllerType::MOUSE); + ASSERT_EQ(DISPLAY_ID, unassociatedMousePc->getDisplayId()); + + mChoreographer.notifyMotion( + MotionArgsBuilder(AMOTION_EVENT_ACTION_HOVER_MOVE, AINPUT_SOURCE_MOUSE) + .pointer(MOUSE_POINTER) + .deviceId(SECOND_DEVICE_ID) + .displayId(ANOTHER_DISPLAY_ID) + .build()); + mTestListener.assertNotifyMotionWasCalled(WithMotionAction(AMOTION_EVENT_ACTION_HOVER_MOVE)); + auto associatedMousePc = assertPointerControllerCreated(ControllerType::MOUSE); + ASSERT_EQ(ANOTHER_DISPLAY_ID, associatedMousePc->getDisplayId()); + + // Set bounds and initial position for PointerControllers. + unassociatedMousePc->setPosition(100, 200); + associatedMousePc->setPosition(300, 400); + + // Make NotifyMotionArgs from the associated mouse and notify Choreographer. + mChoreographer.notifyMotion( + MotionArgsBuilder(AMOTION_EVENT_ACTION_HOVER_MOVE, AINPUT_SOURCE_MOUSE) + .pointer(MOUSE_POINTER) + .deviceId(SECOND_DEVICE_ID) + .displayId(ANOTHER_DISPLAY_ID) + .build()); + + // Check the status of the PointerControllers. + unassociatedMousePc->assertPosition(100, 200); + ASSERT_EQ(DISPLAY_ID, unassociatedMousePc->getDisplayId()); + associatedMousePc->assertPosition(310, 420); + ASSERT_EQ(ANOTHER_DISPLAY_ID, associatedMousePc->getDisplayId()); + ASSERT_TRUE(associatedMousePc->isPointerShown()); + + // Check that x-y cooridnates, displayId and cursor position are correctly updated. + mTestListener.assertNotifyMotionWasCalled( + AllOf(WithCoords(310, 420), WithDeviceId(SECOND_DEVICE_ID), + WithDisplayId(ANOTHER_DISPLAY_ID), WithCursorPosition(310, 420))); +} + +TEST_F(PointerChoreographerTest, DoesNotMovePointerForMouseRelativeSource) { + mChoreographer.setDisplayViewports(createViewports({DISPLAY_ID})); + mChoreographer.setDefaultMouseDisplayId(DISPLAY_ID); + mChoreographer.notifyInputDevicesChanged( + {/*id=*/0, {generateTestDeviceInfo(DEVICE_ID, AINPUT_SOURCE_MOUSE, ADISPLAY_ID_NONE)}}); + mChoreographer.notifyMotion( + MotionArgsBuilder(AMOTION_EVENT_ACTION_HOVER_MOVE, AINPUT_SOURCE_MOUSE) + .pointer(MOUSE_POINTER) + .deviceId(DEVICE_ID) + .displayId(ADISPLAY_ID_NONE) + .build()); + mTestListener.assertNotifyMotionWasCalled(WithMotionAction(AMOTION_EVENT_ACTION_HOVER_MOVE)); + auto pc = assertPointerControllerCreated(ControllerType::MOUSE); + ASSERT_EQ(DISPLAY_ID, pc->getDisplayId()); + + // Set bounds and initial position of the PointerController. + pc->setPosition(100, 200); + + // Assume that pointer capture is enabled. + mChoreographer.notifyInputDevicesChanged( + {/*id=*/1, + {generateTestDeviceInfo(DEVICE_ID, AINPUT_SOURCE_MOUSE_RELATIVE, ADISPLAY_ID_NONE)}}); + mChoreographer.notifyPointerCaptureChanged( + NotifyPointerCaptureChangedArgs(/*id=*/2, systemTime(SYSTEM_TIME_MONOTONIC), + PointerCaptureRequest(/*enable=*/true, /*seq=*/0))); + + // Notify motion as if pointer capture is enabled. + mChoreographer.notifyMotion( + MotionArgsBuilder(AMOTION_EVENT_ACTION_MOVE, AINPUT_SOURCE_MOUSE_RELATIVE) + .pointer(PointerBuilder(/*id=*/0, ToolType::MOUSE) + .x(10) + .y(20) + .axis(AMOTION_EVENT_AXIS_RELATIVE_X, 10) + .axis(AMOTION_EVENT_AXIS_RELATIVE_Y, 20)) + .deviceId(DEVICE_ID) + .displayId(ADISPLAY_ID_NONE) + .build()); + + // Check that there's no update on the PointerController. + pc->assertPosition(100, 200); + ASSERT_FALSE(pc->isPointerShown()); + + // Check x-y cooridnates, displayId and cursor position are not changed. + mTestListener.assertNotifyMotionWasCalled( + AllOf(WithCoords(10, 20), WithRelativeMotion(10, 20), WithDisplayId(ADISPLAY_ID_NONE), + WithCursorPosition(AMOTION_EVENT_INVALID_CURSOR_POSITION, + AMOTION_EVENT_INVALID_CURSOR_POSITION))); +} + +TEST_F(PointerChoreographerTest, WhenPointerCaptureEnabledHidesPointer) { + mChoreographer.setDisplayViewports(createViewports({DISPLAY_ID})); + mChoreographer.setDefaultMouseDisplayId(DISPLAY_ID); + mChoreographer.notifyInputDevicesChanged( + {/*id=*/0, {generateTestDeviceInfo(DEVICE_ID, AINPUT_SOURCE_MOUSE, ADISPLAY_ID_NONE)}}); + mChoreographer.notifyMotion( + MotionArgsBuilder(AMOTION_EVENT_ACTION_HOVER_MOVE, AINPUT_SOURCE_MOUSE) + .pointer(MOUSE_POINTER) + .deviceId(DEVICE_ID) + .displayId(ADISPLAY_ID_NONE) + .build()); + mTestListener.assertNotifyMotionWasCalled(WithMotionAction(AMOTION_EVENT_ACTION_HOVER_MOVE)); + auto pc = assertPointerControllerCreated(ControllerType::MOUSE); + ASSERT_EQ(DISPLAY_ID, pc->getDisplayId()); + + // Set bounds and initial position of the PointerController. + pc->setPosition(100, 200); + + // Make NotifyMotionArgs and notify Choreographer. + mChoreographer.notifyMotion( + MotionArgsBuilder(AMOTION_EVENT_ACTION_HOVER_MOVE, AINPUT_SOURCE_MOUSE) + .pointer(MOUSE_POINTER) + .deviceId(DEVICE_ID) + .displayId(ADISPLAY_ID_NONE) + .build()); + + // Check that the PointerController updated the position and the pointer is shown. + pc->assertPosition(110, 220); + ASSERT_TRUE(pc->isPointerShown()); + + // Enable pointer capture and check if the PointerController hid the pointer. + mChoreographer.notifyPointerCaptureChanged( + NotifyPointerCaptureChangedArgs(/*id=*/1, systemTime(SYSTEM_TIME_MONOTONIC), + PointerCaptureRequest(/*enable=*/true, /*seq=*/0))); + ASSERT_FALSE(pc->isPointerShown()); +} + +TEST_F(PointerChoreographerTest, WhenShowTouchesEnabledAndDisabledDoesNotCreatePointerController) { + // Disable show touches and add a touch device. + mChoreographer.setShowTouchesEnabled(false); + mChoreographer.notifyInputDevicesChanged( + {/*id=*/0, {generateTestDeviceInfo(DEVICE_ID, AINPUT_SOURCE_TOUCHSCREEN, DISPLAY_ID)}}); + assertPointerControllerNotCreated(); + + // Enable show touches. PointerController still should not be created. + mChoreographer.setShowTouchesEnabled(true); + assertPointerControllerNotCreated(); +} + +TEST_F(PointerChoreographerTest, WhenTouchEventOccursCreatesPointerController) { + // Add a touch device and enable show touches. + mChoreographer.notifyInputDevicesChanged( + {/*id=*/0, {generateTestDeviceInfo(DEVICE_ID, AINPUT_SOURCE_TOUCHSCREEN, DISPLAY_ID)}}); + mChoreographer.setShowTouchesEnabled(true); + + // Emit touch event. Now PointerController should be created. + mChoreographer.notifyMotion( + MotionArgsBuilder(AMOTION_EVENT_ACTION_DOWN, AINPUT_SOURCE_TOUCHSCREEN) + .pointer(FIRST_TOUCH_POINTER) + .deviceId(DEVICE_ID) + .displayId(DISPLAY_ID) + .build()); + assertPointerControllerCreated(ControllerType::TOUCH); +} + +TEST_F(PointerChoreographerTest, + WhenShowTouchesDisabledAndTouchEventOccursDoesNotCreatePointerController) { + // Add a touch device and disable show touches. + mChoreographer.notifyInputDevicesChanged( + {/*id=*/0, {generateTestDeviceInfo(DEVICE_ID, AINPUT_SOURCE_TOUCHSCREEN, DISPLAY_ID)}}); + mChoreographer.setShowTouchesEnabled(false); + assertPointerControllerNotCreated(); + + // Emit touch event. Still, PointerController should not be created. + mChoreographer.notifyMotion( + MotionArgsBuilder(AMOTION_EVENT_ACTION_DOWN, AINPUT_SOURCE_TOUCHSCREEN) + .pointer(FIRST_TOUCH_POINTER) + .deviceId(DEVICE_ID) + .displayId(DISPLAY_ID) + .build()); + assertPointerControllerNotCreated(); +} + +TEST_F(PointerChoreographerTest, WhenTouchDeviceIsRemovedRemovesPointerController) { + // Make sure the PointerController is created. + mChoreographer.notifyInputDevicesChanged( + {/*id=*/0, {generateTestDeviceInfo(DEVICE_ID, AINPUT_SOURCE_TOUCHSCREEN, DISPLAY_ID)}}); + mChoreographer.setShowTouchesEnabled(true); + mChoreographer.notifyMotion( + MotionArgsBuilder(AMOTION_EVENT_ACTION_DOWN, AINPUT_SOURCE_TOUCHSCREEN) + .pointer(FIRST_TOUCH_POINTER) + .deviceId(DEVICE_ID) + .displayId(DISPLAY_ID) + .build()); + auto pc = assertPointerControllerCreated(ControllerType::TOUCH); + + // Remove the device. + mChoreographer.notifyInputDevicesChanged({/*id=*/1, {}}); + assertPointerControllerRemoved(pc); +} + +TEST_F(PointerChoreographerTest, WhenShowTouchesDisabledRemovesPointerController) { + // Make sure the PointerController is created. + mChoreographer.notifyInputDevicesChanged( + {/*id=*/0, {generateTestDeviceInfo(DEVICE_ID, AINPUT_SOURCE_TOUCHSCREEN, DISPLAY_ID)}}); + mChoreographer.setShowTouchesEnabled(true); + assertPointerControllerNotCreated(); + mChoreographer.notifyMotion( + MotionArgsBuilder(AMOTION_EVENT_ACTION_DOWN, AINPUT_SOURCE_TOUCHSCREEN) + .pointer(FIRST_TOUCH_POINTER) + .deviceId(DEVICE_ID) + .displayId(DISPLAY_ID) + .build()); + auto pc = assertPointerControllerCreated(ControllerType::TOUCH); + + // Disable show touches. + mChoreographer.setShowTouchesEnabled(false); + assertPointerControllerRemoved(pc); +} + +TEST_F(PointerChoreographerTest, TouchSetsSpots) { + mChoreographer.setShowTouchesEnabled(true); + mChoreographer.notifyInputDevicesChanged( + {/*id=*/0, {generateTestDeviceInfo(DEVICE_ID, AINPUT_SOURCE_TOUCHSCREEN, DISPLAY_ID)}}); + + // Emit first pointer down. + mChoreographer.notifyMotion( + MotionArgsBuilder(AMOTION_EVENT_ACTION_DOWN, AINPUT_SOURCE_TOUCHSCREEN) + .pointer(FIRST_TOUCH_POINTER) + .deviceId(DEVICE_ID) + .displayId(DISPLAY_ID) + .build()); + auto pc = assertPointerControllerCreated(ControllerType::TOUCH); + pc->assertSpotCount(DISPLAY_ID, 1); + + // Emit second pointer down. + mChoreographer.notifyMotion( + MotionArgsBuilder(AMOTION_EVENT_ACTION_POINTER_DOWN | + (1 << AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT), + AINPUT_SOURCE_TOUCHSCREEN) + .pointer(FIRST_TOUCH_POINTER) + .pointer(SECOND_TOUCH_POINTER) + .deviceId(DEVICE_ID) + .displayId(DISPLAY_ID) + .build()); + pc->assertSpotCount(DISPLAY_ID, 2); + + // Emit second pointer up. + mChoreographer.notifyMotion( + MotionArgsBuilder(AMOTION_EVENT_ACTION_POINTER_UP | + (1 << AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT), + AINPUT_SOURCE_TOUCHSCREEN) + .pointer(FIRST_TOUCH_POINTER) + .pointer(SECOND_TOUCH_POINTER) + .deviceId(DEVICE_ID) + .displayId(DISPLAY_ID) + .build()); + pc->assertSpotCount(DISPLAY_ID, 1); + + // Emit first pointer up. + mChoreographer.notifyMotion( + MotionArgsBuilder(AMOTION_EVENT_ACTION_UP, AINPUT_SOURCE_TOUCHSCREEN) + .pointer(FIRST_TOUCH_POINTER) + .deviceId(DEVICE_ID) + .displayId(DISPLAY_ID) + .build()); + pc->assertSpotCount(DISPLAY_ID, 0); +} + +TEST_F(PointerChoreographerTest, TouchSetsSpotsForStylusEvent) { + mChoreographer.setShowTouchesEnabled(true); + mChoreographer.notifyInputDevicesChanged( + {/*id=*/0, + {generateTestDeviceInfo(DEVICE_ID, AINPUT_SOURCE_TOUCHSCREEN | AINPUT_SOURCE_STYLUS, + DISPLAY_ID)}}); + + // Emit down event with stylus properties. + mChoreographer.notifyMotion(MotionArgsBuilder(AMOTION_EVENT_ACTION_DOWN, + AINPUT_SOURCE_TOUCHSCREEN | AINPUT_SOURCE_STYLUS) + .pointer(STYLUS_POINTER) + .deviceId(DEVICE_ID) + .displayId(DISPLAY_ID) + .build()); + auto pc = assertPointerControllerCreated(ControllerType::TOUCH); + pc->assertSpotCount(DISPLAY_ID, 1); +} + +TEST_F(PointerChoreographerTest, TouchSetsSpotsForTwoDisplays) { + mChoreographer.setShowTouchesEnabled(true); + // Add two touch devices associated to different displays. + mChoreographer.notifyInputDevicesChanged( + {/*id=*/0, + {generateTestDeviceInfo(DEVICE_ID, AINPUT_SOURCE_TOUCHSCREEN, DISPLAY_ID), + generateTestDeviceInfo(SECOND_DEVICE_ID, AINPUT_SOURCE_TOUCHSCREEN, + ANOTHER_DISPLAY_ID)}}); + + // Emit touch event with first device. + mChoreographer.notifyMotion( + MotionArgsBuilder(AMOTION_EVENT_ACTION_DOWN, AINPUT_SOURCE_TOUCHSCREEN) + .pointer(FIRST_TOUCH_POINTER) + .deviceId(DEVICE_ID) + .displayId(DISPLAY_ID) + .build()); + auto firstDisplayPc = assertPointerControllerCreated(ControllerType::TOUCH); + firstDisplayPc->assertSpotCount(DISPLAY_ID, 1); + + // Emit touch events with second device. + mChoreographer.notifyMotion( + MotionArgsBuilder(AMOTION_EVENT_ACTION_DOWN, AINPUT_SOURCE_TOUCHSCREEN) + .pointer(FIRST_TOUCH_POINTER) + .deviceId(SECOND_DEVICE_ID) + .displayId(ANOTHER_DISPLAY_ID) + .build()); + mChoreographer.notifyMotion( + MotionArgsBuilder(AMOTION_EVENT_ACTION_POINTER_DOWN, AINPUT_SOURCE_TOUCHSCREEN) + .pointer(FIRST_TOUCH_POINTER) + .pointer(SECOND_TOUCH_POINTER) + .deviceId(SECOND_DEVICE_ID) + .displayId(ANOTHER_DISPLAY_ID) + .build()); + + // There should be another PointerController created. + auto secondDisplayPc = assertPointerControllerCreated(ControllerType::TOUCH); + + // Check if the spots are set for the second device. + secondDisplayPc->assertSpotCount(ANOTHER_DISPLAY_ID, 2); + + // Check if there's no change on the spot of the first device. + firstDisplayPc->assertSpotCount(DISPLAY_ID, 1); +} + +TEST_F(PointerChoreographerTest, WhenTouchDeviceIsResetClearsSpots) { + // Make sure the PointerController is created and there is a spot. + mChoreographer.notifyInputDevicesChanged( + {/*id=*/0, {generateTestDeviceInfo(DEVICE_ID, AINPUT_SOURCE_TOUCHSCREEN, DISPLAY_ID)}}); + mChoreographer.setShowTouchesEnabled(true); + mChoreographer.notifyMotion( + MotionArgsBuilder(AMOTION_EVENT_ACTION_DOWN, AINPUT_SOURCE_TOUCHSCREEN) + .pointer(FIRST_TOUCH_POINTER) + .deviceId(DEVICE_ID) + .displayId(DISPLAY_ID) + .build()); + auto pc = assertPointerControllerCreated(ControllerType::TOUCH); + pc->assertSpotCount(DISPLAY_ID, 1); + + // Reset the device and ensure the touch pointer controller was removed. + mChoreographer.notifyDeviceReset(NotifyDeviceResetArgs(/*id=*/1, /*eventTime=*/0, DEVICE_ID)); + assertPointerControllerRemoved(pc); +} + +TEST_F(PointerChoreographerTest, + WhenStylusPointerIconEnabledAndDisabledDoesNotCreatePointerController) { + // Disable stylus pointer icon and add a stylus device. + mChoreographer.setStylusPointerIconEnabled(false); + mChoreographer.notifyInputDevicesChanged( + {/*id=*/0, {generateTestDeviceInfo(DEVICE_ID, AINPUT_SOURCE_STYLUS, DISPLAY_ID)}}); + assertPointerControllerNotCreated(); + + // Enable stylus pointer icon. PointerController still should not be created. + mChoreographer.setStylusPointerIconEnabled(true); + assertPointerControllerNotCreated(); +} + +TEST_F(PointerChoreographerTest, WhenStylusHoverEventOccursCreatesPointerController) { + // Add a stylus device and enable stylus pointer icon. + mChoreographer.notifyInputDevicesChanged( + {/*id=*/0, {generateTestDeviceInfo(DEVICE_ID, AINPUT_SOURCE_STYLUS, DISPLAY_ID)}}); + mChoreographer.setStylusPointerIconEnabled(true); + assertPointerControllerNotCreated(); + + // Emit hover event. Now PointerController should be created. + mChoreographer.notifyMotion( + MotionArgsBuilder(AMOTION_EVENT_ACTION_HOVER_ENTER, AINPUT_SOURCE_STYLUS) + .pointer(STYLUS_POINTER) + .deviceId(DEVICE_ID) + .displayId(DISPLAY_ID) + .build()); + assertPointerControllerCreated(ControllerType::STYLUS); +} + +TEST_F(PointerChoreographerTest, + WhenStylusPointerIconDisabledAndHoverEventOccursDoesNotCreatePointerController) { + // Add a stylus device and disable stylus pointer icon. + mChoreographer.notifyInputDevicesChanged( + {/*id=*/0, {generateTestDeviceInfo(DEVICE_ID, AINPUT_SOURCE_STYLUS, DISPLAY_ID)}}); + mChoreographer.setStylusPointerIconEnabled(false); + assertPointerControllerNotCreated(); + + // Emit hover event. Still, PointerController should not be created. + mChoreographer.notifyMotion( + MotionArgsBuilder(AMOTION_EVENT_ACTION_HOVER_ENTER, AINPUT_SOURCE_STYLUS) + .pointer(STYLUS_POINTER) + .deviceId(DEVICE_ID) + .displayId(DISPLAY_ID) + .build()); + assertPointerControllerNotCreated(); +} + +TEST_F(PointerChoreographerTest, WhenStylusDeviceIsRemovedRemovesPointerController) { + // Make sure the PointerController is created. + mChoreographer.notifyInputDevicesChanged( + {/*id=*/0, {generateTestDeviceInfo(DEVICE_ID, AINPUT_SOURCE_STYLUS, DISPLAY_ID)}}); + mChoreographer.setStylusPointerIconEnabled(true); + mChoreographer.notifyMotion( + MotionArgsBuilder(AMOTION_EVENT_ACTION_HOVER_ENTER, AINPUT_SOURCE_STYLUS) + .pointer(STYLUS_POINTER) + .deviceId(DEVICE_ID) + .displayId(DISPLAY_ID) + .build()); + auto pc = assertPointerControllerCreated(ControllerType::STYLUS); + + // Remove the device. + mChoreographer.notifyInputDevicesChanged({/*id=*/1, {}}); + assertPointerControllerRemoved(pc); +} + +TEST_F(PointerChoreographerTest, WhenStylusPointerIconDisabledRemovesPointerController) { + // Make sure the PointerController is created. + mChoreographer.notifyInputDevicesChanged( + {/*id=*/0, {generateTestDeviceInfo(DEVICE_ID, AINPUT_SOURCE_STYLUS, DISPLAY_ID)}}); + mChoreographer.setStylusPointerIconEnabled(true); + mChoreographer.notifyMotion( + MotionArgsBuilder(AMOTION_EVENT_ACTION_HOVER_ENTER, AINPUT_SOURCE_STYLUS) + .pointer(STYLUS_POINTER) + .deviceId(DEVICE_ID) + .displayId(DISPLAY_ID) + .build()); + auto pc = assertPointerControllerCreated(ControllerType::STYLUS); + + // Disable stylus pointer icon. + mChoreographer.setStylusPointerIconEnabled(false); + assertPointerControllerRemoved(pc); +} + +TEST_F(PointerChoreographerTest, SetsViewportForStylusPointerController) { + // Set viewport. + mChoreographer.setDisplayViewports(createViewports({DISPLAY_ID})); + + // Make sure the PointerController is created. + mChoreographer.notifyInputDevicesChanged( + {/*id=*/0, {generateTestDeviceInfo(DEVICE_ID, AINPUT_SOURCE_STYLUS, DISPLAY_ID)}}); + mChoreographer.setStylusPointerIconEnabled(true); + mChoreographer.notifyMotion( + MotionArgsBuilder(AMOTION_EVENT_ACTION_HOVER_ENTER, AINPUT_SOURCE_STYLUS) + .pointer(STYLUS_POINTER) + .deviceId(DEVICE_ID) + .displayId(DISPLAY_ID) + .build()); + auto pc = assertPointerControllerCreated(ControllerType::STYLUS); + + // Check that displayId is set. + ASSERT_EQ(DISPLAY_ID, pc->getDisplayId()); +} + +TEST_F(PointerChoreographerTest, WhenViewportIsSetLaterSetsViewportForStylusPointerController) { + // Make sure the PointerController is created. + mChoreographer.notifyInputDevicesChanged( + {/*id=*/0, {generateTestDeviceInfo(DEVICE_ID, AINPUT_SOURCE_STYLUS, DISPLAY_ID)}}); + mChoreographer.setStylusPointerIconEnabled(true); + mChoreographer.notifyMotion( + MotionArgsBuilder(AMOTION_EVENT_ACTION_HOVER_ENTER, AINPUT_SOURCE_STYLUS) + .pointer(STYLUS_POINTER) + .deviceId(DEVICE_ID) + .displayId(DISPLAY_ID) + .build()); + auto pc = assertPointerControllerCreated(ControllerType::STYLUS); + + // Check that displayId is unset. + ASSERT_EQ(ADISPLAY_ID_NONE, pc->getDisplayId()); + + // Set viewport. + mChoreographer.setDisplayViewports(createViewports({DISPLAY_ID})); + + // Check that displayId is set. + ASSERT_EQ(DISPLAY_ID, pc->getDisplayId()); +} + +TEST_F(PointerChoreographerTest, + WhenViewportDoesNotMatchDoesNotSetViewportForStylusPointerController) { + // Make sure the PointerController is created. + mChoreographer.notifyInputDevicesChanged( + {/*id=*/0, {generateTestDeviceInfo(DEVICE_ID, AINPUT_SOURCE_STYLUS, DISPLAY_ID)}}); + mChoreographer.setStylusPointerIconEnabled(true); + mChoreographer.notifyMotion( + MotionArgsBuilder(AMOTION_EVENT_ACTION_HOVER_ENTER, AINPUT_SOURCE_STYLUS) + .pointer(STYLUS_POINTER) + .deviceId(DEVICE_ID) + .displayId(DISPLAY_ID) + .build()); + auto pc = assertPointerControllerCreated(ControllerType::STYLUS); + + // Check that displayId is unset. + ASSERT_EQ(ADISPLAY_ID_NONE, pc->getDisplayId()); + + // Set viewport which does not match the associated display of the stylus. + mChoreographer.setDisplayViewports(createViewports({ANOTHER_DISPLAY_ID})); + + // Check that displayId is still unset. + ASSERT_EQ(ADISPLAY_ID_NONE, pc->getDisplayId()); +} + +TEST_F(PointerChoreographerTest, StylusHoverManipulatesPointer) { + mChoreographer.setStylusPointerIconEnabled(true); + mChoreographer.notifyInputDevicesChanged( + {/*id=*/0, {generateTestDeviceInfo(DEVICE_ID, AINPUT_SOURCE_STYLUS, DISPLAY_ID)}}); + mChoreographer.setDisplayViewports(createViewports({DISPLAY_ID})); + + // Emit hover enter event. This is for creating PointerController. + mChoreographer.notifyMotion( + MotionArgsBuilder(AMOTION_EVENT_ACTION_HOVER_ENTER, AINPUT_SOURCE_STYLUS) + .pointer(STYLUS_POINTER) + .deviceId(DEVICE_ID) + .displayId(DISPLAY_ID) + .build()); + auto pc = assertPointerControllerCreated(ControllerType::STYLUS); + + // Emit hover move event. After bounds are set, PointerController will update the position. + mChoreographer.notifyMotion( + MotionArgsBuilder(AMOTION_EVENT_ACTION_HOVER_MOVE, AINPUT_SOURCE_STYLUS) + .pointer(PointerBuilder(/*id=*/0, ToolType::STYLUS).x(150).y(250)) + .deviceId(DEVICE_ID) + .displayId(DISPLAY_ID) + .build()); + pc->assertPosition(150, 250); + ASSERT_TRUE(pc->isPointerShown()); + + // Emit hover exit event. + mChoreographer.notifyMotion( + MotionArgsBuilder(AMOTION_EVENT_ACTION_HOVER_EXIT, AINPUT_SOURCE_STYLUS) + .pointer(PointerBuilder(/*id=*/0, ToolType::STYLUS).x(150).y(250)) + .deviceId(DEVICE_ID) + .displayId(DISPLAY_ID) + .build()); + // Check that the pointer is gone. + ASSERT_FALSE(pc->isPointerShown()); +} + +TEST_F(PointerChoreographerTest, StylusHoverManipulatesPointerForTwoDisplays) { + mChoreographer.setStylusPointerIconEnabled(true); + // Add two stylus devices associated to different displays. + mChoreographer.notifyInputDevicesChanged( + {/*id=*/0, + {generateTestDeviceInfo(DEVICE_ID, AINPUT_SOURCE_STYLUS, DISPLAY_ID), + generateTestDeviceInfo(SECOND_DEVICE_ID, AINPUT_SOURCE_STYLUS, ANOTHER_DISPLAY_ID)}}); + mChoreographer.setDisplayViewports(createViewports({DISPLAY_ID, ANOTHER_DISPLAY_ID})); + + // Emit hover event with first device. This is for creating PointerController. + mChoreographer.notifyMotion( + MotionArgsBuilder(AMOTION_EVENT_ACTION_HOVER_ENTER, AINPUT_SOURCE_STYLUS) + .pointer(STYLUS_POINTER) + .deviceId(DEVICE_ID) + .displayId(DISPLAY_ID) + .build()); + auto firstDisplayPc = assertPointerControllerCreated(ControllerType::STYLUS); + + // Emit hover event with second device. This is for creating PointerController. + mChoreographer.notifyMotion( + MotionArgsBuilder(AMOTION_EVENT_ACTION_HOVER_ENTER, AINPUT_SOURCE_STYLUS) + .pointer(STYLUS_POINTER) + .deviceId(SECOND_DEVICE_ID) + .displayId(ANOTHER_DISPLAY_ID) + .build()); + + // There should be another PointerController created. + auto secondDisplayPc = assertPointerControllerCreated(ControllerType::STYLUS); + + // Emit hover event with first device. + mChoreographer.notifyMotion( + MotionArgsBuilder(AMOTION_EVENT_ACTION_HOVER_MOVE, AINPUT_SOURCE_STYLUS) + .pointer(PointerBuilder(/*id=*/0, ToolType::STYLUS).x(150).y(250)) + .deviceId(DEVICE_ID) + .displayId(DISPLAY_ID) + .build()); + + // Check the pointer of the first device. + firstDisplayPc->assertPosition(150, 250); + ASSERT_TRUE(firstDisplayPc->isPointerShown()); + + // Emit hover event with second device. + mChoreographer.notifyMotion( + MotionArgsBuilder(AMOTION_EVENT_ACTION_HOVER_MOVE, AINPUT_SOURCE_STYLUS) + .pointer(PointerBuilder(/*id=*/0, ToolType::STYLUS).x(250).y(350)) + .deviceId(SECOND_DEVICE_ID) + .displayId(ANOTHER_DISPLAY_ID) + .build()); + + // Check the pointer of the second device. + secondDisplayPc->assertPosition(250, 350); + ASSERT_TRUE(secondDisplayPc->isPointerShown()); + + // Check that there's no change on the pointer of the first device. + firstDisplayPc->assertPosition(150, 250); + ASSERT_TRUE(firstDisplayPc->isPointerShown()); +} + +TEST_F(PointerChoreographerTest, WhenStylusDeviceIsResetRemovesPointer) { + // Make sure the PointerController is created and there is a pointer. + mChoreographer.notifyInputDevicesChanged( + {/*id=*/0, {generateTestDeviceInfo(DEVICE_ID, AINPUT_SOURCE_STYLUS, DISPLAY_ID)}}); + mChoreographer.setStylusPointerIconEnabled(true); + mChoreographer.setDisplayViewports(createViewports({DISPLAY_ID})); + mChoreographer.notifyMotion( + MotionArgsBuilder(AMOTION_EVENT_ACTION_HOVER_ENTER, AINPUT_SOURCE_STYLUS) + .pointer(STYLUS_POINTER) + .deviceId(DEVICE_ID) + .displayId(DISPLAY_ID) + .build()); + auto pc = assertPointerControllerCreated(ControllerType::STYLUS); + ASSERT_TRUE(pc->isPointerShown()); + + // Reset the device and see the pointer controller was removed. + mChoreographer.notifyDeviceReset(NotifyDeviceResetArgs(/*id=*/1, /*eventTime=*/0, DEVICE_ID)); + assertPointerControllerRemoved(pc); +} + } // namespace android |