diff options
| author | 2023-10-06 17:03:45 +0900 | |
|---|---|---|
| committer | 2023-11-03 14:28:42 +0000 | |
| commit | da10dd34cb41f0f117fe87f568f16f6025ab3d72 (patch) | |
| tree | 39baba45bb0587d81210b6fba3be866b4144ca77 | |
| parent | 0a1d7be40a7e364cd808ba6dfad58a66620e2eef (diff) | |
Pointer icon refactor for mouse
When PointerChoreographer is enabled, CursorInputMapper no longer
depends on the legacy PointerController. PointerChoreographer is
responsible for accumulating movements, fading/unfading pointers,
and deciding display/coordinates.
Test: atest inputflinger_tests
Bug: 293587049
Change-Id: I3a4fa4ab260673076d95dbcb0832d13d0fc34c75
13 files changed, 1252 insertions, 47 deletions
diff --git a/services/inputflinger/PointerChoreographer.cpp b/services/inputflinger/PointerChoreographer.cpp index e411abb251..4dfd05f476 100644 --- a/services/inputflinger/PointerChoreographer.cpp +++ b/services/inputflinger/PointerChoreographer.cpp @@ -16,17 +16,37 @@ #define LOG_TAG "PointerChoreographer" +#include <android-base/logging.h> +#include <input/PrintTools.h> + #include "PointerChoreographer.h" +#define INDENT " " + namespace android { +namespace { +bool isFromMouse(const NotifyMotionArgs& args) { + return isFromSource(args.source, AINPUT_SOURCE_MOUSE) && + args.pointerProperties[0].toolType == ToolType::MOUSE; +} + +} // namespace + // --- PointerChoreographer --- PointerChoreographer::PointerChoreographer(InputListenerInterface& listener, PointerChoreographerPolicyInterface& policy) - : mNextListener(listener) {} + : mNextListener(listener), + mPolicy(policy), + mDefaultMouseDisplayId(ADISPLAY_ID_DEFAULT), + mNotifiedPointerDisplayId(ADISPLAY_ID_NONE) {} void PointerChoreographer::notifyInputDevicesChanged(const NotifyInputDevicesChangedArgs& args) { + std::scoped_lock _l(mLock); + + mInputDeviceInfos = args.inputDeviceInfos; + updatePointerControllersLocked(); mNextListener.notify(args); } @@ -39,7 +59,70 @@ void PointerChoreographer::notifyKey(const NotifyKeyArgs& args) { } void PointerChoreographer::notifyMotion(const NotifyMotionArgs& args) { - mNextListener.notify(args); + NotifyMotionArgs newArgs = processMotion(args); + + mNextListener.notify(newArgs); +} + +NotifyMotionArgs PointerChoreographer::processMotion(const NotifyMotionArgs& args) { + std::scoped_lock _l(mLock); + + if (isFromMouse(args)) { + return processMouseEventLocked(args); + } else if (isFromSource(args.source, AINPUT_SOURCE_TOUCHSCREEN)) { + return processTouchscreenEventLocked(args); + } + return args; +} + +NotifyMotionArgs PointerChoreographer::processMouseEventLocked(const NotifyMotionArgs& args) { + if (args.getPointerCount() != 1) { + LOG(FATAL) << "Wrong number of pointers " << args.dump(); + } + + const int32_t displayId = getTargetMouseDisplayLocked(args.displayId); + auto it = mMousePointersByDisplay.find(displayId); + if (it == mMousePointersByDisplay.end()) { + it = mMousePointersByDisplay + .insert({displayId, + mPolicy.createPointerController( + PointerControllerInterface::ControllerType::MOUSE)}) + .first; + if (const auto viewport = findViewportByIdLocked(displayId); viewport) { + it->second->setDisplayViewport(*viewport); + } + notifyPointerDisplayIdChangedLocked(); + } + + PointerControllerInterface& pc = *it->second; + + const float deltaX = args.pointerCoords[0].getAxisValue(AMOTION_EVENT_AXIS_RELATIVE_X); + const float deltaY = args.pointerCoords[0].getAxisValue(AMOTION_EVENT_AXIS_RELATIVE_Y); + pc.move(deltaX, deltaY); + pc.unfade(PointerControllerInterface::Transition::IMMEDIATE); + + const auto [x, y] = pc.getPosition(); + NotifyMotionArgs newArgs(args); + newArgs.pointerCoords[0].setAxisValue(AMOTION_EVENT_AXIS_X, x); + newArgs.pointerCoords[0].setAxisValue(AMOTION_EVENT_AXIS_Y, y); + newArgs.xCursorPosition = x; + newArgs.yCursorPosition = y; + newArgs.displayId = displayId; + return newArgs; +} + +/** + * When screen is touched, fade the mouse pointer on that display. We only call fade for + * ACTION_DOWN events.This would allow both mouse and touch to be used at the same time if the + * 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) { + 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; } void PointerChoreographer::notifySwitch(const NotifySwitchArgs& args) { @@ -60,11 +143,123 @@ void PointerChoreographer::notifyDeviceReset(const NotifyDeviceResetArgs& args) void PointerChoreographer::notifyPointerCaptureChanged( const NotifyPointerCaptureChangedArgs& args) { + if (args.request.enable) { + std::scoped_lock _l(mLock); + for (const auto& [_, mousePointerController] : mMousePointersByDisplay) { + mousePointerController->fade(PointerControllerInterface::Transition::IMMEDIATE); + } + } mNextListener.notify(args); } void PointerChoreographer::dump(std::string& dump) { + std::scoped_lock _l(mLock); + dump += "PointerChoreographer:\n"; + + 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 += "\n"; +} + +const DisplayViewport* PointerChoreographer::findViewportByIdLocked(int32_t displayId) const { + for (auto& viewport : mViewports) { + if (viewport.displayId == displayId) { + return &viewport; + } + } + return nullptr; +} + +int32_t PointerChoreographer::getTargetMouseDisplayLocked(int32_t associatedDisplayId) const { + return associatedDisplayId == ADISPLAY_ID_NONE ? mDefaultMouseDisplayId : associatedDisplayId; +} + +void PointerChoreographer::updatePointerControllersLocked() { + std::set<int32_t /*displayId*/> mouseDisplaysToKeep; + + // Mark the displayIds or deviceIds of PointerControllers currently needed. + for (const auto& info : mInputDeviceInfos) { + const uint32_t sources = info.getSources(); + if (isFromSource(sources, AINPUT_SOURCE_MOUSE) || + isFromSource(sources, AINPUT_SOURCE_MOUSE_RELATIVE)) { + const int32_t resolvedDisplayId = + getTargetMouseDisplayLocked(info.getAssociatedDisplayId()); + mouseDisplaysToKeep.insert(resolvedDisplayId); + } + } + + // 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& item) { + if (mouseDisplaysToKeep.find(item.first) == mouseDisplaysToKeep.end()) { + item.second->fade(PointerControllerInterface::Transition::IMMEDIATE); + return true; + } + return false; + }); + + // Notify the policy if there's a change on the pointer display ID. + notifyPointerDisplayIdChangedLocked(); +} + +void PointerChoreographer::notifyPointerDisplayIdChangedLocked() { + int32_t displayIdToNotify = ADISPLAY_ID_NONE; + FloatPoint cursorPosition = {0, 0}; + if (const auto it = mMousePointersByDisplay.find(mDefaultMouseDisplayId); + it != mMousePointersByDisplay.end()) { + displayIdToNotify = it->second->getDisplayId(); + cursorPosition = it->second->getPosition(); + } + + if (mNotifiedPointerDisplayId == displayIdToNotify) { + return; + } + mPolicy.notifyPointerDisplayIdChanged(displayIdToNotify, cursorPosition); + mNotifiedPointerDisplayId = displayIdToNotify; +} + +void PointerChoreographer::setDefaultMouseDisplayId(int32_t displayId) { + std::scoped_lock _l(mLock); + + mDefaultMouseDisplayId = displayId; + updatePointerControllersLocked(); +} + +void PointerChoreographer::setDisplayViewports(const std::vector<DisplayViewport>& viewports) { + std::scoped_lock _l(mLock); + for (const auto& viewport : viewports) { + int32_t displayId = viewport.displayId; + if (const auto it = mMousePointersByDisplay.find(displayId); + it != mMousePointersByDisplay.end()) { + it->second->setDisplayViewport(viewport); + } + } + mViewports = viewports; + notifyPointerDisplayIdChangedLocked(); +} + +std::optional<DisplayViewport> PointerChoreographer::getViewportForPointerDevice( + int32_t associatedDisplayId) { + std::scoped_lock _l(mLock); + const int32_t resolvedDisplayId = getTargetMouseDisplayLocked(associatedDisplayId); + if (const auto viewport = findViewportByIdLocked(resolvedDisplayId); viewport) { + return *viewport; + } + return std::nullopt; +} + +FloatPoint PointerChoreographer::getMouseCursorPosition(int32_t displayId) { + std::scoped_lock _l(mLock); + const int32_t resolvedDisplayId = getTargetMouseDisplayLocked(displayId); + if (auto it = mMousePointersByDisplay.find(resolvedDisplayId); + it != mMousePointersByDisplay.end()) { + return it->second->getPosition(); + } + return {AMOTION_EVENT_INVALID_CURSOR_POSITION, AMOTION_EVENT_INVALID_CURSOR_POSITION}; } } // namespace android diff --git a/services/inputflinger/PointerChoreographer.h b/services/inputflinger/PointerChoreographer.h index 5e5f78257b..4300d70c08 100644 --- a/services/inputflinger/PointerChoreographer.h +++ b/services/inputflinger/PointerChoreographer.h @@ -20,6 +20,8 @@ #include "NotifyArgs.h" #include "PointerChoreographerPolicyInterface.h" +#include <android-base/thread_annotations.h> + namespace android { /** @@ -31,6 +33,15 @@ namespace android { class PointerChoreographerInterface : public InputListenerInterface { public: /** + * Set the display that pointers, like the mouse cursor and drawing tablets, + * should be drawn on. + */ + virtual void setDefaultMouseDisplayId(int32_t displayId) = 0; + virtual void setDisplayViewports(const std::vector<DisplayViewport>& viewports) = 0; + virtual std::optional<DisplayViewport> getViewportForPointerDevice( + int32_t associatedDisplayId = ADISPLAY_ID_NONE) = 0; + virtual FloatPoint getMouseCursorPosition(int32_t displayId) = 0; + /** * This method may be called on any thread (usually by the input manager on a binder thread). */ virtual void dump(std::string& dump) = 0; @@ -42,6 +53,12 @@ public: PointerChoreographerPolicyInterface&); ~PointerChoreographer() override = default; + void setDefaultMouseDisplayId(int32_t displayId) override; + void setDisplayViewports(const std::vector<DisplayViewport>& viewports) override; + std::optional<DisplayViewport> getViewportForPointerDevice( + int32_t associatedDisplayId) override; + FloatPoint getMouseCursorPosition(int32_t displayId) override; + void notifyInputDevicesChanged(const NotifyInputDevicesChangedArgs& args) override; void notifyConfigurationChanged(const NotifyConfigurationChangedArgs& args) override; void notifyKey(const NotifyKeyArgs& args) override; @@ -55,7 +72,27 @@ public: void dump(std::string& dump) override; private: + void updatePointerControllersLocked() REQUIRES(mLock); + void notifyPointerDisplayIdChangedLocked() REQUIRES(mLock); + const DisplayViewport* findViewportByIdLocked(int32_t displayId) const REQUIRES(mLock); + int32_t getTargetMouseDisplayLocked(int32_t associatedDisplayId) const REQUIRES(mLock); + + NotifyMotionArgs processMotion(const NotifyMotionArgs& args); + NotifyMotionArgs processMouseEventLocked(const NotifyMotionArgs& args) REQUIRES(mLock); + NotifyMotionArgs processTouchscreenEventLocked(const NotifyMotionArgs& args) REQUIRES(mLock); + + std::mutex mLock; + InputListenerInterface& mNextListener; + PointerChoreographerPolicyInterface& mPolicy; + + std::map<int32_t, std::shared_ptr<PointerControllerInterface>> mMousePointersByDisplay + 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); }; } // namespace android diff --git a/services/inputflinger/include/InputReaderBase.h b/services/inputflinger/include/InputReaderBase.h index 25e1d21850..e9737dd2d6 100644 --- a/services/inputflinger/include/InputReaderBase.h +++ b/services/inputflinger/include/InputReaderBase.h @@ -451,6 +451,12 @@ public: /* Returns true if any InputConnection is currently active. */ virtual bool isInputMethodConnectionActive() = 0; + + /* Gets the viewport of a particular display. The logical bounds of the viewport should be used + * as the range of possible values for pointing devices, like mice and touchpads. + */ + virtual std::optional<DisplayViewport> getViewportForPointerDevice( + int32_t associatedDisplayId) = 0; }; } // namespace android diff --git a/services/inputflinger/include/PointerChoreographerPolicyInterface.h b/services/inputflinger/include/PointerChoreographerPolicyInterface.h index 9e020c7c7f..93c0bdf305 100644 --- a/services/inputflinger/include/PointerChoreographerPolicyInterface.h +++ b/services/inputflinger/include/PointerChoreographerPolicyInterface.h @@ -38,7 +38,10 @@ public: * library, libinputservice, that has the additional dependencies. The PointerController * will be mocked when testing PointerChoreographer. */ - virtual std::shared_ptr<PointerControllerInterface> createPointerController() = 0; + virtual std::shared_ptr<PointerControllerInterface> createPointerController( + PointerControllerInterface::ControllerType type) = 0; + + virtual void notifyPointerDisplayIdChanged(int32_t displayId, const FloatPoint& position) = 0; }; } // namespace android diff --git a/services/inputflinger/include/PointerControllerInterface.h b/services/inputflinger/include/PointerControllerInterface.h index 95f819a8da..8837b25378 100644 --- a/services/inputflinger/include/PointerControllerInterface.h +++ b/services/inputflinger/include/PointerControllerInterface.h @@ -52,6 +52,22 @@ protected: virtual ~PointerControllerInterface() { } public: + /** + * Enum used to differentiate various types of PointerControllers for the transition to + * using PointerChoreographer. + * + * TODO(b/293587049): Refactor the PointerController class into different controller types. + */ + enum class ControllerType { + // The PointerController that is responsible for drawing all icons. + LEGACY, + // Represents a single mouse pointer. + MOUSE, + }; + + /* Dumps the state of the pointer controller. */ + virtual std::string dump() = 0; + /* Gets the bounds of the region that the pointer can traverse. * Returns true if the bounds are available. */ virtual std::optional<FloatRect> getBounds() const = 0; diff --git a/services/inputflinger/reader/mapper/CursorInputMapper.cpp b/services/inputflinger/reader/mapper/CursorInputMapper.cpp index 79f07a5f7c..35cdba02bb 100644 --- a/services/inputflinger/reader/mapper/CursorInputMapper.cpp +++ b/services/inputflinger/reader/mapper/CursorInputMapper.cpp @@ -20,6 +20,7 @@ #include "CursorInputMapper.h" +#include <com_android_input_flags.h> #include <optional> #include "CursorButtonAccumulator.h" @@ -29,11 +30,15 @@ #include "input/PrintTools.h" +namespace input_flags = com::android::input::flags; + namespace android { // The default velocity control parameters that has no effect. static const VelocityControlParameters FLAT_VELOCITY_CONTROL_PARAMS{}; +static const DisplayViewport INVALID_VIEWPORT{}; + // --- CursorMotionAccumulator --- CursorMotionAccumulator::CursorMotionAccumulator() { @@ -87,11 +92,11 @@ void CursorInputMapper::populateDeviceInfo(InputDeviceInfo& info) { InputMapper::populateDeviceInfo(info); if (mParameters.mode == Parameters::Mode::POINTER) { - if (const auto bounds = mPointerController->getBounds(); bounds) { - info.addMotionRange(AMOTION_EVENT_AXIS_X, mSource, bounds->left, bounds->right, 0.0f, - 0.0f, 0.0f); - info.addMotionRange(AMOTION_EVENT_AXIS_Y, mSource, bounds->top, bounds->bottom, 0.0f, - 0.0f, 0.0f); + if (!mBoundsInLogicalDisplay.isEmpty()) { + info.addMotionRange(AMOTION_EVENT_AXIS_X, mSource, mBoundsInLogicalDisplay.left, + mBoundsInLogicalDisplay.right, 0.0f, 0.0f, 0.0f); + info.addMotionRange(AMOTION_EVENT_AXIS_Y, mSource, mBoundsInLogicalDisplay.top, + mBoundsInLogicalDisplay.bottom, 0.0f, 0.0f, 0.0f); } } else { info.addMotionRange(AMOTION_EVENT_AXIS_X, mSource, -1.0f, 1.0f, 0.0f, mXScale, 0.0f); @@ -283,19 +288,22 @@ std::list<NotifyArgs> CursorInputMapper::sync(nsecs_t when, nsecs_t readTime) { float xCursorPosition = AMOTION_EVENT_INVALID_CURSOR_POSITION; float yCursorPosition = AMOTION_EVENT_INVALID_CURSOR_POSITION; if (mSource == AINPUT_SOURCE_MOUSE) { - if (moved || scrolled || buttonsChanged) { - mPointerController->setPresentation(PointerControllerInterface::Presentation::POINTER); - - if (moved) { - mPointerController->move(deltaX, deltaY); + if (!input_flags::enable_pointer_choreographer()) { + if (moved || scrolled || buttonsChanged) { + mPointerController->setPresentation( + PointerControllerInterface::Presentation::POINTER); + + if (moved) { + mPointerController->move(deltaX, deltaY); + } + mPointerController->unfade(PointerControllerInterface::Transition::IMMEDIATE); } - mPointerController->unfade(PointerControllerInterface::Transition::IMMEDIATE); - } - std::tie(xCursorPosition, yCursorPosition) = mPointerController->getPosition(); + std::tie(xCursorPosition, yCursorPosition) = mPointerController->getPosition(); - pointerCoords.setAxisValue(AMOTION_EVENT_AXIS_X, xCursorPosition); - pointerCoords.setAxisValue(AMOTION_EVENT_AXIS_Y, yCursorPosition); + pointerCoords.setAxisValue(AMOTION_EVENT_AXIS_X, xCursorPosition); + pointerCoords.setAxisValue(AMOTION_EVENT_AXIS_Y, yCursorPosition); + } pointerCoords.setAxisValue(AMOTION_EVENT_AXIS_RELATIVE_X, deltaX); pointerCoords.setAxisValue(AMOTION_EVENT_AXIS_RELATIVE_Y, deltaY); } else { @@ -499,31 +507,53 @@ void CursorInputMapper::configureOnChangeDisplayInfo(const InputReaderConfigurat const bool isPointer = mParameters.mode == Parameters::Mode::POINTER; mDisplayId = ADISPLAY_ID_NONE; - if (auto viewport = mDeviceContext.getAssociatedViewport(); viewport) { + DisplayViewport resolvedViewport = INVALID_VIEWPORT; + bool isBoundsSet = false; + if (auto assocViewport = mDeviceContext.getAssociatedViewport(); assocViewport) { // This InputDevice is associated with a viewport. // Only generate events for the associated display. - const bool mismatchedPointerDisplay = - isPointer && (viewport->displayId != mPointerController->getDisplayId()); - mDisplayId = - mismatchedPointerDisplay ? std::nullopt : std::make_optional(viewport->displayId); + mDisplayId = assocViewport->displayId; + resolvedViewport = *assocViewport; + if (!input_flags::enable_pointer_choreographer()) { + const bool mismatchedPointerDisplay = + isPointer && (assocViewport->displayId != mPointerController->getDisplayId()); + if (mismatchedPointerDisplay) { + // This device's associated display doesn't match PointerController's current + // display. Do not associate it with any display. + mDisplayId.reset(); + } + } } else if (isPointer) { // The InputDevice is not associated with a viewport, but it controls the mouse pointer. - mDisplayId = mPointerController->getDisplayId(); + if (input_flags::enable_pointer_choreographer()) { + // Always use DISPLAY_ID_NONE for mouse events. + // PointerChoreographer will make it target the correct the displayId later. + const auto pointerViewport = + getContext()->getPolicy()->getViewportForPointerDevice(ADISPLAY_ID_NONE); + mDisplayId = pointerViewport ? std::make_optional(ADISPLAY_ID_NONE) : std::nullopt; + resolvedViewport = pointerViewport.value_or(INVALID_VIEWPORT); + } else { + mDisplayId = mPointerController->getDisplayId(); + if (auto v = config.getDisplayViewportById(*mDisplayId); v) { + resolvedViewport = *v; + } + if (auto bounds = mPointerController->getBounds(); bounds) { + mBoundsInLogicalDisplay = *bounds; + isBoundsSet = true; + } + } } - mOrientation = ui::ROTATION_0; - const bool isOrientedDevice = - (mParameters.orientationAware && mParameters.hasAssociatedDisplay); - // InputReader works in the un-rotated display coordinate space, so we don't need to do - // anything if the device is already orientation-aware. If the device is not - // orientation-aware, then we need to apply the inverse rotation of the display so that - // when the display rotation is applied later as a part of the per-window transform, we - // get the expected screen coordinates. When pointer capture is enabled, we do not apply any - // rotations and report values directly from the input device. - if (!isOrientedDevice && mDisplayId && mParameters.mode != Parameters::Mode::POINTER_RELATIVE) { - if (auto viewport = config.getDisplayViewportById(*mDisplayId); viewport) { - mOrientation = getInverseRotation(viewport->orientation); - } + mOrientation = (mParameters.orientationAware && mParameters.hasAssociatedDisplay) || + mParameters.mode == Parameters::Mode::POINTER_RELATIVE + ? ui::ROTATION_0 + : getInverseRotation(resolvedViewport.orientation); + + if (!isBoundsSet) { + mBoundsInLogicalDisplay = {static_cast<float>(resolvedViewport.logicalLeft), + static_cast<float>(resolvedViewport.logicalTop), + static_cast<float>(resolvedViewport.logicalRight - 1), + static_cast<float>(resolvedViewport.logicalBottom - 1)}; } bumpGeneration(); diff --git a/services/inputflinger/reader/mapper/CursorInputMapper.h b/services/inputflinger/reader/mapper/CursorInputMapper.h index b879bfdd08..e6523a5bf1 100644 --- a/services/inputflinger/reader/mapper/CursorInputMapper.h +++ b/services/inputflinger/reader/mapper/CursorInputMapper.h @@ -119,7 +119,8 @@ private: // ADISPLAY_ID_NONE to target the focused display. If there is no display target (i.e. // std::nullopt), all events will be ignored. std::optional<int32_t> mDisplayId; - ui::Rotation mOrientation; + ui::Rotation mOrientation{ui::ROTATION_0}; + FloatRect mBoundsInLogicalDisplay{}; std::shared_ptr<PointerControllerInterface> mPointerController; diff --git a/services/inputflinger/tests/FakeInputReaderPolicy.cpp b/services/inputflinger/tests/FakeInputReaderPolicy.cpp index 41c98ef9e9..305685df44 100644 --- a/services/inputflinger/tests/FakeInputReaderPolicy.cpp +++ b/services/inputflinger/tests/FakeInputReaderPolicy.cpp @@ -261,4 +261,17 @@ void FakeInputReaderPolicy::notifyStylusGestureStarted(int32_t deviceId, nsecs_t mStylusGestureNotified = deviceId; } +std::optional<DisplayViewport> FakeInputReaderPolicy::getViewportForPointerDevice( + int32_t associatedDisplayId) { + if (associatedDisplayId == ADISPLAY_ID_NONE) { + associatedDisplayId = mConfig.defaultPointerDisplayId; + } + for (auto& viewport : mViewports) { + if (viewport.displayId == associatedDisplayId) { + return std::make_optional(viewport); + } + } + return std::nullopt; +} + } // namespace android diff --git a/services/inputflinger/tests/FakeInputReaderPolicy.h b/services/inputflinger/tests/FakeInputReaderPolicy.h index 48912a6a28..084aae1725 100644 --- a/services/inputflinger/tests/FakeInputReaderPolicy.h +++ b/services/inputflinger/tests/FakeInputReaderPolicy.h @@ -79,6 +79,8 @@ public: void setStylusPointerIconEnabled(bool enabled); void setIsInputMethodConnectionActive(bool active); bool isInputMethodConnectionActive() override; + std::optional<DisplayViewport> getViewportForPointerDevice( + int32_t associatedDisplayId) override; private: void getReaderConfiguration(InputReaderConfiguration* outConfig) override; diff --git a/services/inputflinger/tests/FakePointerController.h b/services/inputflinger/tests/FakePointerController.h index c374267ff9..c75f6ed7e5 100644 --- a/services/inputflinger/tests/FakePointerController.h +++ b/services/inputflinger/tests/FakePointerController.h @@ -40,6 +40,7 @@ public: bool isPointerShown(); private: + std::string dump() override { return ""; } std::optional<FloatRect> getBounds() const override; void move(float deltaX, float deltaY) override; void fade(Transition) override; @@ -52,7 +53,7 @@ private: bool mHaveBounds{false}; float mMinX{0}, mMinY{0}, mMaxX{0}, mMaxY{0}; float mX{0}, mY{0}; - int32_t mDisplayId{ADISPLAY_ID_DEFAULT}; + int32_t mDisplayId{ADISPLAY_ID_NONE}; bool mIsPointerShown{false}; std::map<int32_t, std::vector<int32_t>> mSpotsByDisplay; diff --git a/services/inputflinger/tests/InputReader_test.cpp b/services/inputflinger/tests/InputReader_test.cpp index 64ae9e8b14..53a55f8a3e 100644 --- a/services/inputflinger/tests/InputReader_test.cpp +++ b/services/inputflinger/tests/InputReader_test.cpp @@ -37,6 +37,7 @@ #include <UinputDevice.h> #include <VibratorInputMapper.h> #include <android-base/thread_annotations.h> +#include <com_android_input_flags.h> #include <ftl/enum.h> #include <gtest/gtest.h> #include <gui/constants.h> @@ -99,6 +100,8 @@ static constexpr nsecs_t MIN_BLUETOOTH_TIMESTAMP_DELTA = ms2ns(4); // Maximum smoothing time delta so that we don't generate events too far into the future. constexpr static nsecs_t MAX_BLUETOOTH_SMOOTHING_DELTA = ms2ns(32); +namespace input_flags = com::android::input::flags; + template<typename T> static inline T min(T a, T b) { return a < b ? a : b; @@ -4097,9 +4100,9 @@ TEST_F(KeyboardInputMapperTest_ExternalDevice, DoNotWakeByDefaultBehavior) { ASSERT_EQ(POLICY_FLAG_WAKE, args.policyFlags); } -// --- CursorInputMapperTest --- +// --- CursorInputMapperTestBase --- -class CursorInputMapperTest : public InputMapperTest { +class CursorInputMapperTestBase : public InputMapperTest { protected: static const int32_t TRACKBALL_MOVEMENT_THRESHOLD; @@ -4133,11 +4136,11 @@ protected: } }; -const int32_t CursorInputMapperTest::TRACKBALL_MOVEMENT_THRESHOLD = 6; +const int32_t CursorInputMapperTestBase::TRACKBALL_MOVEMENT_THRESHOLD = 6; -void CursorInputMapperTest::testMotionRotation(CursorInputMapper& mapper, int32_t originalX, - int32_t originalY, int32_t rotatedX, - int32_t rotatedY) { +void CursorInputMapperTestBase::testMotionRotation(CursorInputMapper& mapper, int32_t originalX, + int32_t originalY, int32_t rotatedX, + int32_t rotatedY) { NotifyMotionArgs args; process(mapper, ARBITRARY_TIME, READ_TIME, EV_REL, REL_X, originalX); @@ -4151,6 +4154,16 @@ void CursorInputMapperTest::testMotionRotation(CursorInputMapper& mapper, int32_ float(rotatedY) / TRACKBALL_MOVEMENT_THRESHOLD, 0.0f)); } +// --- CursorInputMapperTest --- + +class CursorInputMapperTest : public CursorInputMapperTestBase { +protected: + void SetUp() override { + input_flags::enable_pointer_choreographer(false); + CursorInputMapperTestBase::SetUp(); + } +}; + TEST_F(CursorInputMapperTest, WhenModeIsPointer_GetSources_ReturnsMouse) { addConfigurationProperty("cursor.mode", "pointer"); CursorInputMapper& mapper = constructAndAddMapper<CursorInputMapper>(); @@ -4180,6 +4193,7 @@ TEST_F(CursorInputMapperTest, WhenModeIsPointer_PopulateDeviceInfo_ReturnsRangeF // When the bounds are set, then there should be a valid motion range. mFakePointerController->setBounds(1, 2, 800 - 1, 480 - 1); + configureDevice(InputReaderConfiguration::Change::DISPLAY_INFO); InputDeviceInfo info2; mapper.populateDeviceInfo(info2); @@ -4906,11 +4920,459 @@ TEST_F(CursorInputMapperTest, ConfigureDisplayId_IgnoresEventsForMismatchedPoint ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasNotCalled()); } +// --- CursorInputMapperTestWithChoreographer --- + +class CursorInputMapperTestWithChoreographer : public CursorInputMapperTestBase { +protected: + void SetUp() override { + input_flags::enable_pointer_choreographer(true); + CursorInputMapperTestBase::SetUp(); + } +}; + +TEST_F(CursorInputMapperTestWithChoreographer, PopulateDeviceInfo_ReturnsRangeFromPolicy) { + addConfigurationProperty("cursor.mode", "pointer"); + CursorInputMapper& mapper = constructAndAddMapper<CursorInputMapper>(); + + InputDeviceInfo info; + mapper.populateDeviceInfo(info); + + // Initially there may not be a valid motion range. + ASSERT_EQ(nullptr, info.getMotionRange(AINPUT_MOTION_RANGE_X, AINPUT_SOURCE_MOUSE)); + ASSERT_EQ(nullptr, info.getMotionRange(AINPUT_MOTION_RANGE_Y, AINPUT_SOURCE_MOUSE)); + ASSERT_NO_FATAL_FAILURE(assertMotionRange(info, AINPUT_MOTION_RANGE_PRESSURE, + AINPUT_SOURCE_MOUSE, 0.0f, 1.0f, 0.0f, 0.0f)); + + // When the viewport and the default pointer display ID is set, then there should be a valid + // motion range. + mFakePolicy->setDefaultPointerDisplayId(DISPLAY_ID); + mFakePolicy->addDisplayViewport(DISPLAY_ID, DISPLAY_WIDTH, DISPLAY_HEIGHT, ui::ROTATION_0, + /*isActive=*/true, "local:0", NO_PORT, ViewportType::INTERNAL); + configureDevice(InputReaderConfiguration::Change::DISPLAY_INFO); + + InputDeviceInfo info2; + mapper.populateDeviceInfo(info2); + + ASSERT_NO_FATAL_FAILURE(assertMotionRange(info2, AINPUT_MOTION_RANGE_X, AINPUT_SOURCE_MOUSE, 0, + DISPLAY_WIDTH - 1, 0.0f, 0.0f)); + ASSERT_NO_FATAL_FAILURE(assertMotionRange(info2, AINPUT_MOTION_RANGE_Y, AINPUT_SOURCE_MOUSE, 0, + DISPLAY_HEIGHT - 1, 0.0f, 0.0f)); + ASSERT_NO_FATAL_FAILURE(assertMotionRange(info2, AINPUT_MOTION_RANGE_PRESSURE, + AINPUT_SOURCE_MOUSE, 0.0f, 1.0f, 0.0f, 0.0f)); +} + +TEST_F(CursorInputMapperTestWithChoreographer, Process_ShouldHandleAllButtonsWithZeroCoords) { + addConfigurationProperty("cursor.mode", "pointer"); + CursorInputMapper& mapper = constructAndAddMapper<CursorInputMapper>(); + + mFakePolicy->setDefaultPointerDisplayId(DISPLAY_ID); + prepareDisplay(ui::ROTATION_0); + + mFakePointerController->setBounds(0, 0, 800 - 1, 480 - 1); + mFakePointerController->setPosition(100, 200); + + NotifyMotionArgs motionArgs; + NotifyKeyArgs keyArgs; + + // press BTN_LEFT, release BTN_LEFT + process(mapper, ARBITRARY_TIME, READ_TIME, EV_KEY, BTN_LEFT, 1); + process(mapper, ARBITRARY_TIME, READ_TIME, EV_SYN, SYN_REPORT, 0); + ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs)); + ASSERT_EQ(AMOTION_EVENT_ACTION_DOWN, motionArgs.action); + ASSERT_EQ(AMOTION_EVENT_BUTTON_PRIMARY, motionArgs.buttonState); + ASSERT_NO_FATAL_FAILURE( + assertCursorPointerCoords(motionArgs.pointerCoords[0], 0.0f, 0.0f, 1.0f)); + + ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs)); + ASSERT_EQ(AMOTION_EVENT_ACTION_BUTTON_PRESS, motionArgs.action); + ASSERT_EQ(AMOTION_EVENT_BUTTON_PRIMARY, motionArgs.buttonState); + ASSERT_NO_FATAL_FAILURE( + assertCursorPointerCoords(motionArgs.pointerCoords[0], 0.0f, 0.0f, 1.0f)); + + process(mapper, ARBITRARY_TIME, READ_TIME, EV_KEY, BTN_LEFT, 0); + process(mapper, ARBITRARY_TIME, READ_TIME, EV_SYN, SYN_REPORT, 0); + ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs)); + ASSERT_EQ(AMOTION_EVENT_ACTION_BUTTON_RELEASE, motionArgs.action); + ASSERT_EQ(0, motionArgs.buttonState); + ASSERT_NO_FATAL_FAILURE( + assertCursorPointerCoords(motionArgs.pointerCoords[0], 0.0f, 0.0f, 0.0f)); + + ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs)); + ASSERT_EQ(AMOTION_EVENT_ACTION_UP, motionArgs.action); + ASSERT_EQ(0, motionArgs.buttonState); + ASSERT_NO_FATAL_FAILURE( + assertCursorPointerCoords(motionArgs.pointerCoords[0], 0.0f, 0.0f, 0.0f)); + + ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs)); + ASSERT_EQ(AMOTION_EVENT_ACTION_HOVER_MOVE, motionArgs.action); + ASSERT_EQ(0, motionArgs.buttonState); + ASSERT_NO_FATAL_FAILURE( + assertCursorPointerCoords(motionArgs.pointerCoords[0], 0.0f, 0.0f, 0.0f)); + + // press BTN_RIGHT + BTN_MIDDLE, release BTN_RIGHT, release BTN_MIDDLE + process(mapper, ARBITRARY_TIME, READ_TIME, EV_KEY, BTN_RIGHT, 1); + process(mapper, ARBITRARY_TIME, READ_TIME, EV_KEY, BTN_MIDDLE, 1); + process(mapper, ARBITRARY_TIME, READ_TIME, EV_SYN, SYN_REPORT, 0); + ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs)); + ASSERT_EQ(AMOTION_EVENT_ACTION_DOWN, motionArgs.action); + ASSERT_EQ(AMOTION_EVENT_BUTTON_SECONDARY | AMOTION_EVENT_BUTTON_TERTIARY, + motionArgs.buttonState); + ASSERT_NO_FATAL_FAILURE( + assertCursorPointerCoords(motionArgs.pointerCoords[0], 0.0f, 0.0f, 1.0f)); + + ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs)); + ASSERT_EQ(AMOTION_EVENT_ACTION_BUTTON_PRESS, motionArgs.action); + ASSERT_EQ(AMOTION_EVENT_BUTTON_TERTIARY, motionArgs.buttonState); + ASSERT_NO_FATAL_FAILURE( + assertCursorPointerCoords(motionArgs.pointerCoords[0], 0.0f, 0.0f, 1.0f)); + + ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs)); + ASSERT_EQ(AMOTION_EVENT_ACTION_BUTTON_PRESS, motionArgs.action); + ASSERT_EQ(AMOTION_EVENT_BUTTON_SECONDARY | AMOTION_EVENT_BUTTON_TERTIARY, + motionArgs.buttonState); + ASSERT_NO_FATAL_FAILURE( + assertCursorPointerCoords(motionArgs.pointerCoords[0], 0.0f, 0.0f, 1.0f)); + + process(mapper, ARBITRARY_TIME, READ_TIME, EV_KEY, BTN_RIGHT, 0); + process(mapper, ARBITRARY_TIME, READ_TIME, EV_SYN, SYN_REPORT, 0); + ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs)); + ASSERT_EQ(AMOTION_EVENT_ACTION_BUTTON_RELEASE, motionArgs.action); + ASSERT_EQ(AMOTION_EVENT_BUTTON_TERTIARY, motionArgs.buttonState); + ASSERT_NO_FATAL_FAILURE( + assertCursorPointerCoords(motionArgs.pointerCoords[0], 0.0f, 0.0f, 1.0f)); + + ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs)); + ASSERT_EQ(AMOTION_EVENT_ACTION_MOVE, motionArgs.action); + ASSERT_EQ(AMOTION_EVENT_BUTTON_TERTIARY, motionArgs.buttonState); + ASSERT_NO_FATAL_FAILURE( + assertCursorPointerCoords(motionArgs.pointerCoords[0], 0.0f, 0.0f, 1.0f)); + + process(mapper, ARBITRARY_TIME, READ_TIME, EV_KEY, BTN_MIDDLE, 0); + process(mapper, ARBITRARY_TIME, READ_TIME, EV_SYN, SYN_REPORT, 0); + ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs)); + ASSERT_EQ(AMOTION_EVENT_ACTION_BUTTON_RELEASE, motionArgs.action); + ASSERT_EQ(0, motionArgs.buttonState); + ASSERT_NO_FATAL_FAILURE( + assertCursorPointerCoords(motionArgs.pointerCoords[0], 0.0f, 0.0f, 0.0f)); + process(mapper, ARBITRARY_TIME, READ_TIME, EV_KEY, BTN_MIDDLE, 0); + process(mapper, ARBITRARY_TIME, READ_TIME, EV_SYN, SYN_REPORT, 0); + + ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs)); + ASSERT_EQ(0, motionArgs.buttonState); + ASSERT_EQ(AMOTION_EVENT_ACTION_UP, motionArgs.action); + ASSERT_NO_FATAL_FAILURE( + assertCursorPointerCoords(motionArgs.pointerCoords[0], 0.0f, 0.0f, 0.0f)); + + ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs)); + ASSERT_EQ(0, motionArgs.buttonState); + ASSERT_EQ(AMOTION_EVENT_ACTION_HOVER_MOVE, motionArgs.action); + ASSERT_NO_FATAL_FAILURE( + assertCursorPointerCoords(motionArgs.pointerCoords[0], 0.0f, 0.0f, 0.0f)); + + // press BTN_BACK, release BTN_BACK + process(mapper, ARBITRARY_TIME, READ_TIME, EV_KEY, BTN_BACK, 1); + process(mapper, ARBITRARY_TIME, READ_TIME, EV_SYN, SYN_REPORT, 0); + ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyKeyWasCalled(&keyArgs)); + ASSERT_EQ(AKEY_EVENT_ACTION_DOWN, keyArgs.action); + ASSERT_EQ(AKEYCODE_BACK, keyArgs.keyCode); + + ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs)); + ASSERT_EQ(AMOTION_EVENT_ACTION_HOVER_MOVE, motionArgs.action); + ASSERT_EQ(AMOTION_EVENT_BUTTON_BACK, motionArgs.buttonState); + ASSERT_NO_FATAL_FAILURE( + assertCursorPointerCoords(motionArgs.pointerCoords[0], 0.0f, 0.0f, 0.0f)); + + ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs)); + ASSERT_EQ(AMOTION_EVENT_ACTION_BUTTON_PRESS, motionArgs.action); + ASSERT_EQ(AMOTION_EVENT_BUTTON_BACK, motionArgs.buttonState); + ASSERT_NO_FATAL_FAILURE( + assertCursorPointerCoords(motionArgs.pointerCoords[0], 0.0f, 0.0f, 0.0f)); + + process(mapper, ARBITRARY_TIME, READ_TIME, EV_KEY, BTN_BACK, 0); + process(mapper, ARBITRARY_TIME, READ_TIME, EV_SYN, SYN_REPORT, 0); + ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs)); + ASSERT_EQ(AMOTION_EVENT_ACTION_BUTTON_RELEASE, motionArgs.action); + ASSERT_EQ(0, motionArgs.buttonState); + ASSERT_NO_FATAL_FAILURE( + assertCursorPointerCoords(motionArgs.pointerCoords[0], 0.0f, 0.0f, 0.0f)); + + ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs)); + ASSERT_EQ(AMOTION_EVENT_ACTION_HOVER_MOVE, motionArgs.action); + ASSERT_EQ(0, motionArgs.buttonState); + + ASSERT_NO_FATAL_FAILURE( + assertCursorPointerCoords(motionArgs.pointerCoords[0], 0.0f, 0.0f, 0.0f)); + ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyKeyWasCalled(&keyArgs)); + ASSERT_EQ(AKEY_EVENT_ACTION_UP, keyArgs.action); + ASSERT_EQ(AKEYCODE_BACK, keyArgs.keyCode); + + // press BTN_SIDE, release BTN_SIDE + process(mapper, ARBITRARY_TIME, READ_TIME, EV_KEY, BTN_SIDE, 1); + process(mapper, ARBITRARY_TIME, READ_TIME, EV_SYN, SYN_REPORT, 0); + ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyKeyWasCalled(&keyArgs)); + ASSERT_EQ(AKEY_EVENT_ACTION_DOWN, keyArgs.action); + ASSERT_EQ(AKEYCODE_BACK, keyArgs.keyCode); + + ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs)); + ASSERT_EQ(AMOTION_EVENT_ACTION_HOVER_MOVE, motionArgs.action); + ASSERT_EQ(AMOTION_EVENT_BUTTON_BACK, motionArgs.buttonState); + ASSERT_NO_FATAL_FAILURE( + assertCursorPointerCoords(motionArgs.pointerCoords[0], 0.0f, 0.0f, 0.0f)); + + ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs)); + ASSERT_EQ(AMOTION_EVENT_ACTION_BUTTON_PRESS, motionArgs.action); + ASSERT_EQ(AMOTION_EVENT_BUTTON_BACK, motionArgs.buttonState); + ASSERT_NO_FATAL_FAILURE( + assertCursorPointerCoords(motionArgs.pointerCoords[0], 0.0f, 0.0f, 0.0f)); + + process(mapper, ARBITRARY_TIME, READ_TIME, EV_KEY, BTN_SIDE, 0); + process(mapper, ARBITRARY_TIME, READ_TIME, EV_SYN, SYN_REPORT, 0); + ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs)); + ASSERT_EQ(AMOTION_EVENT_ACTION_BUTTON_RELEASE, motionArgs.action); + ASSERT_EQ(0, motionArgs.buttonState); + ASSERT_NO_FATAL_FAILURE( + assertCursorPointerCoords(motionArgs.pointerCoords[0], 0.0f, 0.0f, 0.0f)); + + ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs)); + ASSERT_EQ(AMOTION_EVENT_ACTION_HOVER_MOVE, motionArgs.action); + ASSERT_EQ(0, motionArgs.buttonState); + ASSERT_NO_FATAL_FAILURE( + assertCursorPointerCoords(motionArgs.pointerCoords[0], 0.0f, 0.0f, 0.0f)); + + ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyKeyWasCalled(&keyArgs)); + ASSERT_EQ(AKEY_EVENT_ACTION_UP, keyArgs.action); + ASSERT_EQ(AKEYCODE_BACK, keyArgs.keyCode); + + // press BTN_FORWARD, release BTN_FORWARD + process(mapper, ARBITRARY_TIME, READ_TIME, EV_KEY, BTN_FORWARD, 1); + process(mapper, ARBITRARY_TIME, READ_TIME, EV_SYN, SYN_REPORT, 0); + ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyKeyWasCalled(&keyArgs)); + ASSERT_EQ(AKEY_EVENT_ACTION_DOWN, keyArgs.action); + ASSERT_EQ(AKEYCODE_FORWARD, keyArgs.keyCode); + + ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs)); + ASSERT_EQ(AMOTION_EVENT_ACTION_HOVER_MOVE, motionArgs.action); + ASSERT_EQ(AMOTION_EVENT_BUTTON_FORWARD, motionArgs.buttonState); + ASSERT_NO_FATAL_FAILURE( + assertCursorPointerCoords(motionArgs.pointerCoords[0], 0.0f, 0.0f, 0.0f)); + + ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs)); + ASSERT_EQ(AMOTION_EVENT_ACTION_BUTTON_PRESS, motionArgs.action); + ASSERT_EQ(AMOTION_EVENT_BUTTON_FORWARD, motionArgs.buttonState); + ASSERT_NO_FATAL_FAILURE( + assertCursorPointerCoords(motionArgs.pointerCoords[0], 0.0f, 0.0f, 0.0f)); + + process(mapper, ARBITRARY_TIME, READ_TIME, EV_KEY, BTN_FORWARD, 0); + process(mapper, ARBITRARY_TIME, READ_TIME, EV_SYN, SYN_REPORT, 0); + ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs)); + ASSERT_EQ(AMOTION_EVENT_ACTION_BUTTON_RELEASE, motionArgs.action); + ASSERT_EQ(0, motionArgs.buttonState); + ASSERT_NO_FATAL_FAILURE( + assertCursorPointerCoords(motionArgs.pointerCoords[0], 0.0f, 0.0f, 0.0f)); + + ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs)); + ASSERT_EQ(AMOTION_EVENT_ACTION_HOVER_MOVE, motionArgs.action); + ASSERT_EQ(0, motionArgs.buttonState); + ASSERT_NO_FATAL_FAILURE( + assertCursorPointerCoords(motionArgs.pointerCoords[0], 0.0f, 0.0f, 0.0f)); + + ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyKeyWasCalled(&keyArgs)); + ASSERT_EQ(AKEY_EVENT_ACTION_UP, keyArgs.action); + ASSERT_EQ(AKEYCODE_FORWARD, keyArgs.keyCode); + + // press BTN_EXTRA, release BTN_EXTRA + process(mapper, ARBITRARY_TIME, READ_TIME, EV_KEY, BTN_EXTRA, 1); + process(mapper, ARBITRARY_TIME, READ_TIME, EV_SYN, SYN_REPORT, 0); + ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyKeyWasCalled(&keyArgs)); + ASSERT_EQ(AKEY_EVENT_ACTION_DOWN, keyArgs.action); + ASSERT_EQ(AKEYCODE_FORWARD, keyArgs.keyCode); + + ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs)); + ASSERT_EQ(AMOTION_EVENT_ACTION_HOVER_MOVE, motionArgs.action); + ASSERT_EQ(AMOTION_EVENT_BUTTON_FORWARD, motionArgs.buttonState); + ASSERT_NO_FATAL_FAILURE( + assertCursorPointerCoords(motionArgs.pointerCoords[0], 0.0f, 0.0f, 0.0f)); + + ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs)); + ASSERT_EQ(AMOTION_EVENT_ACTION_BUTTON_PRESS, motionArgs.action); + ASSERT_EQ(AMOTION_EVENT_BUTTON_FORWARD, motionArgs.buttonState); + ASSERT_NO_FATAL_FAILURE( + assertCursorPointerCoords(motionArgs.pointerCoords[0], 0.0f, 0.0f, 0.0f)); + + process(mapper, ARBITRARY_TIME, READ_TIME, EV_KEY, BTN_EXTRA, 0); + process(mapper, ARBITRARY_TIME, READ_TIME, EV_SYN, SYN_REPORT, 0); + ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs)); + ASSERT_EQ(AMOTION_EVENT_ACTION_BUTTON_RELEASE, motionArgs.action); + ASSERT_EQ(0, motionArgs.buttonState); + ASSERT_NO_FATAL_FAILURE( + assertCursorPointerCoords(motionArgs.pointerCoords[0], 0.0f, 0.0f, 0.0f)); + + ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs)); + ASSERT_EQ(AMOTION_EVENT_ACTION_HOVER_MOVE, motionArgs.action); + ASSERT_EQ(0, motionArgs.buttonState); + ASSERT_NO_FATAL_FAILURE( + assertCursorPointerCoords(motionArgs.pointerCoords[0], 0.0f, 0.0f, 0.0f)); + + ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyKeyWasCalled(&keyArgs)); + ASSERT_EQ(AKEY_EVENT_ACTION_UP, keyArgs.action); + ASSERT_EQ(AKEYCODE_FORWARD, keyArgs.keyCode); +} + +TEST_F(CursorInputMapperTestWithChoreographer, Process_WhenModeIsPointer_ShouldKeepZeroCoords) { + addConfigurationProperty("cursor.mode", "pointer"); + CursorInputMapper& mapper = constructAndAddMapper<CursorInputMapper>(); + + mFakePolicy->setDefaultPointerDisplayId(DISPLAY_ID); + prepareDisplay(ui::ROTATION_0); + + mFakePointerController->setBounds(0, 0, 800 - 1, 480 - 1); + mFakePointerController->setPosition(100, 200); + + NotifyMotionArgs args; + + process(mapper, ARBITRARY_TIME, READ_TIME, EV_REL, REL_X, 10); + process(mapper, ARBITRARY_TIME, READ_TIME, EV_REL, REL_Y, 20); + process(mapper, ARBITRARY_TIME, READ_TIME, EV_SYN, SYN_REPORT, 0); + ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&args)); + ASSERT_EQ(AINPUT_SOURCE_MOUSE, args.source); + ASSERT_EQ(AMOTION_EVENT_ACTION_HOVER_MOVE, args.action); + ASSERT_NO_FATAL_FAILURE(assertPointerCoords(args.pointerCoords[0], 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, + 0.0f, 0.0f, 0.0f, 0.0f, 0.0f)); +} + +TEST_F(CursorInputMapperTestWithChoreographer, PointerCaptureDisablesVelocityProcessing) { + addConfigurationProperty("cursor.mode", "pointer"); + const VelocityControlParameters testParams(/*scale=*/5.f, /*low threshold=*/0.f, + /*high threshold=*/100.f, /*acceleration=*/10.f); + mFakePolicy->setVelocityControlParams(testParams); + CursorInputMapper& mapper = constructAndAddMapper<CursorInputMapper>(); + + mFakePolicy->setDefaultPointerDisplayId(DISPLAY_ID); + prepareDisplay(ui::ROTATION_0); + + NotifyDeviceResetArgs resetArgs; + ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyDeviceResetWasCalled(&resetArgs)); + ASSERT_EQ(ARBITRARY_TIME, resetArgs.eventTime); + ASSERT_EQ(DEVICE_ID, resetArgs.deviceId); + + NotifyMotionArgs args; + + // Move and verify scale is applied. + process(mapper, ARBITRARY_TIME, READ_TIME, EV_REL, REL_X, 10); + process(mapper, ARBITRARY_TIME, READ_TIME, EV_REL, REL_Y, 20); + process(mapper, ARBITRARY_TIME, READ_TIME, EV_SYN, SYN_REPORT, 0); + ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&args)); + ASSERT_EQ(AINPUT_SOURCE_MOUSE, args.source); + ASSERT_EQ(AMOTION_EVENT_ACTION_HOVER_MOVE, args.action); + const float relX = args.pointerCoords[0].getAxisValue(AMOTION_EVENT_AXIS_RELATIVE_X); + const float relY = args.pointerCoords[0].getAxisValue(AMOTION_EVENT_AXIS_RELATIVE_Y); + ASSERT_GT(relX, 10); + ASSERT_GT(relY, 20); + + // Enable Pointer Capture + mFakePolicy->setPointerCapture(true); + configureDevice(InputReaderConfiguration::Change::POINTER_CAPTURE); + NotifyPointerCaptureChangedArgs captureArgs; + ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyCaptureWasCalled(&captureArgs)); + ASSERT_TRUE(captureArgs.request.enable); + + // Move and verify scale is not applied. + process(mapper, ARBITRARY_TIME, READ_TIME, EV_REL, REL_X, 10); + process(mapper, ARBITRARY_TIME, READ_TIME, EV_REL, REL_Y, 20); + process(mapper, ARBITRARY_TIME, READ_TIME, EV_SYN, SYN_REPORT, 0); + ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&args)); + ASSERT_EQ(AINPUT_SOURCE_MOUSE_RELATIVE, args.source); + ASSERT_EQ(AMOTION_EVENT_ACTION_MOVE, args.action); + const float relX2 = args.pointerCoords[0].getAxisValue(AMOTION_EVENT_AXIS_RELATIVE_X); + const float relY2 = args.pointerCoords[0].getAxisValue(AMOTION_EVENT_AXIS_RELATIVE_Y); + ASSERT_EQ(10, relX2); + ASSERT_EQ(20, relY2); +} + +TEST_F(CursorInputMapperTestWithChoreographer, ConfigureDisplayId_NoAssociatedViewport) { + CursorInputMapper& mapper = constructAndAddMapper<CursorInputMapper>(); + + // Set up the default display. + prepareDisplay(ui::ROTATION_90); + + // Set up the secondary display as the display on which the pointer should be shown. + // The InputDevice is not associated with any display. + prepareSecondaryDisplay(); + mFakePolicy->setDefaultPointerDisplayId(SECONDARY_DISPLAY_ID); + configureDevice(InputReaderConfiguration::Change::DISPLAY_INFO); + + mFakePointerController->setBounds(0, 0, DISPLAY_WIDTH - 1, DISPLAY_HEIGHT - 1); + mFakePointerController->setPosition(100, 200); + + // Ensure input events are generated without display ID and coords, + // because they will be decided later by PointerChoreographer. + process(mapper, ARBITRARY_TIME, READ_TIME, EV_REL, REL_X, 10); + process(mapper, ARBITRARY_TIME, READ_TIME, EV_REL, REL_Y, 20); + process(mapper, ARBITRARY_TIME, READ_TIME, EV_SYN, SYN_REPORT, 0); + ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled( + AllOf(WithMotionAction(AMOTION_EVENT_ACTION_HOVER_MOVE), + WithSource(AINPUT_SOURCE_MOUSE), WithDisplayId(ADISPLAY_ID_NONE), + WithCoords(0.0f, 0.0f)))); +} + +TEST_F(CursorInputMapperTestWithChoreographer, ConfigureDisplayId_WithAssociatedViewport) { + CursorInputMapper& mapper = constructAndAddMapper<CursorInputMapper>(); + + // Set up the default display. + prepareDisplay(ui::ROTATION_90); + + // Set up the secondary display as the display on which the pointer should be shown, + // and associate the InputDevice with the secondary display. + prepareSecondaryDisplay(); + mFakePolicy->setDefaultPointerDisplayId(SECONDARY_DISPLAY_ID); + mFakePolicy->addInputUniqueIdAssociation(DEVICE_LOCATION, SECONDARY_DISPLAY_UNIQUE_ID); + configureDevice(InputReaderConfiguration::Change::DISPLAY_INFO); + + mFakePointerController->setBounds(0, 0, DISPLAY_WIDTH - 1, DISPLAY_HEIGHT - 1); + mFakePointerController->setPosition(100, 200); + + // Ensure input events are generated with associated display ID but not with coords, + // because the coords will be decided later by PointerChoreographer. + process(mapper, ARBITRARY_TIME, READ_TIME, EV_REL, REL_X, 10); + process(mapper, ARBITRARY_TIME, READ_TIME, EV_REL, REL_Y, 20); + process(mapper, ARBITRARY_TIME, READ_TIME, EV_SYN, SYN_REPORT, 0); + ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled( + AllOf(WithMotionAction(AMOTION_EVENT_ACTION_HOVER_MOVE), + WithSource(AINPUT_SOURCE_MOUSE), WithDisplayId(SECONDARY_DISPLAY_ID), + WithCoords(0.0f, 0.0f)))); +} + +TEST_F(CursorInputMapperTestWithChoreographer, + ConfigureDisplayId_ShouldGenerateEventWithMismatchedPointerDisplay) { + CursorInputMapper& mapper = constructAndAddMapper<CursorInputMapper>(); + + // Set up the default display as the display on which the pointer should be shown. + prepareDisplay(ui::ROTATION_90); + mFakePolicy->setDefaultPointerDisplayId(DISPLAY_ID); + + // Associate the InputDevice with the secondary display. + prepareSecondaryDisplay(); + mFakePolicy->addInputUniqueIdAssociation(DEVICE_LOCATION, SECONDARY_DISPLAY_UNIQUE_ID); + configureDevice(InputReaderConfiguration::Change::DISPLAY_INFO); + + // With PointerChoreographer enabled, there could be a PointerController for the associated + // display even if it is different from the pointer display. So the mapper should generate an + // event. + process(mapper, ARBITRARY_TIME, READ_TIME, EV_REL, REL_X, 10); + process(mapper, ARBITRARY_TIME, READ_TIME, EV_REL, REL_Y, 20); + process(mapper, ARBITRARY_TIME, READ_TIME, EV_SYN, SYN_REPORT, 0); + ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled( + AllOf(WithMotionAction(AMOTION_EVENT_ACTION_HOVER_MOVE), + WithSource(AINPUT_SOURCE_MOUSE), WithDisplayId(SECONDARY_DISPLAY_ID), + WithCoords(0.0f, 0.0f)))); +} + // --- BluetoothCursorInputMapperTest --- -class BluetoothCursorInputMapperTest : public CursorInputMapperTest { +class BluetoothCursorInputMapperTest : public CursorInputMapperTestBase { protected: void SetUp() override { + input_flags::enable_pointer_choreographer(false); InputMapperTest::SetUp(DEVICE_CLASSES | InputDeviceClass::EXTERNAL, BUS_BLUETOOTH); mFakePointerController = std::make_shared<FakePointerController>(); @@ -5006,6 +5468,122 @@ TEST_F(BluetoothCursorInputMapperTest, TimestampSmootheningNotUsed) { WithEventTime(expectedEventTime)))); } +// --- BluetoothCursorInputMapperTestWithChoreographer --- + +class BluetoothCursorInputMapperTestWithChoreographer : public CursorInputMapperTestBase { +protected: + void SetUp() override { + input_flags::enable_pointer_choreographer(true); + InputMapperTest::SetUp(DEVICE_CLASSES | InputDeviceClass::EXTERNAL, BUS_BLUETOOTH); + + mFakePointerController = std::make_shared<FakePointerController>(); + mFakePolicy->setPointerController(mFakePointerController); + } +}; + +TEST_F(BluetoothCursorInputMapperTestWithChoreographer, TimestampSmoothening) { + addConfigurationProperty("cursor.mode", "pointer"); + CursorInputMapper& mapper = constructAndAddMapper<CursorInputMapper>(); + + // Set up the default display. + prepareDisplay(ui::ROTATION_0); + mFakePolicy->setDefaultPointerDisplayId(DISPLAY_ID); + configureDevice(InputReaderConfiguration::Change::DISPLAY_INFO); + + nsecs_t kernelEventTime = ARBITRARY_TIME; + nsecs_t expectedEventTime = ARBITRARY_TIME; + process(mapper, kernelEventTime, READ_TIME, EV_REL, REL_X, 1); + process(mapper, kernelEventTime, READ_TIME, EV_SYN, SYN_REPORT, 0); + ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled( + AllOf(WithMotionAction(AMOTION_EVENT_ACTION_HOVER_MOVE), + WithEventTime(expectedEventTime)))); + + // Process several events that come in quick succession, according to their timestamps. + for (int i = 0; i < 3; i++) { + constexpr static nsecs_t delta = ms2ns(1); + static_assert(delta < MIN_BLUETOOTH_TIMESTAMP_DELTA); + kernelEventTime += delta; + expectedEventTime += MIN_BLUETOOTH_TIMESTAMP_DELTA; + + process(mapper, kernelEventTime, READ_TIME, EV_REL, REL_X, 1); + process(mapper, kernelEventTime, READ_TIME, EV_SYN, SYN_REPORT, 0); + ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled( + AllOf(WithMotionAction(AMOTION_EVENT_ACTION_HOVER_MOVE), + WithEventTime(expectedEventTime)))); + } +} + +TEST_F(BluetoothCursorInputMapperTestWithChoreographer, TimestampSmootheningIsCapped) { + addConfigurationProperty("cursor.mode", "pointer"); + CursorInputMapper& mapper = constructAndAddMapper<CursorInputMapper>(); + + // Set up the default display. + prepareDisplay(ui::ROTATION_0); + mFakePolicy->setDefaultPointerDisplayId(DISPLAY_ID); + configureDevice(InputReaderConfiguration::Change::DISPLAY_INFO); + + nsecs_t expectedEventTime = ARBITRARY_TIME; + process(mapper, ARBITRARY_TIME, READ_TIME, EV_REL, REL_X, 1); + process(mapper, ARBITRARY_TIME, READ_TIME, EV_SYN, SYN_REPORT, 0); + ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled( + AllOf(WithMotionAction(AMOTION_EVENT_ACTION_HOVER_MOVE), + WithEventTime(expectedEventTime)))); + + // Process several events with the same timestamp from the kernel. + // Ensure that we do not generate events too far into the future. + constexpr static int32_t numEvents = + MAX_BLUETOOTH_SMOOTHING_DELTA / MIN_BLUETOOTH_TIMESTAMP_DELTA; + for (int i = 0; i < numEvents; i++) { + expectedEventTime += MIN_BLUETOOTH_TIMESTAMP_DELTA; + + process(mapper, ARBITRARY_TIME, READ_TIME, EV_REL, REL_X, 1); + process(mapper, ARBITRARY_TIME, READ_TIME, EV_SYN, SYN_REPORT, 0); + ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled( + AllOf(WithMotionAction(AMOTION_EVENT_ACTION_HOVER_MOVE), + WithEventTime(expectedEventTime)))); + } + + // By processing more events with the same timestamp, we should not generate events with a + // timestamp that is more than the specified max time delta from the timestamp at its injection. + const nsecs_t cappedEventTime = ARBITRARY_TIME + MAX_BLUETOOTH_SMOOTHING_DELTA; + for (int i = 0; i < 3; i++) { + process(mapper, ARBITRARY_TIME, READ_TIME, EV_REL, REL_X, 1); + process(mapper, ARBITRARY_TIME, READ_TIME, EV_SYN, SYN_REPORT, 0); + ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled( + AllOf(WithMotionAction(AMOTION_EVENT_ACTION_HOVER_MOVE), + WithEventTime(cappedEventTime)))); + } +} + +TEST_F(BluetoothCursorInputMapperTestWithChoreographer, TimestampSmootheningNotUsed) { + addConfigurationProperty("cursor.mode", "pointer"); + CursorInputMapper& mapper = constructAndAddMapper<CursorInputMapper>(); + + // Set up the default display. + prepareDisplay(ui::ROTATION_0); + mFakePolicy->setDefaultPointerDisplayId(DISPLAY_ID); + configureDevice(InputReaderConfiguration::Change::DISPLAY_INFO); + + nsecs_t kernelEventTime = ARBITRARY_TIME; + nsecs_t expectedEventTime = ARBITRARY_TIME; + process(mapper, kernelEventTime, READ_TIME, EV_REL, REL_X, 1); + process(mapper, kernelEventTime, READ_TIME, EV_SYN, SYN_REPORT, 0); + ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled( + AllOf(WithMotionAction(AMOTION_EVENT_ACTION_HOVER_MOVE), + WithEventTime(expectedEventTime)))); + + // If the next event has a timestamp that is sufficiently spaced out so that Bluetooth timestamp + // smoothening is not needed, its timestamp is not affected. + kernelEventTime += MAX_BLUETOOTH_SMOOTHING_DELTA + ms2ns(1); + expectedEventTime = kernelEventTime; + + process(mapper, kernelEventTime, READ_TIME, EV_REL, REL_X, 1); + process(mapper, kernelEventTime, READ_TIME, EV_SYN, SYN_REPORT, 0); + ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled( + AllOf(WithMotionAction(AMOTION_EVENT_ACTION_HOVER_MOVE), + WithEventTime(expectedEventTime)))); +} + // --- TouchInputMapperTest --- class TouchInputMapperTest : public InputMapperTest { diff --git a/services/inputflinger/tests/PointerChoreographer_test.cpp b/services/inputflinger/tests/PointerChoreographer_test.cpp index 7237424e2a..84673a6934 100644 --- a/services/inputflinger/tests/PointerChoreographer_test.cpp +++ b/services/inputflinger/tests/PointerChoreographer_test.cpp @@ -19,24 +19,99 @@ #include <gtest/gtest.h> #include <vector> +#include "FakePointerController.h" +#include "NotifyArgsBuilders.h" +#include "TestEventMatchers.h" #include "TestInputListener.h" namespace android { +using ControllerType = PointerControllerInterface::ControllerType; + // Helpers to std::visit with lambdas. template <typename... V> struct Visitor : V... {}; template <typename... V> Visitor(V...) -> Visitor<V...>; +constexpr int32_t DEVICE_ID = 3; +constexpr int32_t DISPLAY_ID = 5; +constexpr int32_t ANOTHER_DISPLAY_ID = 10; + +static InputDeviceInfo generateTestDeviceInfo(int32_t deviceId, uint32_t source, + int32_t associatedDisplayId) { + InputDeviceIdentifier identifier; + + auto info = InputDeviceInfo(); + info.initialize(deviceId, /*generation*/ 1, /*controllerNumber*/ 1, identifier, "alias", + /*isExternal*/ false, /*hasMic*/ false, associatedDisplayId); + info.addSource(source); + return info; +} + +static std::vector<DisplayViewport> createViewports(std::vector<int32_t> displayIds) { + std::vector<DisplayViewport> viewports; + for (auto displayId : displayIds) { + DisplayViewport viewport; + viewport.displayId = displayId; + viewports.push_back(viewport); + } + return viewports; +} + // --- PointerChoreographerTest --- class PointerChoreographerTest : public testing::Test, public PointerChoreographerPolicyInterface { protected: TestInputListener mTestListener; PointerChoreographer mChoreographer{mTestListener, *this}; + std::list<std::weak_ptr<FakePointerController>> mPointerControllers{}; - std::shared_ptr<PointerControllerInterface> createPointerController() { return {}; } + std::shared_ptr<PointerControllerInterface> createPointerController(ControllerType type) { + mLastCreatedControllerType = type; + std::shared_ptr<FakePointerController> pc = std::make_shared<FakePointerController>(); + mPointerControllers.emplace_back(pc); + return pc; + } + + void notifyPointerDisplayIdChanged(int32_t displayId, const FloatPoint& position) { + mPointerDisplayIdNotified = displayId; + } + + void assertPointerControllerCreated(ControllerType type) { + ASSERT_EQ(type, mLastCreatedControllerType); + mLastCreatedControllerType.reset(); + } + + void assertPointerControllerNotCreated() { + ASSERT_EQ(std::nullopt, mLastCreatedControllerType); + } + + void assertPointerControllerCount(size_t count) { + // At first, erase ones which aren't used anymore. + auto it = mPointerControllers.begin(); + while (it != mPointerControllers.end()) { + auto pc = it->lock(); + if (!pc) { + it = mPointerControllers.erase(it); + continue; + } + it++; + } + + ASSERT_EQ(count, mPointerControllers.size()); + } + + void assertPointerDisplayIdNotified(int32_t displayId) { + ASSERT_EQ(displayId, mPointerDisplayIdNotified); + mPointerDisplayIdNotified.reset(); + } + + void assertPointerDisplayIdNotNotified() { ASSERT_EQ(std::nullopt, mPointerDisplayIdNotified); } + +private: + std::optional<ControllerType> mLastCreatedControllerType; + std::optional<int32_t> mPointerDisplayIdNotified; }; TEST_F(PointerChoreographerTest, ForwardsArgsToInnerListener) { @@ -86,4 +161,247 @@ TEST_F(PointerChoreographerTest, ForwardsArgsToInnerListener) { } } +TEST_F(PointerChoreographerTest, WhenMouseIsJustAdded_DoesNotCreatePointerController) { + mChoreographer.notifyInputDevicesChanged( + {/*id=*/0, {generateTestDeviceInfo(DEVICE_ID, AINPUT_SOURCE_MOUSE, ADISPLAY_ID_NONE)}}); + assertPointerControllerNotCreated(); + assertPointerControllerCount(size_t(0)); +} + +TEST_F(PointerChoreographerTest, WhenMouseEventOccurs_CreatesPointerController) { + 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(PointerBuilder(/*id=*/0, ToolType::MOUSE) + .axis(AMOTION_EVENT_AXIS_RELATIVE_X, 10) + .axis(AMOTION_EVENT_AXIS_RELATIVE_Y, 20)) + .deviceId(DEVICE_ID) + .displayId(ADISPLAY_ID_NONE) + .build()); + assertPointerControllerCreated(ControllerType::MOUSE); + assertPointerControllerCount(size_t(1)); +} + +TEST_F(PointerChoreographerTest, WhenMouseIsRemoved_RemovesPointerController) { + 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(PointerBuilder(/*id=*/0, ToolType::MOUSE) + .axis(AMOTION_EVENT_AXIS_RELATIVE_X, 10) + .axis(AMOTION_EVENT_AXIS_RELATIVE_Y, 20)) + .deviceId(DEVICE_ID) + .displayId(ADISPLAY_ID_NONE) + .build()); + assertPointerControllerCreated(ControllerType::MOUSE); + assertPointerControllerCount(size_t(1)); + + // Remove the mouse. + mChoreographer.notifyInputDevicesChanged({/*id=*/1, {}}); + assertPointerControllerCount(size_t(0)); +} + +TEST_F(PointerChoreographerTest, WhenKeyboardIsAdded_DoesNotCreatePointerController) { + mChoreographer.notifyInputDevicesChanged( + {/*id=*/0, + {generateTestDeviceInfo(DEVICE_ID, AINPUT_SOURCE_KEYBOARD, ADISPLAY_ID_NONE)}}); + assertPointerControllerNotCreated(); +} + +TEST_F(PointerChoreographerTest, SetsViewportForAssociatedMouse) { + // Just adding a viewport should not create a PointerController. + mChoreographer.setDisplayViewports(createViewports({DISPLAY_ID})); + assertPointerControllerCount(size_t(0)); + assertPointerControllerNotCreated(); + + // After the mouse emits event, PointerController will be created and viewport will be set. + mChoreographer.notifyInputDevicesChanged( + {/*id=*/0, {generateTestDeviceInfo(DEVICE_ID, AINPUT_SOURCE_MOUSE, DISPLAY_ID)}}); + mChoreographer.notifyMotion( + MotionArgsBuilder(AMOTION_EVENT_ACTION_HOVER_MOVE, AINPUT_SOURCE_MOUSE) + .pointer(PointerBuilder(/*id=*/0, ToolType::MOUSE) + .axis(AMOTION_EVENT_AXIS_RELATIVE_X, 10) + .axis(AMOTION_EVENT_AXIS_RELATIVE_Y, 20)) + .deviceId(DEVICE_ID) + .displayId(DISPLAY_ID) + .build()); + assertPointerControllerCount(size_t(1)); + assertPointerControllerCreated(ControllerType::MOUSE); + ASSERT_EQ(DISPLAY_ID, mPointerControllers.back().lock()->getDisplayId()); +} + +TEST_F(PointerChoreographerTest, WhenViewportSetLater_SetsViewportForAssociatedMouse) { + // Without viewport information, PointerController will be created by a mouse event + // but viewport won't be set. + mChoreographer.notifyInputDevicesChanged( + {/*id=*/0, {generateTestDeviceInfo(DEVICE_ID, AINPUT_SOURCE_MOUSE, DISPLAY_ID)}}); + mChoreographer.notifyMotion( + MotionArgsBuilder(AMOTION_EVENT_ACTION_HOVER_MOVE, AINPUT_SOURCE_MOUSE) + .pointer(PointerBuilder(/*id=*/0, ToolType::MOUSE) + .axis(AMOTION_EVENT_AXIS_RELATIVE_X, 10) + .axis(AMOTION_EVENT_AXIS_RELATIVE_Y, 20)) + .deviceId(DEVICE_ID) + .displayId(DISPLAY_ID) + .build()); + assertPointerControllerCount(size_t(1)); + assertPointerControllerCreated(ControllerType::MOUSE); + ASSERT_EQ(ADISPLAY_ID_NONE, mPointerControllers.back().lock()->getDisplayId()); + + // After Choreographer gets viewport, PointerController should also have viewport. + mChoreographer.setDisplayViewports(createViewports({DISPLAY_ID})); + ASSERT_EQ(DISPLAY_ID, mPointerControllers.back().lock()->getDisplayId()); +} + +TEST_F(PointerChoreographerTest, SetsDefaultMouseViewportForPointerController) { + mChoreographer.setDisplayViewports(createViewports({DISPLAY_ID})); + mChoreographer.setDefaultMouseDisplayId(DISPLAY_ID); + + // For a mouse event without a target display, default viewport should be set for + // the PointerController. + 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(PointerBuilder(/*id=*/0, ToolType::MOUSE) + .axis(AMOTION_EVENT_AXIS_RELATIVE_X, 10) + .axis(AMOTION_EVENT_AXIS_RELATIVE_Y, 20)) + .deviceId(DEVICE_ID) + .displayId(ADISPLAY_ID_NONE) + .build()); + assertPointerControllerCount(size_t(1)); + assertPointerControllerCreated(ControllerType::MOUSE); + ASSERT_EQ(DISPLAY_ID, mPointerControllers.back().lock()->getDisplayId()); +} + +TEST_F(PointerChoreographerTest, + WhenDefaultMouseDisplayChanges_SetsDefaultMouseViewportForPointerController) { + // Set one display as a default mouse display and emit mouse event to create PointerController. + mChoreographer.setDisplayViewports(createViewports({DISPLAY_ID, ANOTHER_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(PointerBuilder(/*id=*/0, ToolType::MOUSE) + .axis(AMOTION_EVENT_AXIS_RELATIVE_X, 10) + .axis(AMOTION_EVENT_AXIS_RELATIVE_Y, 20)) + .deviceId(DEVICE_ID) + .displayId(ADISPLAY_ID_NONE) + .build()); + assertPointerControllerCount(size_t(1)); + assertPointerControllerCreated(ControllerType::MOUSE); + ASSERT_EQ(DISPLAY_ID, mPointerControllers.back().lock()->getDisplayId()); + + // Change default mouse display. Existing PointerController should be removed. + mChoreographer.setDefaultMouseDisplayId(ANOTHER_DISPLAY_ID); + assertPointerControllerNotCreated(); + assertPointerControllerCount(size_t(0)); + + // New PointerController for the new default display will be created by the motion event. + mChoreographer.notifyMotion( + MotionArgsBuilder(AMOTION_EVENT_ACTION_HOVER_MOVE, AINPUT_SOURCE_MOUSE) + .pointer(PointerBuilder(/*id=*/0, ToolType::MOUSE) + .axis(AMOTION_EVENT_AXIS_RELATIVE_X, 10) + .axis(AMOTION_EVENT_AXIS_RELATIVE_Y, 20)) + .deviceId(DEVICE_ID) + .displayId(ADISPLAY_ID_NONE) + .build()); + assertPointerControllerCreated(ControllerType::MOUSE); + assertPointerControllerCount(size_t(1)); + ASSERT_EQ(ANOTHER_DISPLAY_ID, mPointerControllers.back().lock()->getDisplayId()); +} + +TEST_F(PointerChoreographerTest, CallsNotifyPointerDisplayIdChanged) { + mChoreographer.setDefaultMouseDisplayId(DISPLAY_ID); + mChoreographer.setDisplayViewports(createViewports({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(PointerBuilder(/*id=*/0, ToolType::MOUSE) + .axis(AMOTION_EVENT_AXIS_RELATIVE_X, 10) + .axis(AMOTION_EVENT_AXIS_RELATIVE_Y, 20)) + .deviceId(DEVICE_ID) + .displayId(ADISPLAY_ID_NONE) + .build()); + + assertPointerDisplayIdNotified(DISPLAY_ID); +} + +TEST_F(PointerChoreographerTest, WhenViewportIsSetLater_CallsNotifyPointerDisplayIdChanged) { + 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(PointerBuilder(/*id=*/0, ToolType::MOUSE) + .axis(AMOTION_EVENT_AXIS_RELATIVE_X, 10) + .axis(AMOTION_EVENT_AXIS_RELATIVE_Y, 20)) + .deviceId(DEVICE_ID) + .displayId(ADISPLAY_ID_NONE) + .build()); + assertPointerDisplayIdNotNotified(); + + mChoreographer.setDisplayViewports(createViewports({DISPLAY_ID})); + assertPointerDisplayIdNotified(DISPLAY_ID); +} + +TEST_F(PointerChoreographerTest, WhenMouseIsRemoved_CallsNotifyPointerDisplayIdChanged) { + mChoreographer.setDefaultMouseDisplayId(DISPLAY_ID); + mChoreographer.setDisplayViewports(createViewports({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(PointerBuilder(/*id=*/0, ToolType::MOUSE) + .axis(AMOTION_EVENT_AXIS_RELATIVE_X, 10) + .axis(AMOTION_EVENT_AXIS_RELATIVE_Y, 20)) + .deviceId(DEVICE_ID) + .displayId(ADISPLAY_ID_NONE) + .build()); + assertPointerDisplayIdNotified(DISPLAY_ID); + assertPointerControllerCount(size_t(1)); + + mChoreographer.notifyInputDevicesChanged({/*id=*/1, {}}); + assertPointerDisplayIdNotified(ADISPLAY_ID_NONE); + assertPointerControllerCount(size_t(0)); +} + +TEST_F(PointerChoreographerTest, + WhenDefaultMouseDisplayChanges_CallsNotifyPointerDisplayIdChanged) { + // Add two viewports. + mChoreographer.setDisplayViewports(createViewports({DISPLAY_ID, ANOTHER_DISPLAY_ID})); + + // Set one viewport as a default mouse 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(PointerBuilder(/*id=*/0, ToolType::MOUSE) + .axis(AMOTION_EVENT_AXIS_RELATIVE_X, 10) + .axis(AMOTION_EVENT_AXIS_RELATIVE_Y, 20)) + .deviceId(DEVICE_ID) + .displayId(ADISPLAY_ID_NONE) + .build()); + assertPointerDisplayIdNotified(DISPLAY_ID); + + // Set another viewport as a default mouse display ID. ADISPLAY_ID_NONE will be notified + // before a mouse event. + mChoreographer.setDefaultMouseDisplayId(ANOTHER_DISPLAY_ID); + assertPointerDisplayIdNotified(ADISPLAY_ID_NONE); + + // After a mouse event, pointer display ID will be notified with new default mouse display. + mChoreographer.notifyMotion( + MotionArgsBuilder(AMOTION_EVENT_ACTION_HOVER_MOVE, AINPUT_SOURCE_MOUSE) + .pointer(PointerBuilder(/*id=*/0, ToolType::MOUSE) + .axis(AMOTION_EVENT_AXIS_RELATIVE_X, 10) + .axis(AMOTION_EVENT_AXIS_RELATIVE_Y, 20)) + .deviceId(DEVICE_ID) + .displayId(ADISPLAY_ID_NONE) + .build()); + assertPointerDisplayIdNotified(ANOTHER_DISPLAY_ID); +} + } // namespace android diff --git a/services/inputflinger/tests/fuzzers/MapperHelpers.h b/services/inputflinger/tests/fuzzers/MapperHelpers.h index e1c0fe28d9..6245a22519 100644 --- a/services/inputflinger/tests/fuzzers/MapperHelpers.h +++ b/services/inputflinger/tests/fuzzers/MapperHelpers.h @@ -275,6 +275,7 @@ public: void clearSpots() override {} int32_t getDisplayId() const override { return mFdp->ConsumeIntegral<int32_t>(); } void setDisplayViewport(const DisplayViewport& displayViewport) override {} + std::string dump() override { return ""; } }; class FuzzInputReaderPolicy : public InputReaderPolicyInterface { @@ -309,6 +310,10 @@ public: void setTouchAffineTransformation(const TouchAffineTransformation t) { mTransform = t; } void notifyStylusGestureStarted(int32_t, nsecs_t) {} bool isInputMethodConnectionActive() override { return mFdp->ConsumeBool(); } + std::optional<DisplayViewport> getViewportForPointerDevice( + int32_t associatedDisplayId) override { + return {}; + } }; class FuzzInputListener : public virtual InputListenerInterface { |