diff options
author | 2024-07-09 19:57:33 +0000 | |
---|---|---|
committer | 2024-07-10 10:36:06 +0000 | |
commit | ba27d1d9484c25a9ab58d13dcc59d35ad73fc7d3 (patch) | |
tree | fe80a0d5ed9c2fa2c460cd7b44cf6c139106d9b1 | |
parent | 41da946d8c62a36725a23872ab264cfed401520f (diff) |
Native support for rotary encoder high-res scroll
Test: atest RotaryEncoderInputMapperTest
Test: atest VirtualRotaryEncoderTest
Flag: android.companion.virtualdevice.flags.high_resolution_scroll
Bug: 320328752
Change-Id: Iac9092597010582bd3f55e51ee63e9eb9c8d9433
8 files changed, 115 insertions, 28 deletions
diff --git a/include/input/Input.h b/include/input/Input.h index 17672d11f7..77d7448db3 100644 --- a/include/input/Input.h +++ b/include/input/Input.h @@ -196,11 +196,11 @@ static constexpr size_t MAX_POINTERS = 16; #define MAX_POINTER_ID 31 /* - * Number of high resolution mouse scroll units for one detent (mouse wheel click), as defined in + * Number of high resolution scroll units for one detent (scroll wheel click), as defined in * evdev. This is relevant when an input device is emitting REL_WHEEL_HI_RES or REL_HWHEEL_HI_RES * events. */ -constexpr int32_t kEvdevMouseHighResScrollUnitsPerDetent = 120; +constexpr int32_t kEvdevHighResScrollUnitsPerDetent = 120; /* * Declare a concrete type for the NDK's input event forward declaration. diff --git a/include/input/VirtualInputDevice.h b/include/input/VirtualInputDevice.h index 9fbae73167..dabe45ce21 100644 --- a/include/input/VirtualInputDevice.h +++ b/include/input/VirtualInputDevice.h @@ -129,6 +129,9 @@ public: VirtualRotaryEncoder(android::base::unique_fd fd); virtual ~VirtualRotaryEncoder() override; bool writeScrollEvent(float scrollAmount, std::chrono::nanoseconds eventTime); + +private: + int32_t mAccumulatedHighResScrollAmount; }; } // namespace android diff --git a/libs/input/VirtualInputDevice.cpp b/libs/input/VirtualInputDevice.cpp index 2e3e1a09b8..0579967698 100644 --- a/libs/input/VirtualInputDevice.cpp +++ b/libs/input/VirtualInputDevice.cpp @@ -279,13 +279,17 @@ bool VirtualMouse::writeRelativeEvent(float relativeX, float relativeY, bool VirtualMouse::writeScrollEvent(float xAxisMovement, float yAxisMovement, std::chrono::nanoseconds eventTime) { if (!vd_flags::high_resolution_scroll()) { - return writeInputEvent(EV_REL, REL_HWHEEL, xAxisMovement, eventTime) && - writeInputEvent(EV_REL, REL_WHEEL, yAxisMovement, eventTime) && + return writeInputEvent(EV_REL, REL_HWHEEL, static_cast<int32_t>(xAxisMovement), + eventTime) && + writeInputEvent(EV_REL, REL_WHEEL, static_cast<int32_t>(yAxisMovement), + eventTime) && writeInputEvent(EV_SYN, SYN_REPORT, 0, eventTime); } - const int32_t highResScrollX = xAxisMovement * kEvdevMouseHighResScrollUnitsPerDetent; - const int32_t highResScrollY = yAxisMovement * kEvdevMouseHighResScrollUnitsPerDetent; + const auto highResScrollX = + static_cast<int32_t>(xAxisMovement * kEvdevHighResScrollUnitsPerDetent); + const auto highResScrollY = + static_cast<int32_t>(yAxisMovement * kEvdevHighResScrollUnitsPerDetent); bool highResScrollResult = writeInputEvent(EV_REL, REL_HWHEEL_HI_RES, highResScrollX, eventTime) && writeInputEvent(EV_REL, REL_WHEEL_HI_RES, highResScrollY, eventTime); @@ -299,19 +303,19 @@ bool VirtualMouse::writeScrollEvent(float xAxisMovement, float yAxisMovement, // (single mouse wheel click). mAccumulatedHighResScrollX += highResScrollX; mAccumulatedHighResScrollY += highResScrollY; - const int32_t scrollX = mAccumulatedHighResScrollX / kEvdevMouseHighResScrollUnitsPerDetent; - const int32_t scrollY = mAccumulatedHighResScrollY / kEvdevMouseHighResScrollUnitsPerDetent; + const int32_t scrollX = mAccumulatedHighResScrollX / kEvdevHighResScrollUnitsPerDetent; + const int32_t scrollY = mAccumulatedHighResScrollY / kEvdevHighResScrollUnitsPerDetent; if (scrollX != 0) { if (!writeInputEvent(EV_REL, REL_HWHEEL, scrollX, eventTime)) { return false; } - mAccumulatedHighResScrollX %= kEvdevMouseHighResScrollUnitsPerDetent; + mAccumulatedHighResScrollX %= kEvdevHighResScrollUnitsPerDetent; } if (scrollY != 0) { if (!writeInputEvent(EV_REL, REL_WHEEL, scrollY, eventTime)) { return false; } - mAccumulatedHighResScrollY %= kEvdevMouseHighResScrollUnitsPerDetent; + mAccumulatedHighResScrollY %= kEvdevHighResScrollUnitsPerDetent; } return writeInputEvent(EV_SYN, SYN_REPORT, 0, eventTime); @@ -550,14 +554,38 @@ bool VirtualStylus::handleStylusUp(uint16_t tool, std::chrono::nanoseconds event } // --- VirtualRotaryEncoder --- -VirtualRotaryEncoder::VirtualRotaryEncoder(unique_fd fd) : VirtualInputDevice(std::move(fd)) {} +VirtualRotaryEncoder::VirtualRotaryEncoder(unique_fd fd) + : VirtualInputDevice(std::move(fd)), mAccumulatedHighResScrollAmount(0) {} VirtualRotaryEncoder::~VirtualRotaryEncoder() {} bool VirtualRotaryEncoder::writeScrollEvent(float scrollAmount, std::chrono::nanoseconds eventTime) { - return writeInputEvent(EV_REL, REL_WHEEL, static_cast<int32_t>(scrollAmount), eventTime) && - writeInputEvent(EV_SYN, SYN_REPORT, 0, eventTime); + if (!vd_flags::high_resolution_scroll()) { + return writeInputEvent(EV_REL, REL_WHEEL, static_cast<int32_t>(scrollAmount), eventTime) && + writeInputEvent(EV_SYN, SYN_REPORT, 0, eventTime); + } + + const auto highResScrollAmount = + static_cast<int32_t>(scrollAmount * kEvdevHighResScrollUnitsPerDetent); + if (!writeInputEvent(EV_REL, REL_WHEEL_HI_RES, highResScrollAmount, eventTime)) { + return false; + } + + // According to evdev spec, a high-resolution scroll device needs to emit REL_WHEEL / REL_HWHEEL + // events in addition to high-res scroll events. Regular scroll events can approximate high-res + // scroll events, so we send a regular scroll event when the accumulated scroll motion reaches a + // detent (single wheel click). + mAccumulatedHighResScrollAmount += highResScrollAmount; + const int32_t scroll = mAccumulatedHighResScrollAmount / kEvdevHighResScrollUnitsPerDetent; + if (scroll != 0) { + if (!writeInputEvent(EV_REL, REL_WHEEL, scroll, eventTime)) { + return false; + } + mAccumulatedHighResScrollAmount %= kEvdevHighResScrollUnitsPerDetent; + } + + return writeInputEvent(EV_SYN, SYN_REPORT, 0, eventTime); } } // namespace android diff --git a/services/inputflinger/reader/mapper/RotaryEncoderInputMapper.cpp b/services/inputflinger/reader/mapper/RotaryEncoderInputMapper.cpp index 20fd3598f7..b72cc6e060 100644 --- a/services/inputflinger/reader/mapper/RotaryEncoderInputMapper.cpp +++ b/services/inputflinger/reader/mapper/RotaryEncoderInputMapper.cpp @@ -27,11 +27,14 @@ namespace android { +constexpr float kDefaultScaleFactor = 1.0f; + RotaryEncoderInputMapper::RotaryEncoderInputMapper(InputDeviceContext& deviceContext, const InputReaderConfiguration& readerConfig) - : InputMapper(deviceContext, readerConfig), mOrientation(ui::ROTATION_0) { - mSource = AINPUT_SOURCE_ROTARY_ENCODER; -} + : InputMapper(deviceContext, readerConfig), + mSource(AINPUT_SOURCE_ROTARY_ENCODER), + mScalingFactor(kDefaultScaleFactor), + mOrientation(ui::ROTATION_0) {} RotaryEncoderInputMapper::~RotaryEncoderInputMapper() {} @@ -51,9 +54,10 @@ void RotaryEncoderInputMapper::populateDeviceInfo(InputDeviceInfo& info) { std::optional<float> scalingFactor = config.getFloat("device.scalingFactor"); if (!scalingFactor.has_value()) { ALOGW("Rotary Encoder device configuration file didn't specify scaling factor," - "default to 1.0!\n"); + "default to %f!\n", + kDefaultScaleFactor); } - mScalingFactor = scalingFactor.value_or(1.0f); + mScalingFactor = scalingFactor.value_or(kDefaultScaleFactor); info.addMotionRange(AMOTION_EVENT_AXIS_SCROLL, mSource, -1.0f, 1.0f, 0.0f, 0.0f, res.value_or(0.0f) * mScalingFactor); } diff --git a/services/inputflinger/reader/mapper/accumulator/CursorScrollAccumulator.cpp b/services/inputflinger/reader/mapper/accumulator/CursorScrollAccumulator.cpp index 06315e2259..537344048b 100644 --- a/services/inputflinger/reader/mapper/accumulator/CursorScrollAccumulator.cpp +++ b/services/inputflinger/reader/mapper/accumulator/CursorScrollAccumulator.cpp @@ -55,14 +55,14 @@ void CursorScrollAccumulator::process(const RawEvent& rawEvent) { switch (rawEvent.code) { case REL_WHEEL_HI_RES: if (mHaveRelWheelHighRes) { - mRelWheel = rawEvent.value / - static_cast<float>(kEvdevMouseHighResScrollUnitsPerDetent); + mRelWheel = + rawEvent.value / static_cast<float>(kEvdevHighResScrollUnitsPerDetent); } break; case REL_HWHEEL_HI_RES: if (mHaveRelHWheelHighRes) { - mRelHWheel = rawEvent.value / - static_cast<float>(kEvdevMouseHighResScrollUnitsPerDetent); + mRelHWheel = + rawEvent.value / static_cast<float>(kEvdevHighResScrollUnitsPerDetent); } break; case REL_WHEEL: diff --git a/services/inputflinger/reader/mapper/accumulator/CursorScrollAccumulator.h b/services/inputflinger/reader/mapper/accumulator/CursorScrollAccumulator.h index 6990d208f9..d3373ccd33 100644 --- a/services/inputflinger/reader/mapper/accumulator/CursorScrollAccumulator.h +++ b/services/inputflinger/reader/mapper/accumulator/CursorScrollAccumulator.h @@ -16,8 +16,6 @@ #pragma once -#include <stdint.h> - namespace android { class InputDeviceContext; @@ -36,8 +34,6 @@ public: inline bool haveRelativeVWheel() const { return mHaveRelWheel; } inline bool haveRelativeHWheel() const { return mHaveRelHWheel; } - inline int32_t getRelativeX() const { return mRelX; } - inline int32_t getRelativeY() const { return mRelY; } inline float getRelativeVWheel() const { return mRelWheel; } inline float getRelativeHWheel() const { return mRelHWheel; } @@ -47,8 +43,6 @@ private: bool mHaveRelWheelHighRes; bool mHaveRelHWheelHighRes; - int32_t mRelX; - int32_t mRelY; float mRelWheel; float mRelHWheel; diff --git a/services/inputflinger/tests/RotaryEncoderInputMapper_test.cpp b/services/inputflinger/tests/RotaryEncoderInputMapper_test.cpp index 2b8071bd88..366b3dcdd0 100644 --- a/services/inputflinger/tests/RotaryEncoderInputMapper_test.cpp +++ b/services/inputflinger/tests/RotaryEncoderInputMapper_test.cpp @@ -22,6 +22,7 @@ #include <variant> #include <android-base/logging.h> +#include <android_companion_virtualdevice_flags.h> #include <gtest/gtest.h> #include <input/DisplayViewport.h> #include <linux/input-event-codes.h> @@ -109,6 +110,8 @@ private: } // namespace +namespace vd_flags = android::companion::virtualdevice::flags; + /** * Unit tests for RotaryEncoderInputMapper. */ @@ -170,4 +173,53 @@ TEST_F(RotaryEncoderInputMapperTest, ConfigureDisplayIdNoAssociatedViewport) { WithDisplayId(ui::LogicalDisplayId::INVALID))))); } +TEST_F(RotaryEncoderInputMapperTest, ProcessRegularScroll) { + createDevice(); + mMapper = createInputMapper<RotaryEncoderInputMapper>(*mDeviceContext, mReaderConfiguration); + + std::list<NotifyArgs> args; + args += process(ARBITRARY_TIME, EV_REL, REL_WHEEL, 1); + args += process(ARBITRARY_TIME, EV_SYN, SYN_REPORT, 0); + + EXPECT_THAT(args, + ElementsAre(VariantWith<NotifyMotionArgs>( + AllOf(WithSource(AINPUT_SOURCE_ROTARY_ENCODER), + WithMotionAction(AMOTION_EVENT_ACTION_SCROLL), WithScroll(1.0f))))); +} + +TEST_F(RotaryEncoderInputMapperTest, ProcessHighResScroll) { + vd_flags::high_resolution_scroll(true); + EXPECT_CALL(mMockEventHub, hasRelativeAxis(EVENTHUB_ID, REL_WHEEL_HI_RES)) + .WillRepeatedly(Return(true)); + createDevice(); + mMapper = createInputMapper<RotaryEncoderInputMapper>(*mDeviceContext, mReaderConfiguration); + + std::list<NotifyArgs> args; + args += process(ARBITRARY_TIME, EV_REL, REL_WHEEL_HI_RES, 60); + args += process(ARBITRARY_TIME, EV_SYN, SYN_REPORT, 0); + + EXPECT_THAT(args, + ElementsAre(VariantWith<NotifyMotionArgs>( + AllOf(WithSource(AINPUT_SOURCE_ROTARY_ENCODER), + WithMotionAction(AMOTION_EVENT_ACTION_SCROLL), WithScroll(0.5f))))); +} + +TEST_F(RotaryEncoderInputMapperTest, HighResScrollIgnoresRegularScroll) { + vd_flags::high_resolution_scroll(true); + EXPECT_CALL(mMockEventHub, hasRelativeAxis(EVENTHUB_ID, REL_WHEEL_HI_RES)) + .WillRepeatedly(Return(true)); + createDevice(); + mMapper = createInputMapper<RotaryEncoderInputMapper>(*mDeviceContext, mReaderConfiguration); + + std::list<NotifyArgs> args; + args += process(ARBITRARY_TIME, EV_REL, REL_WHEEL_HI_RES, 60); + args += process(ARBITRARY_TIME, EV_REL, REL_WHEEL, 1); + args += process(ARBITRARY_TIME, EV_SYN, SYN_REPORT, 0); + + EXPECT_THAT(args, + ElementsAre(VariantWith<NotifyMotionArgs>( + AllOf(WithSource(AINPUT_SOURCE_ROTARY_ENCODER), + WithMotionAction(AMOTION_EVENT_ACTION_SCROLL), WithScroll(0.5f))))); +} + } // namespace android
\ No newline at end of file diff --git a/services/inputflinger/tests/TestEventMatchers.h b/services/inputflinger/tests/TestEventMatchers.h index f643fb11e5..f8e3c22ae9 100644 --- a/services/inputflinger/tests/TestEventMatchers.h +++ b/services/inputflinger/tests/TestEventMatchers.h @@ -697,6 +697,12 @@ MATCHER_P(WithDistance, distance, "MotionEvent with specified distance") { return argDistance == distance; } +MATCHER_P(WithScroll, scroll, "InputEvent with specified scroll value") { + const auto argScroll = arg.pointerCoords[0].getAxisValue(AMOTION_EVENT_AXIS_SCROLL); + *result_listener << "expected scroll value " << scroll << ", but got " << argScroll; + return argScroll == scroll; +} + MATCHER_P2(WithScroll, scrollX, scrollY, "InputEvent with specified scroll values") { const auto argScrollX = arg.pointerCoords[0].getAxisValue(AMOTION_EVENT_AXIS_HSCROLL); const auto argScrollY = arg.pointerCoords[0].getAxisValue(AMOTION_EVENT_AXIS_VSCROLL); |