diff options
| author | 2023-11-24 18:19:56 +0000 | |
|---|---|---|
| committer | 2024-01-31 15:26:57 +0000 | |
| commit | cb7de99672a8aee59438bc783bdaceec9ecf9d4e (patch) | |
| tree | ee918f97dffbb0297407a4b4ecfadf100b5e851c | |
| parent | 9bb81c04efa74efb7c2411b17682298f814abda6 (diff) | |
Sync MT slots on reset and buffer overflow
At present we don't repopulate the MT slot values when a buffer overflow
occurs. This may lead to incosistent event being generate due to out of
sync Mt Slot values.
In this change we repopulate the MT slot values with EVIOCGMTSLOTS
system call after resetting the mapper. Now in case of a buffer overflow
a ongoing multitouch gesture will be cancelled and restarted.
In addition to cherry pick this change also includes some modifications
in test files to account for diverging branches
Test: atest inputflinger_tests
Bug: b/291626046
Merged-In: I8195406ce1f9e3e704381b47e282c65463537745
Change-Id: I8195406ce1f9e3e704381b47e282c65463537745
(cherry picked from commit 4b4a457fb72ddb2c4ad269dff39b422fbf887999)
15 files changed, 562 insertions, 76 deletions
diff --git a/services/inputflinger/reader/EventHub.cpp b/services/inputflinger/reader/EventHub.cpp index 44e80a732c..16e7c45768 100644 --- a/services/inputflinger/reader/EventHub.cpp +++ b/services/inputflinger/reader/EventHub.cpp @@ -1072,6 +1072,22 @@ status_t EventHub::getAbsoluteAxisValue(int32_t deviceId, int32_t axis, int32_t* return -1; } +base::Result<std::vector<int32_t>> EventHub::getMtSlotValues(int32_t deviceId, int32_t axis, + size_t slotCount) const { + std::scoped_lock _l(mLock); + const Device* device = getDeviceLocked(deviceId); + if (device == nullptr || !device->hasValidFd() || !device->absBitmask.test(axis)) { + return base::ResultError("device problem or axis not supported", NAME_NOT_FOUND); + } + std::vector<int32_t> outValues(slotCount + 1); + outValues[0] = axis; + const size_t bufferSize = outValues.size() * sizeof(int32_t); + if (ioctl(device->fd, EVIOCGMTSLOTS(bufferSize), outValues.data()) != OK) { + return base::ErrnoError(); + } + return std::move(outValues); +} + bool EventHub::markSupportedKeyCodes(int32_t deviceId, const std::vector<int32_t>& keyCodes, uint8_t* outFlags) const { std::scoped_lock _l(mLock); diff --git a/services/inputflinger/reader/InputDevice.cpp b/services/inputflinger/reader/InputDevice.cpp index 0a64a1c4a8..0917c21148 100644 --- a/services/inputflinger/reader/InputDevice.cpp +++ b/services/inputflinger/reader/InputDevice.cpp @@ -359,6 +359,7 @@ std::list<NotifyArgs> InputDevice::process(const RawEvent* rawEvents, size_t cou if (mDropUntilNextSync) { if (rawEvent->type == EV_SYN && rawEvent->code == SYN_REPORT) { + out += reset(rawEvent->when); mDropUntilNextSync = false; ALOGD_IF(debugRawEvents(), "Recovered from input event buffer overrun."); } else { @@ -368,7 +369,6 @@ std::list<NotifyArgs> InputDevice::process(const RawEvent* rawEvents, size_t cou } else if (rawEvent->type == EV_SYN && rawEvent->code == SYN_DROPPED) { ALOGI("Detected input event buffer overrun for device %s.", getName().c_str()); mDropUntilNextSync = true; - out += reset(rawEvent->when); } else { for_each_mapper_in_subdevice(rawEvent->deviceId, [&](InputMapper& mapper) { out += mapper.process(rawEvent); diff --git a/services/inputflinger/reader/include/EventHub.h b/services/inputflinger/reader/include/EventHub.h index 024187f5b5..284d8c242a 100644 --- a/services/inputflinger/reader/include/EventHub.h +++ b/services/inputflinger/reader/include/EventHub.h @@ -334,6 +334,10 @@ public: virtual int32_t getSwitchState(int32_t deviceId, int32_t sw) const = 0; virtual status_t getAbsoluteAxisValue(int32_t deviceId, int32_t axis, int32_t* outValue) const = 0; + /* Query Multi-Touch slot values for an axis. Returns error or an 1 indexed array of size + * (slotCount + 1). The value at the 0 index is set to queried axis. */ + virtual base::Result<std::vector<int32_t>> getMtSlotValues(int32_t deviceId, int32_t axis, + size_t slotCount) const = 0; virtual int32_t getKeyCodeForKeyLocation(int32_t deviceId, int32_t locationKeyCode) const = 0; /* @@ -526,6 +530,8 @@ public: int32_t locationKeyCode) const override final; status_t getAbsoluteAxisValue(int32_t deviceId, int32_t axis, int32_t* outValue) const override final; + base::Result<std::vector<int32_t>> getMtSlotValues(int32_t deviceId, int32_t axis, + size_t slotCount) const override final; bool markSupportedKeyCodes(int32_t deviceId, const std::vector<int32_t>& keyCodes, uint8_t* outFlags) const override final; diff --git a/services/inputflinger/reader/include/InputDevice.h b/services/inputflinger/reader/include/InputDevice.h index 2f8e5bd6cf..82b7367d21 100644 --- a/services/inputflinger/reader/include/InputDevice.h +++ b/services/inputflinger/reader/include/InputDevice.h @@ -349,6 +349,10 @@ public: inline status_t getAbsoluteAxisValue(int32_t code, int32_t* outValue) const { return mEventHub->getAbsoluteAxisValue(mId, code, outValue); } + inline base::Result<std::vector<int32_t>> getMtSlotValues(int32_t axis, + size_t slotCount) const { + return mEventHub->getMtSlotValues(mId, axis, slotCount); + } inline bool markSupportedKeyCodes(const std::vector<int32_t>& keyCodes, uint8_t* outFlags) const { return mEventHub->markSupportedKeyCodes(mId, keyCodes, outFlags); diff --git a/services/inputflinger/reader/mapper/MultiTouchInputMapper.cpp b/services/inputflinger/reader/mapper/MultiTouchInputMapper.cpp index 9c87c62a7c..7b2711d798 100644 --- a/services/inputflinger/reader/mapper/MultiTouchInputMapper.cpp +++ b/services/inputflinger/reader/mapper/MultiTouchInputMapper.cpp @@ -35,12 +35,8 @@ MultiTouchInputMapper::MultiTouchInputMapper(InputDeviceContext& deviceContext, MultiTouchInputMapper::~MultiTouchInputMapper() {} std::list<NotifyArgs> MultiTouchInputMapper::reset(nsecs_t when) { - // The evdev multi-touch protocol does not allow userspace applications to query the initial or - // current state of the pointers at any time. This means if we clear our accumulated state when - // resetting the input mapper, there's no way to rebuild the full initial state of the pointers. - // We can only wait for updates to all the pointers and axes. Rather than clearing the state and - // rebuilding the state from scratch, we work around this kernel API limitation by never - // fully clearing any state specific to the multi-touch protocol. + mPointerIdBits.clear(); + mMultiTouchMotionAccumulator.reset(mDeviceContext); return TouchInputMapper::reset(when); } diff --git a/services/inputflinger/reader/mapper/accumulator/MultiTouchMotionAccumulator.cpp b/services/inputflinger/reader/mapper/accumulator/MultiTouchMotionAccumulator.cpp index f70be72741..f180e032b9 100644 --- a/services/inputflinger/reader/mapper/accumulator/MultiTouchMotionAccumulator.cpp +++ b/services/inputflinger/reader/mapper/accumulator/MultiTouchMotionAccumulator.cpp @@ -30,24 +30,12 @@ void MultiTouchMotionAccumulator::configure(const InputDeviceContext& deviceCont size_t slotCount, bool usingSlotsProtocol) { mUsingSlotsProtocol = usingSlotsProtocol; mSlots = std::vector<Slot>(slotCount); + populateCurrentSlot(deviceContext); +} - mCurrentSlot = -1; - if (mUsingSlotsProtocol) { - // Query the driver for the current slot index and use it as the initial slot before we - // start reading events from the device. It is possible that the current slot index will - // not be the same as it was when the first event was written into the evdev buffer, which - // means the input mapper could start out of sync with the initial state of the events in - // the evdev buffer. In the extremely unlikely case that this happens, the data from two - // slots will be confused until the next ABS_MT_SLOT event is received. This can cause the - // touch point to "jump", but at least there will be no stuck touches. - int32_t initialSlot; - if (const auto status = deviceContext.getAbsoluteAxisValue(ABS_MT_SLOT, &initialSlot); - status == OK) { - mCurrentSlot = initialSlot; - } else { - ALOGD("Could not retrieve current multi-touch slot index. status=%d", status); - } - } +void MultiTouchMotionAccumulator::reset(const InputDeviceContext& deviceContext) { + resetSlots(); + syncSlots(deviceContext); } void MultiTouchMotionAccumulator::resetSlots() { @@ -84,54 +72,10 @@ void MultiTouchMotionAccumulator::process(const RawEvent* rawEvent) { if (!mUsingSlotsProtocol) { slot.mInUse = true; } - - switch (rawEvent->code) { - case ABS_MT_POSITION_X: - slot.mAbsMtPositionX = rawEvent->value; - warnIfNotInUse(*rawEvent, slot); - break; - case ABS_MT_POSITION_Y: - slot.mAbsMtPositionY = rawEvent->value; - warnIfNotInUse(*rawEvent, slot); - break; - case ABS_MT_TOUCH_MAJOR: - slot.mAbsMtTouchMajor = rawEvent->value; - break; - case ABS_MT_TOUCH_MINOR: - slot.mAbsMtTouchMinor = rawEvent->value; - slot.mHaveAbsMtTouchMinor = true; - break; - case ABS_MT_WIDTH_MAJOR: - slot.mAbsMtWidthMajor = rawEvent->value; - break; - case ABS_MT_WIDTH_MINOR: - slot.mAbsMtWidthMinor = rawEvent->value; - slot.mHaveAbsMtWidthMinor = true; - break; - case ABS_MT_ORIENTATION: - slot.mAbsMtOrientation = rawEvent->value; - break; - case ABS_MT_TRACKING_ID: - if (mUsingSlotsProtocol && rawEvent->value < 0) { - // The slot is no longer in use but it retains its previous contents, - // which may be reused for subsequent touches. - slot.mInUse = false; - } else { - slot.mInUse = true; - slot.mAbsMtTrackingId = rawEvent->value; - } - break; - case ABS_MT_PRESSURE: - slot.mAbsMtPressure = rawEvent->value; - break; - case ABS_MT_DISTANCE: - slot.mAbsMtDistance = rawEvent->value; - break; - case ABS_MT_TOOL_TYPE: - slot.mAbsMtToolType = rawEvent->value; - slot.mHaveAbsMtToolType = true; - break; + if (rawEvent->code == ABS_MT_POSITION_X || rawEvent->code == ABS_MT_POSITION_Y) { + warnIfNotInUse(*rawEvent, slot); } + slot.populateAxisValue(rawEvent->code, rawEvent->value); } } else if (rawEvent->type == EV_SYN && rawEvent->code == SYN_MT_REPORT) { // MultiTouch Sync: The driver has returned all data for *one* of the pointers. @@ -139,6 +83,36 @@ void MultiTouchMotionAccumulator::process(const RawEvent* rawEvent) { } } +void MultiTouchMotionAccumulator::syncSlots(const InputDeviceContext& deviceContext) { + if (!mUsingSlotsProtocol) { + return; + } + constexpr std::array<int32_t, 11> axisCodes = {ABS_MT_POSITION_X, ABS_MT_POSITION_Y, + ABS_MT_TOUCH_MAJOR, ABS_MT_TOUCH_MINOR, + ABS_MT_WIDTH_MAJOR, ABS_MT_WIDTH_MINOR, + ABS_MT_ORIENTATION, ABS_MT_TRACKING_ID, + ABS_MT_PRESSURE, ABS_MT_DISTANCE, + ABS_MT_TOOL_TYPE}; + const size_t numSlots = mSlots.size(); + for (int32_t axisCode : axisCodes) { + if (!deviceContext.hasAbsoluteAxis(axisCode)) { + continue; + } + const auto result = deviceContext.getMtSlotValues(axisCode, numSlots); + if (result.ok()) { + const std::vector<int32_t>& mtSlotValues = result.value(); + for (size_t i = 1; i <= numSlots; ++i) { + // The returned slot values are in a 1-indexed vector of size numSlots + 1. + mSlots[i - 1].populateAxisValue(axisCode, mtSlotValues[i]); + } + } else { + ALOGE("Could not retrieve multi-touch slot value for axis=%d error=%s status=%d", + axisCode, result.error().message().c_str(), result.error().code().value()); + } + } + populateCurrentSlot(deviceContext); +} + void MultiTouchMotionAccumulator::finishSync() { if (!mUsingSlotsProtocol) { resetSlots(); @@ -152,6 +126,21 @@ void MultiTouchMotionAccumulator::warnIfNotInUse(const RawEvent& event, const Sl } } +void MultiTouchMotionAccumulator::populateCurrentSlot( + const android::InputDeviceContext& deviceContext) { + if (!mUsingSlotsProtocol) { + return; + } + int32_t initialSlot; + if (const auto status = deviceContext.getAbsoluteAxisValue(ABS_MT_SLOT, &initialSlot); + status == OK) { + mCurrentSlot = initialSlot; + } else { + ALOGE("Could not retrieve current multi-touch slot index. status=%s", + statusToString(status).c_str()); + } +} + // --- MultiTouchMotionAccumulator::Slot --- ToolType MultiTouchMotionAccumulator::Slot::getToolType() const { @@ -168,4 +157,52 @@ ToolType MultiTouchMotionAccumulator::Slot::getToolType() const { return ToolType::UNKNOWN; } +void MultiTouchMotionAccumulator::Slot::populateAxisValue(int32_t axisCode, int32_t value) { + switch (axisCode) { + case ABS_MT_POSITION_X: + mAbsMtPositionX = value; + break; + case ABS_MT_POSITION_Y: + mAbsMtPositionY = value; + break; + case ABS_MT_TOUCH_MAJOR: + mAbsMtTouchMajor = value; + break; + case ABS_MT_TOUCH_MINOR: + mAbsMtTouchMinor = value; + mHaveAbsMtTouchMinor = true; + break; + case ABS_MT_WIDTH_MAJOR: + mAbsMtWidthMajor = value; + break; + case ABS_MT_WIDTH_MINOR: + mAbsMtWidthMinor = value; + mHaveAbsMtWidthMinor = true; + break; + case ABS_MT_ORIENTATION: + mAbsMtOrientation = value; + break; + case ABS_MT_TRACKING_ID: + if (value < 0) { + // The slot is no longer in use but it retains its previous contents, + // which may be reused for subsequent touches. + mInUse = false; + } else { + mInUse = true; + mAbsMtTrackingId = value; + } + break; + case ABS_MT_PRESSURE: + mAbsMtPressure = value; + break; + case ABS_MT_DISTANCE: + mAbsMtDistance = value; + break; + case ABS_MT_TOOL_TYPE: + mAbsMtToolType = value; + mHaveAbsMtToolType = true; + break; + } +} + } // namespace android diff --git a/services/inputflinger/reader/mapper/accumulator/MultiTouchMotionAccumulator.h b/services/inputflinger/reader/mapper/accumulator/MultiTouchMotionAccumulator.h index 943dde5ca2..6102e72764 100644 --- a/services/inputflinger/reader/mapper/accumulator/MultiTouchMotionAccumulator.h +++ b/services/inputflinger/reader/mapper/accumulator/MultiTouchMotionAccumulator.h @@ -68,12 +68,14 @@ public: int32_t mAbsMtToolType = 0; void clear() { *this = Slot(); } + void populateAxisValue(int32_t axisCode, int32_t value); }; MultiTouchMotionAccumulator(); void configure(const InputDeviceContext& deviceContext, size_t slotCount, bool usingSlotsProtocol); + void reset(const InputDeviceContext& deviceContext); void process(const RawEvent* rawEvent); void finishSync(); @@ -84,12 +86,14 @@ public: } private: - int32_t mCurrentSlot; + int32_t mCurrentSlot{-1}; std::vector<Slot> mSlots; bool mUsingSlotsProtocol; void resetSlots(); + void syncSlots(const InputDeviceContext& deviceContext); void warnIfNotInUse(const RawEvent& event, const Slot& slot); + void populateCurrentSlot(const android::InputDeviceContext& deviceContext); }; } // namespace android diff --git a/services/inputflinger/tests/Android.bp b/services/inputflinger/tests/Android.bp index 1585fddfb3..30b40e2502 100644 --- a/services/inputflinger/tests/Android.bp +++ b/services/inputflinger/tests/Android.bp @@ -62,6 +62,7 @@ cc_test { "SyncQueue_test.cpp", "TestInputListener.cpp", "TouchpadInputMapper_test.cpp", + "MultiTouchInputMapper_test.cpp", "KeyboardInputMapper_test.cpp", "UinputDevice.cpp", "UnwantedInteractionBlocker_test.cpp", diff --git a/services/inputflinger/tests/FakeEventHub.cpp b/services/inputflinger/tests/FakeEventHub.cpp index 212fcebecd..daa000f2ce 100644 --- a/services/inputflinger/tests/FakeEventHub.cpp +++ b/services/inputflinger/tests/FakeEventHub.cpp @@ -431,6 +431,38 @@ status_t FakeEventHub::getAbsoluteAxisValue(int32_t deviceId, int32_t axis, return -1; } +void FakeEventHub::setMtSlotValues(int32_t deviceId, int32_t axis, + const std::vector<int32_t>& values) { + Device* device = getDevice(deviceId); + if (!device) { + FAIL() << "Missing device"; + } + device->mtSlotValues[axis] = values; +} + +base::Result<std::vector<int32_t>> FakeEventHub::getMtSlotValues(int32_t deviceId, int32_t axis, + size_t slotCount) const { + Device* device = getDevice(deviceId); + if (!device) { + ADD_FAILURE() << "Missing device"; + return base::ResultError("Missing device", UNKNOWN_ERROR); + } + const auto& mtSlotValuesIterator = device->mtSlotValues.find(axis); + if (mtSlotValuesIterator == device->mtSlotValues.end()) { + return base::ResultError("axis not supported", NAME_NOT_FOUND); + } + const auto& mtSlotValues = mtSlotValuesIterator->second; + if (mtSlotValues.size() != slotCount) { + ADD_FAILURE() << "MtSlot values specified for " << mtSlotValues.size() + << " slots but expected for " << slotCount << " Slots"; + return base::ResultError("Slot count mismatch", NAME_NOT_FOUND); + } + std::vector<int32_t> outValues(slotCount + 1); + outValues[0] = axis; + std::copy(mtSlotValues.begin(), mtSlotValues.end(), outValues.begin() + 1); + return std::move(outValues); +} + int32_t FakeEventHub::getKeyCodeForKeyLocation(int32_t deviceId, int32_t locationKeyCode) const { Device* device = getDevice(deviceId); if (!device) { diff --git a/services/inputflinger/tests/FakeEventHub.h b/services/inputflinger/tests/FakeEventHub.h index 8e06940aec..f07b3441c2 100644 --- a/services/inputflinger/tests/FakeEventHub.h +++ b/services/inputflinger/tests/FakeEventHub.h @@ -65,6 +65,7 @@ class FakeEventHub : public EventHubInterface { bool enabled; std::optional<RawLayoutInfo> layoutInfo; std::string sysfsRootPath; + std::unordered_map<int32_t, std::vector<int32_t>> mtSlotValues; status_t enable() { enabled = true; @@ -154,6 +155,11 @@ public: int32_t value); void assertQueueIsEmpty(); void setSysfsRootPath(int32_t deviceId, std::string sysfsRootPath) const; + // Populate fake slot values to be returned by the getter, size of the values should be equal to + // the slot count + void setMtSlotValues(int32_t deviceId, int32_t axis, const std::vector<int32_t>& values); + base::Result<std::vector<int32_t>> getMtSlotValues(int32_t deviceId, int32_t axis, + size_t slotCount) const override; private: Device* getDevice(int32_t deviceId) const; diff --git a/services/inputflinger/tests/InputReader_test.cpp b/services/inputflinger/tests/InputReader_test.cpp index 8941d16534..cc60e72849 100644 --- a/services/inputflinger/tests/InputReader_test.cpp +++ b/services/inputflinger/tests/InputReader_test.cpp @@ -220,6 +220,11 @@ public: mResetWasCalled = false; } + void assertResetWasNotCalled() { + std::scoped_lock lock(mLock); + ASSERT_FALSE(mResetWasCalled) << "Expected reset to not have been called."; + } + void assertProcessWasCalled(RawEvent* outLastEvent = nullptr) { std::unique_lock<std::mutex> lock(mLock); base::ScopedLockAssertion assumeLocked(mLock); @@ -236,6 +241,11 @@ public: mProcessWasCalled = false; } + void assertProcessWasNotCalled() { + std::scoped_lock lock(mLock); + ASSERT_FALSE(mProcessWasCalled) << "Expected process to not have been called."; + } + void setKeyCodeState(int32_t keyCode, int32_t state) { mKeyCodeStates.replaceValueFor(keyCode, state); } @@ -2715,6 +2725,60 @@ TEST_F(InputDeviceTest, GetBluetoothAddress) { ASSERT_EQ(DEVICE_BLUETOOTH_ADDRESS, *address); } +TEST_F(InputDeviceTest, KernelBufferOverflowResetsMappers) { + mFakePolicy->clearViewports(); + FakeInputMapper& mapper = + mDevice->addMapper<FakeInputMapper>(EVENTHUB_ID, mFakePolicy->getReaderConfiguration(), + AINPUT_SOURCE_KEYBOARD); + std::list<NotifyArgs> unused = + mDevice->configure(ARBITRARY_TIME, mFakePolicy->getReaderConfiguration(), + /*changes=*/{}); + + mapper.assertConfigureWasCalled(); + mapper.assertResetWasNotCalled(); + + RawEvent event{.when = ARBITRARY_TIME, + .readTime = ARBITRARY_TIME, + .deviceId = EVENTHUB_ID, + .type = EV_SYN, + .code = SYN_REPORT, + .value = 0}; + + // Events are processed normally. + unused = mDevice->process(&event, /*count=*/1); + mapper.assertProcessWasCalled(); + + // Simulate a kernel buffer overflow, which generates a SYN_DROPPED event. + event.type = EV_SYN; + event.code = SYN_DROPPED; + event.value = 0; + unused = mDevice->process(&event, /*count=*/1); + mapper.assertProcessWasNotCalled(); + + // All events until the next SYN_REPORT should be dropped. + event.type = EV_KEY; + event.code = KEY_A; + event.value = 1; + unused = mDevice->process(&event, /*count=*/1); + mapper.assertProcessWasNotCalled(); + + // We get the SYN_REPORT event now, which is not forwarded to mappers. + // This should reset the mapper. + event.type = EV_SYN; + event.code = SYN_REPORT; + event.value = 0; + unused = mDevice->process(&event, /*count=*/1); + mapper.assertProcessWasNotCalled(); + mapper.assertResetWasCalled(); + + // The mapper receives events normally now. + event.type = EV_KEY; + event.code = KEY_B; + event.value = 1; + unused = mDevice->process(&event, /*count=*/1); + mapper.assertProcessWasCalled(); +} + // --- SwitchInputMapperTest --- class SwitchInputMapperTest : public InputMapperTest { @@ -10187,15 +10251,16 @@ TEST_F(MultiTouchInputMapperTest, Process_MultiTouch_WithInvalidTrackingId) { ASSERT_EQ(uint32_t(1), motionArgs.getPointerCount()); } -TEST_F(MultiTouchInputMapperTest, Reset_PreservesLastTouchState) { +TEST_F(MultiTouchInputMapperTest, Reset_RepopulatesMultiTouchState) { addConfigurationProperty("touch.deviceType", "touchScreen"); prepareDisplay(ui::ROTATION_0); prepareAxes(POSITION | ID | SLOT | PRESSURE); MultiTouchInputMapper& mapper = constructAndAddMapper<MultiTouchInputMapper>(); // First finger down. + constexpr int32_t x1 = 100, y1 = 200, x2 = 300, y2 = 400; processId(mapper, FIRST_TRACKING_ID); - processPosition(mapper, 100, 200); + processPosition(mapper, x1, y1); processPressure(mapper, RAW_PRESSURE_MAX); processSync(mapper); ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled( @@ -10204,14 +10269,32 @@ TEST_F(MultiTouchInputMapperTest, Reset_PreservesLastTouchState) { // Second finger down. processSlot(mapper, SECOND_SLOT); processId(mapper, SECOND_TRACKING_ID); - processPosition(mapper, 300, 400); + processPosition(mapper, x2, y2); processPressure(mapper, RAW_PRESSURE_MAX); processSync(mapper); ASSERT_NO_FATAL_FAILURE( mFakeListener->assertNotifyMotionWasCalled(WithMotionAction(ACTION_POINTER_1_DOWN))); + // Set MT Slot state to be repopulated for the required slots + std::vector<int32_t> mtSlotValues(RAW_SLOT_MAX + 1, -1); + mtSlotValues[0] = FIRST_TRACKING_ID; + mtSlotValues[1] = SECOND_TRACKING_ID; + mFakeEventHub->setMtSlotValues(EVENTHUB_ID, ABS_MT_TRACKING_ID, mtSlotValues); + + mtSlotValues[0] = x1; + mtSlotValues[1] = x2; + mFakeEventHub->setMtSlotValues(EVENTHUB_ID, ABS_MT_POSITION_X, mtSlotValues); + + mtSlotValues[0] = y1; + mtSlotValues[1] = y2; + mFakeEventHub->setMtSlotValues(EVENTHUB_ID, ABS_MT_POSITION_Y, mtSlotValues); + + mtSlotValues[0] = RAW_PRESSURE_MAX; + mtSlotValues[1] = RAW_PRESSURE_MAX; + mFakeEventHub->setMtSlotValues(EVENTHUB_ID, ABS_MT_PRESSURE, mtSlotValues); + // Reset the mapper. When the mapper is reset, we expect the current multi-touch state to be - // preserved. Resetting should cancel the ongoing gesture. + // repopulated. Resetting should cancel the ongoing gesture. resetMapper(mapper, ARBITRARY_TIME); ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled( WithMotionAction(AMOTION_EVENT_ACTION_CANCEL))); diff --git a/services/inputflinger/tests/InterfaceMocks.h b/services/inputflinger/tests/InterfaceMocks.h index b6720c5712..c630ebb81c 100644 --- a/services/inputflinger/tests/InterfaceMocks.h +++ b/services/inputflinger/tests/InterfaceMocks.h @@ -102,6 +102,9 @@ public: MOCK_METHOD(status_t, getAbsoluteAxisValue, (int32_t deviceId, int32_t axis, int32_t* outValue), (const, override)); + MOCK_METHOD(base::Result<std::vector<int32_t>>, getMtSlotValues, + (int32_t deviceId, int32_t axis, size_t slotCount), (const, override)); + MOCK_METHOD(int32_t, getKeyCodeForKeyLocation, (int32_t deviceId, int32_t locationKeyCode), (const, override)); MOCK_METHOD(bool, markSupportedKeyCodes, diff --git a/services/inputflinger/tests/MultiTouchInputMapper_test.cpp b/services/inputflinger/tests/MultiTouchInputMapper_test.cpp new file mode 100644 index 0000000000..7039752d44 --- /dev/null +++ b/services/inputflinger/tests/MultiTouchInputMapper_test.cpp @@ -0,0 +1,279 @@ +/* + * 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 "MultiTouchInputMapper.h" + +#include <android-base/logging.h> +#include <gtest/gtest.h> +#include <list> +#include <optional> + +#include "InputMapperTest.h" +#include "InterfaceMocks.h" +#include "TestInputListenerMatchers.h" + +#define TAG "MultiTouchpadInputMapperUnit_test" + +namespace android { + +using testing::_; +using testing::IsEmpty; +using testing::Return; +using testing::SetArgPointee; +using testing::VariantWith; + +static constexpr int32_t DISPLAY_ID = 0; +static constexpr int32_t DISPLAY_WIDTH = 480; +static constexpr int32_t DISPLAY_HEIGHT = 800; +static constexpr std::optional<uint8_t> NO_PORT = std::nullopt; // no physical port is specified +static constexpr int32_t SLOT_COUNT = 5; + +static constexpr int32_t ACTION_POINTER_0_UP = + AMOTION_EVENT_ACTION_POINTER_UP | (0 << AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT); +static constexpr int32_t ACTION_POINTER_1_DOWN = + AMOTION_EVENT_ACTION_POINTER_DOWN | (1 << AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT); + +/** + * Unit tests for MultiTouchInputMapper. + */ +class MultiTouchInputMapperUnitTest : public InputMapperUnitTest { +protected: + sp<FakeInputReaderPolicy> mFakePolicy; + void SetUp() override { + InputMapperUnitTest::SetUp(); + + // Present scan codes + expectScanCodes(/*present=*/true, + {BTN_TOUCH, BTN_TOOL_FINGER, BTN_TOOL_DOUBLETAP, BTN_TOOL_TRIPLETAP, + BTN_TOOL_QUADTAP, BTN_TOOL_QUINTTAP}); + + // Missing scan codes that the mapper checks for. + expectScanCodes(/*present=*/false, + {BTN_TOOL_PEN, BTN_TOOL_RUBBER, BTN_TOOL_BRUSH, BTN_TOOL_PENCIL, + BTN_TOOL_AIRBRUSH}); + + // Current scan code state - all keys are UP by default + setScanCodeState(KeyState::UP, {BTN_LEFT, BTN_RIGHT, BTN_MIDDLE, + BTN_BACK, BTN_SIDE, BTN_FORWARD, + BTN_EXTRA, BTN_TASK, BTN_TOUCH, + BTN_STYLUS, BTN_STYLUS2, BTN_0, + BTN_TOOL_FINGER, BTN_TOOL_PEN, BTN_TOOL_RUBBER, + BTN_TOOL_BRUSH, BTN_TOOL_PENCIL, BTN_TOOL_AIRBRUSH, + BTN_TOOL_MOUSE, BTN_TOOL_LENS, BTN_TOOL_DOUBLETAP, + BTN_TOOL_TRIPLETAP, BTN_TOOL_QUADTAP, BTN_TOOL_QUINTTAP}); + + setKeyCodeState(KeyState::UP, + {AKEYCODE_STYLUS_BUTTON_PRIMARY, AKEYCODE_STYLUS_BUTTON_SECONDARY}); + + // Input properties - only INPUT_PROP_DIRECT for touchscreen + EXPECT_CALL(mMockEventHub, hasInputProperty(EVENTHUB_ID, _)).WillRepeatedly(Return(false)); + EXPECT_CALL(mMockEventHub, hasInputProperty(EVENTHUB_ID, INPUT_PROP_DIRECT)) + .WillRepeatedly(Return(true)); + + // Axes that the device has + setupAxis(ABS_MT_SLOT, /*valid=*/true, /*min=*/0, /*max=*/SLOT_COUNT - 1, /*resolution=*/0); + setupAxis(ABS_MT_TRACKING_ID, /*valid=*/true, /*min*/ 0, /*max=*/255, /*resolution=*/0); + setupAxis(ABS_MT_POSITION_X, /*valid=*/true, /*min=*/0, /*max=*/2000, /*resolution=*/24); + setupAxis(ABS_MT_POSITION_Y, /*valid=*/true, /*min=*/0, /*max=*/1000, /*resolution=*/24); + + // Axes that the device does not have + setupAxis(ABS_MT_PRESSURE, /*valid=*/false, /*min*/ 0, /*max=*/255, /*resolution=*/0); + setupAxis(ABS_MT_ORIENTATION, /*valid=*/false, /*min=*/0, /*max=*/0, /*resolution=*/0); + setupAxis(ABS_MT_DISTANCE, /*valid=*/false, /*min=*/0, /*max=*/0, /*resolution=*/0); + setupAxis(ABS_MT_TOUCH_MAJOR, /*valid=*/false, /*min=*/0, /*max=*/0, /*resolution=*/0); + setupAxis(ABS_MT_TOUCH_MINOR, /*valid=*/false, /*min=*/0, /*max=*/0, /*resolution=*/0); + setupAxis(ABS_MT_WIDTH_MAJOR, /*valid=*/false, /*min=*/0, /*max=*/0, /*resolution=*/0); + setupAxis(ABS_MT_WIDTH_MINOR, /*valid=*/false, /*min=*/0, /*max=*/0, /*resolution=*/0); + setupAxis(ABS_MT_TOOL_TYPE, /*valid=*/false, /*min=*/0, /*max=*/0, /*resolution=*/0); + + // reset current slot at the beginning + EXPECT_CALL(mMockEventHub, getAbsoluteAxisValue(EVENTHUB_ID, ABS_MT_SLOT, _)) + .WillRepeatedly([](int32_t, int32_t, int32_t* outValue) { + *outValue = 0; + return OK; + }); + + // mark all slots not in use + mockSlotValues({}); + + mFakePolicy = sp<FakeInputReaderPolicy>::make(); + EXPECT_CALL(mMockInputReaderContext, getPolicy).WillRepeatedly(Return(mFakePolicy.get())); + + mFakePolicy->setDefaultPointerDisplayId(DISPLAY_ID); + mFakePolicy->addDisplayViewport(DISPLAY_ID, DISPLAY_WIDTH, DISPLAY_HEIGHT, ui::ROTATION_0, + /*isActive=*/true, "local:0", NO_PORT, + ViewportType::INTERNAL); + mMapper = createInputMapper<MultiTouchInputMapper>(*mDeviceContext, + mFakePolicy->getReaderConfiguration()); + } + + // Mocks position and tracking Ids for the provided slots. Remaining slots will be marked + // unused. + void mockSlotValues( + const std::unordered_map<int32_t /*slotIndex*/, + std::pair<Point /*position*/, int32_t /*trackingId*/>>& + slotValues) { + EXPECT_CALL(mMockEventHub, getMtSlotValues(EVENTHUB_ID, _, SLOT_COUNT)) + .WillRepeatedly([=](int32_t, int32_t axis, + size_t slotCount) -> base::Result<std::vector<int32_t>> { + // tracking Id for the unused slots must set to be < 0 + std::vector<int32_t> outMtSlotValues(slotCount + 1, -1); + outMtSlotValues[0] = axis; + switch (axis) { + case ABS_MT_POSITION_X: + for (const auto& [slotIndex, valuePair] : slotValues) { + outMtSlotValues[slotIndex] = valuePair.first.x; + } + return outMtSlotValues; + case ABS_MT_POSITION_Y: + for (const auto& [slotIndex, valuePair] : slotValues) { + outMtSlotValues[slotIndex] = valuePair.first.y; + } + return outMtSlotValues; + case ABS_MT_TRACKING_ID: + for (const auto& [slotIndex, valuePair] : slotValues) { + outMtSlotValues[slotIndex] = valuePair.second; + } + return outMtSlotValues; + default: + return base::ResultError("Axis not supported", NAME_NOT_FOUND); + } + }); + } + + std::list<NotifyArgs> processPosition(int32_t x, int32_t y) { + std::list<NotifyArgs> args; + args += process(EV_ABS, ABS_MT_POSITION_X, x); + args += process(EV_ABS, ABS_MT_POSITION_Y, y); + return args; + } + + std::list<NotifyArgs> processId(int32_t id) { return process(EV_ABS, ABS_MT_TRACKING_ID, id); } + + std::list<NotifyArgs> processKey(int32_t code, int32_t value) { + return process(EV_KEY, code, value); + } + + std::list<NotifyArgs> processSlot(int32_t slot) { return process(EV_ABS, ABS_MT_SLOT, slot); } + + std::list<NotifyArgs> processSync() { return process(EV_SYN, SYN_REPORT, 0); } +}; + +// This test simulates a multi-finger gesture with unexpected reset in between. This might happen +// due to buffer overflow and device with report a SYN_DROPPED. In this case we expect mapper to be +// reset, MT slot state to be re-populated and the gesture should be cancelled and restarted. +TEST_F(MultiTouchInputMapperUnitTest, MultiFingerGestureWithUnexpectedReset) { + std::list<NotifyArgs> args; + + // Two fingers down at once. + constexpr int32_t FIRST_TRACKING_ID = 1, SECOND_TRACKING_ID = 2; + int32_t x1 = 100, y1 = 125, x2 = 200, y2 = 225; + processKey(BTN_TOUCH, 1); + args += processPosition(x1, y1); + args += processId(FIRST_TRACKING_ID); + args += processSlot(1); + args += processPosition(x2, y2); + args += processId(SECOND_TRACKING_ID); + ASSERT_THAT(args, IsEmpty()); + + args = processSync(); + ASSERT_THAT(args, + ElementsAre(VariantWith<NotifyMotionArgs>( + WithMotionAction(AMOTION_EVENT_ACTION_DOWN)), + VariantWith<NotifyMotionArgs>( + WithMotionAction(ACTION_POINTER_1_DOWN)))); + + // Move. + x1 += 10; + y1 += 15; + x2 += 5; + y2 -= 10; + args = processSlot(0); + args += processPosition(x1, y1); + args += processSlot(1); + args += processPosition(x2, y2); + ASSERT_THAT(args, IsEmpty()); + + args = processSync(); + ASSERT_THAT(args, + ElementsAre(VariantWith<NotifyMotionArgs>( + WithMotionAction(AMOTION_EVENT_ACTION_MOVE)))); + const auto pointerCoordsBeforeReset = std::get<NotifyMotionArgs>(args.back()).pointerCoords; + + // On buffer overflow mapper will be reset and MT slots data will be repopulated + EXPECT_CALL(mMockEventHub, getAbsoluteAxisValue(EVENTHUB_ID, ABS_MT_SLOT, _)) + .WillRepeatedly([=](int32_t, int32_t, int32_t* outValue) { + *outValue = 1; + return OK; + }); + + mockSlotValues( + {{1, {Point{x1, y1}, FIRST_TRACKING_ID}}, {2, {Point{x2, y2}, SECOND_TRACKING_ID}}}); + + setScanCodeState(KeyState::DOWN, {BTN_TOUCH}); + + args = mMapper->reset(systemTime(SYSTEM_TIME_MONOTONIC)); + ASSERT_THAT(args, + ElementsAre(VariantWith<NotifyMotionArgs>( + WithMotionAction(AMOTION_EVENT_ACTION_CANCEL)))); + + // SYN_REPORT should restart the gesture again + args = processSync(); + ASSERT_THAT(args, + ElementsAre(VariantWith<NotifyMotionArgs>( + WithMotionAction(AMOTION_EVENT_ACTION_DOWN)), + VariantWith<NotifyMotionArgs>( + WithMotionAction(ACTION_POINTER_1_DOWN)))); + ASSERT_EQ(std::get<NotifyMotionArgs>(args.back()).pointerCoords, pointerCoordsBeforeReset); + + // Move. + x1 += 10; + y1 += 15; + x2 += 5; + y2 -= 10; + args = processSlot(0); + args += processPosition(x1, y1); + args += processSlot(1); + args += processPosition(x2, y2); + ASSERT_THAT(args, IsEmpty()); + + args = processSync(); + ASSERT_THAT(args, + ElementsAre(VariantWith<NotifyMotionArgs>( + WithMotionAction(AMOTION_EVENT_ACTION_MOVE)))); + + // First finger up. + args = processSlot(0); + args += processId(-1); + ASSERT_THAT(args, IsEmpty()); + + args = processSync(); + ASSERT_THAT(args, + ElementsAre(VariantWith<NotifyMotionArgs>(WithMotionAction(ACTION_POINTER_0_UP)))); + + // Second finger up. + processKey(BTN_TOUCH, 0); + args = processSlot(1); + args += processId(-1); + ASSERT_THAT(args, IsEmpty()); + + args = processSync(); + ASSERT_THAT(args, + ElementsAre( + VariantWith<NotifyMotionArgs>(WithMotionAction(AMOTION_EVENT_ACTION_UP)))); +} + +} // namespace android diff --git a/services/inputflinger/tests/TouchpadInputMapper_test.cpp b/services/inputflinger/tests/TouchpadInputMapper_test.cpp index 02abf9f0f0..90c4b105f5 100644 --- a/services/inputflinger/tests/TouchpadInputMapper_test.cpp +++ b/services/inputflinger/tests/TouchpadInputMapper_test.cpp @@ -94,12 +94,19 @@ protected: setupAxis(ABS_MT_TOUCH_MINOR, /*valid=*/false, /*min=*/0, /*max=*/0, /*resolution=*/0); setupAxis(ABS_MT_WIDTH_MAJOR, /*valid=*/false, /*min=*/0, /*max=*/0, /*resolution=*/0); setupAxis(ABS_MT_WIDTH_MINOR, /*valid=*/false, /*min=*/0, /*max=*/0, /*resolution=*/0); + setupAxis(ABS_MT_TRACKING_ID, /*valid=*/false, /*min=*/0, /*max=*/0, /*resolution=*/0); + setupAxis(ABS_MT_DISTANCE, /*valid=*/false, /*min=*/0, /*max=*/0, /*resolution=*/0); + setupAxis(ABS_MT_TOOL_TYPE, /*valid=*/false, /*min=*/0, /*max=*/0, /*resolution=*/0); EXPECT_CALL(mMockEventHub, getAbsoluteAxisValue(EVENTHUB_ID, ABS_MT_SLOT, testing::_)) .WillRepeatedly([](int32_t eventHubId, int32_t, int32_t* outValue) { *outValue = 0; return OK; }); + EXPECT_CALL(mMockEventHub, getMtSlotValues(EVENTHUB_ID, testing::_, testing::_)) + .WillRepeatedly([]() -> base::Result<std::vector<int32_t>> { + return base::ResultError("Axis not supported", NAME_NOT_FOUND); + }); mMapper = createInputMapper<TouchpadInputMapper>(*mDeviceContext, mReaderConfiguration); } }; diff --git a/services/inputflinger/tests/fuzzers/MapperHelpers.h b/services/inputflinger/tests/fuzzers/MapperHelpers.h index 1ecaa648f7..2440a9f8ad 100644 --- a/services/inputflinger/tests/fuzzers/MapperHelpers.h +++ b/services/inputflinger/tests/fuzzers/MapperHelpers.h @@ -183,6 +183,18 @@ public: int32_t* outValue) const override { return mFdp->ConsumeIntegral<status_t>(); } + base::Result<std::vector<int32_t>> getMtSlotValues(int32_t deviceId, int32_t axis, + size_t slotCount) const override { + if (mFdp->ConsumeBool()) { + std::vector<int32_t> outValues(slotCount + 1); + for (size_t i = 0; i < outValues.size(); i++) { + outValues.push_back(mFdp->ConsumeIntegral<int32_t>()); + } + return std::move(outValues); + } else { + return base::ResultError("Fuzzer", UNKNOWN_ERROR); + } + } bool markSupportedKeyCodes(int32_t deviceId, const std::vector<int32_t>& keyCodes, uint8_t* outFlags) const override { return mFdp->ConsumeBool(); |