diff options
| -rw-r--r-- | include/input/InputDevice.h | 3 | ||||
| -rw-r--r-- | include/input/PrintTools.h | 10 | ||||
| -rw-r--r-- | services/inputflinger/include/InputReaderBase.h | 5 | ||||
| -rw-r--r-- | services/inputflinger/reader/EventHub.cpp | 21 | ||||
| -rw-r--r-- | services/inputflinger/reader/InputReader.cpp | 38 | ||||
| -rw-r--r-- | services/inputflinger/reader/include/InputDevice.h | 3 | ||||
| -rw-r--r-- | services/inputflinger/reader/include/InputReader.h | 2 | ||||
| -rw-r--r-- | services/inputflinger/tests/InputReader_test.cpp | 92 | ||||
| -rw-r--r-- | services/inputflinger/tests/TestInputListenerMatchers.h | 8 | ||||
| -rw-r--r-- | services/inputflinger/tests/UinputDevice.cpp | 2 | ||||
| -rw-r--r-- | services/inputflinger/tests/fuzzers/InputReaderFuzzer.cpp | 5 | ||||
| -rw-r--r-- | services/inputflinger/tests/fuzzers/MapperHelpers.h | 2 |
12 files changed, 180 insertions, 11 deletions
diff --git a/include/input/InputDevice.h b/include/input/InputDevice.h index ac9c5a5a6f..e911734407 100644 --- a/include/input/InputDevice.h +++ b/include/input/InputDevice.h @@ -58,6 +58,9 @@ struct InputDeviceIdentifier { // reuse values that are not associated with an input anymore. uint16_t nonce; + // The bluetooth address of the device, if known. + std::optional<std::string> bluetoothAddress; + /** * Return InputDeviceIdentifier.name that has been adjusted as follows: * - all characters besides alphanumerics, dash, diff --git a/include/input/PrintTools.h b/include/input/PrintTools.h index 55f730b287..e24344b3f1 100644 --- a/include/input/PrintTools.h +++ b/include/input/PrintTools.h @@ -24,16 +24,20 @@ namespace android { template <typename T> -std::string constToString(const T& v) { +inline std::string constToString(const T& v) { return std::to_string(v); } +inline std::string constToString(const std::string& s) { + return s; +} + /** * Convert an optional type to string. */ template <typename T> -std::string toString(const std::optional<T>& optional, - std::string (*toString)(const T&) = constToString) { +inline std::string toString(const std::optional<T>& optional, + std::string (*toString)(const T&) = constToString) { return optional ? toString(*optional) : "<not set>"; } diff --git a/services/inputflinger/include/InputReaderBase.h b/services/inputflinger/include/InputReaderBase.h index cacb63cb8b..3b0f2ac5a2 100644 --- a/services/inputflinger/include/InputReaderBase.h +++ b/services/inputflinger/include/InputReaderBase.h @@ -142,6 +142,9 @@ public: virtual std::optional<int32_t> getLightColor(int32_t deviceId, int32_t lightId) = 0; /* Get light player ID */ virtual std::optional<int32_t> getLightPlayerId(int32_t deviceId, int32_t lightId) = 0; + + /* Get the Bluetooth address of an input device, if known. */ + virtual std::optional<std::string> getBluetoothAddress(int32_t deviceId) const = 0; }; // --- InputReaderConfiguration --- @@ -393,6 +396,8 @@ public: /* Gets the affine calibration associated with the specified device. */ virtual TouchAffineTransformation getTouchAffineTransformation( const std::string& inputDeviceDescriptor, int32_t surfaceRotation) = 0; + /* Notifies the input reader policy that a stylus gesture has started. */ + virtual void notifyStylusGestureStarted(int32_t deviceId, nsecs_t eventTime) = 0; }; } // namespace android diff --git a/services/inputflinger/reader/EventHub.cpp b/services/inputflinger/reader/EventHub.cpp index b97c46613b..18d03f8bab 100644 --- a/services/inputflinger/reader/EventHub.cpp +++ b/services/inputflinger/reader/EventHub.cpp @@ -43,6 +43,7 @@ #include <ftl/enum.h> #include <input/KeyCharacterMap.h> #include <input/KeyLayoutMap.h> +#include <input/PrintTools.h> #include <input/VirtualKeyMap.h> #include <openssl/sha.h> #include <statslog.h> @@ -134,10 +135,6 @@ const std::unordered_map<std::string, LightColor> LIGHT_COLORS = {{"red", LightC {"green", LightColor::GREEN}, {"blue", LightColor::BLUE}}; -static inline const char* toString(bool value) { - return value ? "true" : "false"; -} - static std::string sha1(const std::string& in) { SHA_CTX ctx; SHA1_Init(&ctx); @@ -2128,6 +2125,17 @@ void EventHub::openDeviceLocked(const std::string& devicePath) { identifier.uniqueId = buffer; } + // Attempt to get the bluetooth address of an input device from the uniqueId. + if (identifier.bus == BUS_BLUETOOTH && + std::regex_match(identifier.uniqueId, + std::regex("^[A-Fa-f0-9]{2}(?::[A-Fa-f0-9]{2}){5}$"))) { + identifier.bluetoothAddress = identifier.uniqueId; + // The Bluetooth stack requires alphabetic characters to be uppercase in a valid address. + for (auto& c : *identifier.bluetoothAddress) { + c = ::toupper(c); + } + } + // Fill in the descriptor. assignDescriptorLocked(identifier); @@ -2625,9 +2633,10 @@ void EventHub::dump(std::string& dump) const { dump += StringPrintf(INDENT3 "ControllerNumber: %d\n", device->controllerNumber); dump += StringPrintf(INDENT3 "UniqueId: %s\n", device->identifier.uniqueId.c_str()); dump += StringPrintf(INDENT3 "Identifier: bus=0x%04x, vendor=0x%04x, " - "product=0x%04x, version=0x%04x\n", + "product=0x%04x, version=0x%04x, bluetoothAddress=%s\n", device->identifier.bus, device->identifier.vendor, - device->identifier.product, device->identifier.version); + device->identifier.product, device->identifier.version, + toString(device->identifier.bluetoothAddress).c_str()); dump += StringPrintf(INDENT3 "KeyLayoutFile: %s\n", device->keyMap.keyLayoutFile.c_str()); dump += StringPrintf(INDENT3 "KeyCharacterMapFile: %s\n", diff --git a/services/inputflinger/reader/InputReader.cpp b/services/inputflinger/reader/InputReader.cpp index 428e999c1b..f8b1b3fa6e 100644 --- a/services/inputflinger/reader/InputReader.cpp +++ b/services/inputflinger/reader/InputReader.cpp @@ -58,6 +58,18 @@ static bool isSubDevice(const InputDeviceIdentifier& identifier1, identifier1.location == identifier2.location); } +static bool isStylusPointerGestureStart(const NotifyMotionArgs& motionArgs) { + const auto actionMasked = MotionEvent::getActionMasked(motionArgs.action); + if (actionMasked != AMOTION_EVENT_ACTION_HOVER_ENTER && + actionMasked != AMOTION_EVENT_ACTION_DOWN && + actionMasked != AMOTION_EVENT_ACTION_POINTER_DOWN) { + return false; + } + const auto actionIndex = MotionEvent::getActionIndex(motionArgs.action); + return motionArgs.pointerProperties[actionIndex].toolType == AMOTION_EVENT_TOOL_TYPE_STYLUS || + motionArgs.pointerProperties[actionIndex].toolType == AMOTION_EVENT_TOOL_TYPE_ERASER; +} + // --- InputReader --- InputReader::InputReader(std::shared_ptr<EventHubInterface> eventHub, @@ -101,8 +113,10 @@ status_t InputReader::stop() { void InputReader::loopOnce() { int32_t oldGeneration; int32_t timeoutMillis; + // Copy some state so that we can access it outside the lock later. bool inputDevicesChanged = false; std::vector<InputDeviceInfo> inputDevices; + std::list<NotifyArgs> notifyArgs; { // acquire lock std::scoped_lock _l(mLock); @@ -127,7 +141,7 @@ void InputReader::loopOnce() { mReaderIsAliveCondition.notify_all(); if (!events.empty()) { - notifyAll(processEventsLocked(events.data(), events.size())); + notifyArgs += processEventsLocked(events.data(), events.size()); } if (mNextTimeout != LLONG_MAX) { @@ -137,7 +151,7 @@ void InputReader::loopOnce() { ALOGD("Timeout expired, latency=%0.3fms", (now - mNextTimeout) * 0.000001f); } mNextTimeout = LLONG_MAX; - notifyAll(timeoutExpiredLocked(now)); + notifyArgs += timeoutExpiredLocked(now); } } @@ -152,6 +166,16 @@ void InputReader::loopOnce() { mPolicy->notifyInputDevicesChanged(inputDevices); } + // Notify the policy of the start of every new stylus gesture outside the lock. + for (const auto& args : notifyArgs) { + const auto* motionArgs = std::get_if<NotifyMotionArgs>(&args); + if (motionArgs != nullptr && isStylusPointerGestureStart(*motionArgs)) { + mPolicy->notifyStylusGestureStarted(motionArgs->deviceId, motionArgs->eventTime); + } + } + + notifyAll(std::move(notifyArgs)); + // Flush queued events out to the listener. // This must happen outside of the lock because the listener could potentially call // back into the InputReader's methods, such as getScanCodeState, or become blocked @@ -851,6 +875,16 @@ std::optional<int32_t> InputReader::getLightPlayerId(int32_t deviceId, int32_t l return std::nullopt; } +std::optional<std::string> InputReader::getBluetoothAddress(int32_t deviceId) const { + std::scoped_lock _l(mLock); + + InputDevice* device = findInputDeviceLocked(deviceId); + if (device) { + return device->getBluetoothAddress(); + } + return std::nullopt; +} + bool InputReader::isInputDeviceEnabled(int32_t deviceId) { std::scoped_lock _l(mLock); diff --git a/services/inputflinger/reader/include/InputDevice.h b/services/inputflinger/reader/include/InputDevice.h index afb1bed1f2..b9a2b4ce92 100644 --- a/services/inputflinger/reader/include/InputDevice.h +++ b/services/inputflinger/reader/include/InputDevice.h @@ -51,6 +51,9 @@ public: inline int32_t getGeneration() const { return mGeneration; } inline const std::string getName() const { return mIdentifier.name; } inline const std::string getDescriptor() { return mIdentifier.descriptor; } + inline std::optional<std::string> getBluetoothAddress() const { + return mIdentifier.bluetoothAddress; + } inline ftl::Flags<InputDeviceClass> getClasses() const { return mClasses; } inline uint32_t getSources() const { return mSources; } inline bool hasEventHubDevices() const { return !mDevices.empty(); } diff --git a/services/inputflinger/reader/include/InputReader.h b/services/inputflinger/reader/include/InputReader.h index de268cf66c..4f2503ad5c 100644 --- a/services/inputflinger/reader/include/InputReader.h +++ b/services/inputflinger/reader/include/InputReader.h @@ -113,6 +113,8 @@ public: std::optional<int32_t> getLightPlayerId(int32_t deviceId, int32_t lightId) override; + std::optional<std::string> getBluetoothAddress(int32_t deviceId) const override; + protected: // These members are protected so they can be instrumented by test cases. virtual std::shared_ptr<InputDevice> createDeviceLocked(int32_t deviceId, diff --git a/services/inputflinger/tests/InputReader_test.cpp b/services/inputflinger/tests/InputReader_test.cpp index bc70584af8..f333306f26 100644 --- a/services/inputflinger/tests/InputReader_test.cpp +++ b/services/inputflinger/tests/InputReader_test.cpp @@ -244,6 +244,7 @@ class FakeInputReaderPolicy : public InputReaderPolicyInterface { bool mInputDevicesChanged GUARDED_BY(mLock){false}; std::vector<DisplayViewport> mViewports; TouchAffineTransformation transform; + std::optional<int32_t /*deviceId*/> mStylusGestureNotified GUARDED_BY(mLock){}; protected: virtual ~FakeInputReaderPolicy() {} @@ -268,6 +269,18 @@ public: }); } + void assertStylusGestureNotified(int32_t deviceId) { + std::scoped_lock lock(mLock); + ASSERT_TRUE(mStylusGestureNotified); + ASSERT_EQ(deviceId, *mStylusGestureNotified); + mStylusGestureNotified.reset(); + } + + void assertStylusGestureNotNotified() { + std::scoped_lock lock(mLock); + ASSERT_FALSE(mStylusGestureNotified); + } + virtual void clearViewports() { mViewports.clear(); mConfig.setDisplayViewports(mViewports); @@ -428,6 +441,11 @@ private: ASSERT_NO_FATAL_FAILURE(processDevicesChanged(devicesChanged)); mInputDevicesChanged = false; } + + void notifyStylusGestureStarted(int32_t deviceId, nsecs_t eventTime) override { + std::scoped_lock<std::mutex> lock(mLock); + mStylusGestureNotified = deviceId; + } }; // --- FakeEventHub --- @@ -2329,6 +2347,15 @@ protected: mTestListener.reset(); mFakePolicy.clear(); } + + std::optional<InputDeviceInfo> findDeviceByName(const std::string& name) { + const std::vector<InputDeviceInfo> inputDevices = mFakePolicy->getInputDevices(); + const auto& it = std::find_if(inputDevices.begin(), inputDevices.end(), + [&name](const InputDeviceInfo& info) { + return info.getIdentifier().name == name; + }); + return it != inputDevices.end() ? std::make_optional(*it) : std::nullopt; + } }; TEST_F(InputReaderIntegrationTest, TestInvalidDevice) { @@ -2450,6 +2477,9 @@ protected: mDevice = createUinputDevice<UinputTouchScreen>(Rect(0, 0, DISPLAY_WIDTH, DISPLAY_HEIGHT)); ASSERT_NO_FATAL_FAILURE(mFakePolicy->assertInputDevicesChanged()); ASSERT_NO_FATAL_FAILURE(mTestListener->assertNotifyConfigurationChangedWasCalled()); + const auto info = findDeviceByName(mDevice->getName()); + ASSERT_TRUE(info); + mDeviceInfo = *info; } void setDisplayInfoAndReconfigure(int32_t displayId, int32_t width, int32_t height, @@ -2473,6 +2503,7 @@ protected: } std::unique_ptr<UinputTouchScreen> mDevice; + InputDeviceInfo mDeviceInfo; }; TEST_F(TouchIntegrationTest, InputEvent_ProcessSingleTouch) { @@ -2689,6 +2720,58 @@ TEST_F(TouchIntegrationTest, InputEvent_ProcessPalm) { ASSERT_EQ(AMOTION_EVENT_ACTION_UP, args.action); } +TEST_F(TouchIntegrationTest, NotifiesPolicyWhenStylusGestureStarted) { + const Point centerPoint = mDevice->getCenterPoint(); + + // Send down with the pen tool selected. The policy should be notified of the stylus presence. + mDevice->sendSlot(FIRST_SLOT); + mDevice->sendTrackingId(FIRST_TRACKING_ID); + mDevice->sendToolType(MT_TOOL_PEN); + mDevice->sendDown(centerPoint); + mDevice->sendSync(); + ASSERT_NO_FATAL_FAILURE(mTestListener->assertNotifyMotionWasCalled( + AllOf(WithMotionAction(AMOTION_EVENT_ACTION_DOWN), + WithToolType(AMOTION_EVENT_TOOL_TYPE_STYLUS)))); + + ASSERT_NO_FATAL_FAILURE(mFakePolicy->assertStylusGestureNotified(mDeviceInfo.getId())); + + // Release the stylus touch. + mDevice->sendUp(); + mDevice->sendSync(); + ASSERT_NO_FATAL_FAILURE( + mTestListener->assertNotifyMotionWasCalled(WithMotionAction(AMOTION_EVENT_ACTION_UP))); + + ASSERT_NO_FATAL_FAILURE(mFakePolicy->assertStylusGestureNotNotified()); + + // Touch down with the finger, without the pen tool selected. The policy is not notified. + mDevice->sendTrackingId(FIRST_TRACKING_ID); + mDevice->sendToolType(MT_TOOL_FINGER); + mDevice->sendDown(centerPoint); + mDevice->sendSync(); + ASSERT_NO_FATAL_FAILURE(mTestListener->assertNotifyMotionWasCalled( + AllOf(WithMotionAction(AMOTION_EVENT_ACTION_DOWN), + WithToolType(AMOTION_EVENT_TOOL_TYPE_FINGER)))); + + ASSERT_NO_FATAL_FAILURE(mFakePolicy->assertStylusGestureNotNotified()); + + mDevice->sendUp(); + mDevice->sendSync(); + ASSERT_NO_FATAL_FAILURE( + mTestListener->assertNotifyMotionWasCalled(WithMotionAction(AMOTION_EVENT_ACTION_UP))); + + // Send a move event with the stylus tool without BTN_TOUCH to generate a hover enter. + // The policy should be notified of the stylus presence. + mDevice->sendTrackingId(FIRST_TRACKING_ID); + mDevice->sendToolType(MT_TOOL_PEN); + mDevice->sendMove(centerPoint); + mDevice->sendSync(); + ASSERT_NO_FATAL_FAILURE(mTestListener->assertNotifyMotionWasCalled( + AllOf(WithMotionAction(AMOTION_EVENT_ACTION_HOVER_ENTER), + WithToolType(AMOTION_EVENT_TOOL_TYPE_STYLUS)))); + + ASSERT_NO_FATAL_FAILURE(mFakePolicy->assertStylusGestureNotified(mDeviceInfo.getId())); +} + // --- InputDeviceTest --- class InputDeviceTest : public testing::Test { protected: @@ -2699,6 +2782,7 @@ protected: static const int32_t DEVICE_CONTROLLER_NUMBER; static const ftl::Flags<InputDeviceClass> DEVICE_CLASSES; static const int32_t EVENTHUB_ID; + static const std::string DEVICE_BLUETOOTH_ADDRESS; std::shared_ptr<FakeEventHub> mFakeEventHub; sp<FakeInputReaderPolicy> mFakePolicy; @@ -2715,6 +2799,7 @@ protected: InputDeviceIdentifier identifier; identifier.name = DEVICE_NAME; identifier.location = DEVICE_LOCATION; + identifier.bluetoothAddress = DEVICE_BLUETOOTH_ADDRESS; mDevice = std::make_shared<InputDevice>(mReader->getContext(), DEVICE_ID, DEVICE_GENERATION, identifier); mReader->pushNextDevice(mDevice); @@ -2736,6 +2821,7 @@ const int32_t InputDeviceTest::DEVICE_CONTROLLER_NUMBER = 0; const ftl::Flags<InputDeviceClass> InputDeviceTest::DEVICE_CLASSES = InputDeviceClass::KEYBOARD | InputDeviceClass::TOUCH | InputDeviceClass::JOYSTICK; const int32_t InputDeviceTest::EVENTHUB_ID = 1; +const std::string InputDeviceTest::DEVICE_BLUETOOTH_ADDRESS = "11:AA:22:BB:33:CC"; TEST_F(InputDeviceTest, ImmutableProperties) { ASSERT_EQ(DEVICE_ID, mDevice->getId()); @@ -3002,6 +3088,12 @@ TEST_F(InputDeviceTest, DumpDoesNotCrash) { device.dump(dumpStr, eventHubDevStr); } +TEST_F(InputDeviceTest, GetBluetoothAddress) { + const auto& address = mReader->getBluetoothAddress(DEVICE_ID); + ASSERT_TRUE(address); + ASSERT_EQ(DEVICE_BLUETOOTH_ADDRESS, *address); +} + // --- InputMapperTest --- class InputMapperTest : public testing::Test { diff --git a/services/inputflinger/tests/TestInputListenerMatchers.h b/services/inputflinger/tests/TestInputListenerMatchers.h index ff7455b452..e48f1d9082 100644 --- a/services/inputflinger/tests/TestInputListenerMatchers.h +++ b/services/inputflinger/tests/TestInputListenerMatchers.h @@ -19,6 +19,7 @@ #include <android/input.h> #include <gmock/gmock.h> #include <gtest/gtest.h> +#include <input/Input.h> namespace android { @@ -62,6 +63,13 @@ MATCHER_P(WithPressure, pressure, "InputEvent with specified pressure") { return argPressure; } +MATCHER_P(WithToolType, toolType, "InputEvent with specified tool type") { + const auto argToolType = arg.pointerProperties[0].toolType; + *result_listener << "expected tool type " << motionToolTypeToString(toolType) << ", but got " + << motionToolTypeToString(argToolType); + return argToolType == toolType; +} + MATCHER_P(WithFlags, flags, "InputEvent with specified flags") { *result_listener << "expected flags " << flags << ", but got " << arg.flags; return arg.flags == flags; diff --git a/services/inputflinger/tests/UinputDevice.cpp b/services/inputflinger/tests/UinputDevice.cpp index a23c873656..626ad67c71 100644 --- a/services/inputflinger/tests/UinputDevice.cpp +++ b/services/inputflinger/tests/UinputDevice.cpp @@ -158,6 +158,8 @@ void UinputTouchScreen::configureDevice(int fd, uinput_user_dev* device) { device->absmax[ABS_MT_POSITION_Y] = mSize.bottom - 1; device->absmin[ABS_MT_TRACKING_ID] = RAW_ID_MIN; device->absmax[ABS_MT_TRACKING_ID] = RAW_ID_MAX; + device->absmin[ABS_MT_TOOL_TYPE] = MT_TOOL_FINGER; + device->absmax[ABS_MT_TOOL_TYPE] = MT_TOOL_MAX; } void UinputTouchScreen::sendSlot(int32_t slot) { diff --git a/services/inputflinger/tests/fuzzers/InputReaderFuzzer.cpp b/services/inputflinger/tests/fuzzers/InputReaderFuzzer.cpp index a9f5a3ac86..2eed9977be 100644 --- a/services/inputflinger/tests/fuzzers/InputReaderFuzzer.cpp +++ b/services/inputflinger/tests/fuzzers/InputReaderFuzzer.cpp @@ -157,6 +157,10 @@ public: return reader->getKeyCodeForKeyLocation(deviceId, locationKeyCode); } + std::optional<std::string> getBluetoothAddress(int32_t deviceId) const { + return reader->getBluetoothAddress(deviceId); + } + private: std::unique_ptr<InputReaderInterface> reader; }; @@ -273,6 +277,7 @@ extern "C" int LLVMFuzzerTestOneInput(uint8_t* data, size_t size) { std::chrono::microseconds(fdp->ConsumeIntegral<size_t>()), std::chrono::microseconds(fdp->ConsumeIntegral<size_t>())); }, + [&]() -> void { reader->getBluetoothAddress(fdp->ConsumeIntegral<int32_t>()); }, })(); } diff --git a/services/inputflinger/tests/fuzzers/MapperHelpers.h b/services/inputflinger/tests/fuzzers/MapperHelpers.h index bd81761981..64316baa3a 100644 --- a/services/inputflinger/tests/fuzzers/MapperHelpers.h +++ b/services/inputflinger/tests/fuzzers/MapperHelpers.h @@ -315,6 +315,7 @@ public: return mTransform; } void setTouchAffineTransformation(const TouchAffineTransformation t) { mTransform = t; } + void notifyStylusGestureStarted(int32_t, nsecs_t) {} }; class FuzzInputListener : public virtual InputListenerInterface { @@ -363,6 +364,7 @@ public: void updateLedMetaState(int32_t metaState) override{}; int32_t getLedMetaState() override { return mFdp->ConsumeIntegral<int32_t>(); }; + void notifyStylusGestureStarted(int32_t, nsecs_t) {} }; } // namespace android |