diff options
author | 2024-02-13 11:47:54 +0100 | |
---|---|---|
committer | 2024-06-25 15:45:56 +0000 | |
commit | dd438e2a4b541fbe88b85346b450a1106b799109 (patch) | |
tree | 04d120bd2374932baec1ea35d3f5613636796fbe | |
parent | 8faf3d40232f7cef05bf588a2f5b175f687232c7 (diff) |
Virtual rotary encoder native support
- detect rotary encoder input devices if they have a single scroll
axis and nothing else
- make the rotary mapper display-aware as the virtual rotary is
constrained to a single virtual display, just like the other
virtual input devices
Fix: 320328752
Test: atest inputflinger_tests
Test: see CTS in topic
Flag: android.companion.virtualdevice.flags.virtual_rotary
Change-Id: I5581013d06708cbcc2c3ac8a622cd259aea8a9b4
-rw-r--r-- | include/input/VirtualInputDevice.h | 7 | ||||
-rw-r--r-- | libs/input/VirtualInputDevice.cpp | 11 | ||||
-rw-r--r-- | services/inputflinger/reader/Android.bp | 1 | ||||
-rw-r--r-- | services/inputflinger/reader/EventHub.cpp | 10 | ||||
-rw-r--r-- | services/inputflinger/reader/mapper/RotaryEncoderInputMapper.cpp | 20 | ||||
-rw-r--r-- | services/inputflinger/reader/mapper/RotaryEncoderInputMapper.h | 1 | ||||
-rw-r--r-- | services/inputflinger/tests/Android.bp | 1 | ||||
-rw-r--r-- | services/inputflinger/tests/RotaryEncoderInputMapper_test.cpp | 169 |
8 files changed, 212 insertions, 8 deletions
diff --git a/include/input/VirtualInputDevice.h b/include/input/VirtualInputDevice.h index 222dac8557..9bbaa0c5ec 100644 --- a/include/input/VirtualInputDevice.h +++ b/include/input/VirtualInputDevice.h @@ -122,4 +122,11 @@ private: bool handleStylusUp(uint16_t tool, std::chrono::nanoseconds eventTime); }; +class VirtualRotaryEncoder : public VirtualInputDevice { +public: + VirtualRotaryEncoder(android::base::unique_fd fd); + virtual ~VirtualRotaryEncoder() override; + bool writeScrollEvent(float scrollAmount, std::chrono::nanoseconds eventTime); +}; + } // namespace android diff --git a/libs/input/VirtualInputDevice.cpp b/libs/input/VirtualInputDevice.cpp index eea06f1720..b73ee65504 100644 --- a/libs/input/VirtualInputDevice.cpp +++ b/libs/input/VirtualInputDevice.cpp @@ -509,4 +509,15 @@ bool VirtualStylus::handleStylusUp(uint16_t tool, std::chrono::nanoseconds event return true; } +// --- VirtualRotaryEncoder --- +VirtualRotaryEncoder::VirtualRotaryEncoder(unique_fd fd) : VirtualInputDevice(std::move(fd)) {} + +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); +} + } // namespace android diff --git a/services/inputflinger/reader/Android.bp b/services/inputflinger/reader/Android.bp index e76b648ce5..b2f15b400d 100644 --- a/services/inputflinger/reader/Android.bp +++ b/services/inputflinger/reader/Android.bp @@ -78,6 +78,7 @@ cc_defaults { name: "libinputreader_defaults", srcs: [":libinputreader_sources"], shared_libs: [ + "android.companion.virtualdevice.flags-aconfig-cc-host", "libbase", "libcap", "libcrypto", diff --git a/services/inputflinger/reader/EventHub.cpp b/services/inputflinger/reader/EventHub.cpp index fe70a51b81..540f55d75d 100644 --- a/services/inputflinger/reader/EventHub.cpp +++ b/services/inputflinger/reader/EventHub.cpp @@ -33,6 +33,8 @@ #include <sys/sysmacros.h> #include <unistd.h> +#include <android_companion_virtualdevice_flags.h> + #define LOG_TAG "EventHub" // #define LOG_NDEBUG 0 @@ -68,6 +70,8 @@ using android::base::StringPrintf; namespace android { +namespace vd_flags = android::companion::virtualdevice::flags; + using namespace ftl::flag_operators; static const char* DEVICE_INPUT_PATH = "/dev/input"; @@ -2503,6 +2507,12 @@ void EventHub::openDeviceLocked(const std::string& devicePath) { } } + // See if the device is a rotary encoder with a single scroll axis and nothing else. + if (vd_flags::virtual_rotary() && device->classes == ftl::Flags<InputDeviceClass>(0) && + device->relBitmask.test(REL_WHEEL) && !device->relBitmask.test(REL_HWHEEL)) { + device->classes |= InputDeviceClass::ROTARY_ENCODER; + } + // If the device isn't recognized as something we handle, don't monitor it. if (device->classes == ftl::Flags<InputDeviceClass>(0)) { ALOGV("Dropping device: id=%d, path='%s', name='%s'", deviceId, devicePath.c_str(), diff --git a/services/inputflinger/reader/mapper/RotaryEncoderInputMapper.cpp b/services/inputflinger/reader/mapper/RotaryEncoderInputMapper.cpp index 27ff52fa65..20fd3598f7 100644 --- a/services/inputflinger/reader/mapper/RotaryEncoderInputMapper.cpp +++ b/services/inputflinger/reader/mapper/RotaryEncoderInputMapper.cpp @@ -84,12 +84,18 @@ std::list<NotifyArgs> RotaryEncoderInputMapper::reconfigure(nsecs_t when, } } if (!changes.any() || changes.test(InputReaderConfiguration::Change::DISPLAY_INFO)) { - std::optional<DisplayViewport> internalViewport = - config.getDisplayViewportByType(ViewportType::INTERNAL); - if (internalViewport) { - mOrientation = internalViewport->orientation; + if (getDeviceContext().getAssociatedViewport()) { + mDisplayId = getDeviceContext().getAssociatedViewport()->displayId; + mOrientation = getDeviceContext().getAssociatedViewport()->orientation; } else { - mOrientation = ui::ROTATION_0; + mDisplayId = ui::LogicalDisplayId::INVALID; + std::optional<DisplayViewport> internalViewport = + config.getDisplayViewportByType(ViewportType::INTERNAL); + if (internalViewport) { + mOrientation = internalViewport->orientation; + } else { + mOrientation = ui::ROTATION_0; + } } } return out; @@ -124,8 +130,6 @@ std::list<NotifyArgs> RotaryEncoderInputMapper::sync(nsecs_t when, nsecs_t readT // Send motion event. if (scrolled) { int32_t metaState = getContext()->getGlobalMetaState(); - // This is not a pointer, so it's not associated with a display. - ui::LogicalDisplayId displayId = ui::LogicalDisplayId::INVALID; if (mOrientation == ui::ROTATION_180) { scroll = -scroll; @@ -147,7 +151,7 @@ std::list<NotifyArgs> RotaryEncoderInputMapper::sync(nsecs_t when, nsecs_t readT out.push_back( NotifyMotionArgs(getContext()->getNextId(), when, readTime, getDeviceId(), mSource, - displayId, policyFlags, AMOTION_EVENT_ACTION_SCROLL, 0, 0, + mDisplayId, policyFlags, AMOTION_EVENT_ACTION_SCROLL, 0, 0, metaState, /*buttonState=*/0, MotionClassification::NONE, AMOTION_EVENT_EDGE_FLAG_NONE, 1, &pointerProperties, &pointerCoords, 0, 0, AMOTION_EVENT_INVALID_CURSOR_POSITION, diff --git a/services/inputflinger/reader/mapper/RotaryEncoderInputMapper.h b/services/inputflinger/reader/mapper/RotaryEncoderInputMapper.h index 14c540bf6e..7e804150b1 100644 --- a/services/inputflinger/reader/mapper/RotaryEncoderInputMapper.h +++ b/services/inputflinger/reader/mapper/RotaryEncoderInputMapper.h @@ -47,6 +47,7 @@ private: int32_t mSource; float mScalingFactor; ui::Rotation mOrientation; + ui::LogicalDisplayId mDisplayId = ui::LogicalDisplayId::INVALID; std::unique_ptr<SlopController> mSlopController; explicit RotaryEncoderInputMapper(InputDeviceContext& deviceContext, diff --git a/services/inputflinger/tests/Android.bp b/services/inputflinger/tests/Android.bp index cf0d46a75d..bddf43e344 100644 --- a/services/inputflinger/tests/Android.bp +++ b/services/inputflinger/tests/Android.bp @@ -76,6 +76,7 @@ cc_test { "PointerChoreographer_test.cpp", "PreferStylusOverTouch_test.cpp", "PropertyProvider_test.cpp", + "RotaryEncoderInputMapper_test.cpp", "SlopController_test.cpp", "SyncQueue_test.cpp", "TimerProvider_test.cpp", diff --git a/services/inputflinger/tests/RotaryEncoderInputMapper_test.cpp b/services/inputflinger/tests/RotaryEncoderInputMapper_test.cpp new file mode 100644 index 0000000000..94cfc3274b --- /dev/null +++ b/services/inputflinger/tests/RotaryEncoderInputMapper_test.cpp @@ -0,0 +1,169 @@ +/* + * Copyright 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "RotaryEncoderInputMapper.h" + +#include <list> +#include <string> +#include <tuple> +#include <variant> + +#include <android-base/logging.h> +#include <gtest/gtest.h> +#include <input/DisplayViewport.h> +#include <linux/input-event-codes.h> +#include <linux/input.h> +#include <utils/Timers.h> + +#include "InputMapperTest.h" +#include "InputReaderBase.h" +#include "InterfaceMocks.h" +#include "NotifyArgs.h" +#include "TestEventMatchers.h" +#include "ui/Rotation.h" + +#define TAG "RotaryEncoderInputMapper_test" + +namespace android { + +using testing::AllOf; +using testing::Return; +using testing::VariantWith; +constexpr ui::LogicalDisplayId DISPLAY_ID = ui::LogicalDisplayId::DEFAULT; +constexpr ui::LogicalDisplayId SECONDARY_DISPLAY_ID = ui::LogicalDisplayId{DISPLAY_ID.val() + 1}; +constexpr int32_t DISPLAY_WIDTH = 480; +constexpr int32_t DISPLAY_HEIGHT = 800; + +namespace { + +DisplayViewport createViewport() { + DisplayViewport v; + v.orientation = ui::Rotation::Rotation0; + v.logicalRight = DISPLAY_HEIGHT; + v.logicalBottom = DISPLAY_WIDTH; + v.physicalRight = DISPLAY_HEIGHT; + v.physicalBottom = DISPLAY_WIDTH; + v.deviceWidth = DISPLAY_HEIGHT; + v.deviceHeight = DISPLAY_WIDTH; + v.isActive = true; + return v; +} + +DisplayViewport createPrimaryViewport() { + DisplayViewport v = createViewport(); + v.displayId = DISPLAY_ID; + v.uniqueId = "local:1"; + return v; +} + +DisplayViewport createSecondaryViewport() { + DisplayViewport v = createViewport(); + v.displayId = SECONDARY_DISPLAY_ID; + v.uniqueId = "local:2"; + v.type = ViewportType::EXTERNAL; + return v; +} + +/** + * A fake InputDeviceContext that allows the associated viewport to be specified for the mapper. + * + * This is currently necessary because InputMapperUnitTest doesn't register the mappers it creates + * with the InputDevice object, meaning that InputDevice::isIgnored becomes true, and the input + * device doesn't set its associated viewport when it's configured. + * + * TODO(b/319217713): work out a way to avoid this fake. + */ +class ViewportFakingInputDeviceContext : public InputDeviceContext { +public: + ViewportFakingInputDeviceContext(InputDevice& device, int32_t eventHubId, + std::optional<DisplayViewport> viewport) + : InputDeviceContext(device, eventHubId), mAssociatedViewport(viewport) {} + + ViewportFakingInputDeviceContext(InputDevice& device, int32_t eventHubId) + : ViewportFakingInputDeviceContext(device, eventHubId, createPrimaryViewport()) {} + + std::optional<DisplayViewport> getAssociatedViewport() const override { + return mAssociatedViewport; + } + + void setViewport(const std::optional<DisplayViewport>& viewport) { + mAssociatedViewport = viewport; + } + +private: + std::optional<DisplayViewport> mAssociatedViewport; +}; + +} // namespace + +/** + * Unit tests for RotaryEncoderInputMapper. + */ +class RotaryEncoderInputMapperTest : public InputMapperUnitTest { +protected: + void SetUp() override { SetUpWithBus(BUS_USB); } + void SetUpWithBus(int bus) override { + InputMapperUnitTest::SetUpWithBus(bus); + + EXPECT_CALL(mMockEventHub, hasRelativeAxis(EVENTHUB_ID, REL_WHEEL)) + .WillRepeatedly(Return(true)); + EXPECT_CALL(mMockEventHub, hasRelativeAxis(EVENTHUB_ID, REL_HWHEEL)) + .WillRepeatedly(Return(false)); + } +}; + +TEST_F(RotaryEncoderInputMapperTest, ConfigureDisplayIdWithAssociatedViewport) { + DisplayViewport primaryViewport = createPrimaryViewport(); + DisplayViewport secondaryViewport = createSecondaryViewport(); + mReaderConfiguration.setDisplayViewports({primaryViewport, secondaryViewport}); + + // Set up the secondary display as the associated viewport of the mapper. + createDevice(); + ViewportFakingInputDeviceContext deviceContext(*mDevice, EVENTHUB_ID, secondaryViewport); + mMapper = createInputMapper<RotaryEncoderInputMapper>(deviceContext, mReaderConfiguration); + + std::list<NotifyArgs> args; + // Ensure input events are generated for the secondary display. + 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(WithMotionAction(AMOTION_EVENT_ACTION_SCROLL), + WithSource(AINPUT_SOURCE_ROTARY_ENCODER), + WithDisplayId(SECONDARY_DISPLAY_ID))))); +} + +TEST_F(RotaryEncoderInputMapperTest, ConfigureDisplayIdNoAssociatedViewport) { + // Set up the default display. + mFakePolicy->clearViewports(); + mFakePolicy->addDisplayViewport(createPrimaryViewport()); + + // Set up the mapper with no associated viewport. + createDevice(); + mMapper = createInputMapper<RotaryEncoderInputMapper>(*mDeviceContext, mReaderConfiguration); + + // Ensure input events are generated without display ID + 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(WithMotionAction(AMOTION_EVENT_ACTION_SCROLL), + WithSource(AINPUT_SOURCE_ROTARY_ENCODER), + WithDisplayId(ui::LogicalDisplayId::INVALID))))); +} + +} // namespace android
\ No newline at end of file |