diff options
author | 2023-12-07 15:45:49 +0000 | |
---|---|---|
committer | 2023-12-07 15:45:49 +0000 | |
commit | c42ed4a081afdc60d54e13e90fd6ea89f5c93c36 (patch) | |
tree | cadd2ef635430f42273637c562373126be56daf8 | |
parent | 8597755c385471ef4bf2286558d381b5ef8c94bb (diff) | |
parent | f8d9e440558f4dfb13ef41e23f41f30092297582 (diff) |
Merge "InputReader: Clear the multi-touch state when the device is reset" into main
4 files changed, 170 insertions, 30 deletions
diff --git a/services/inputflinger/reader/mapper/MultiTouchInputMapper.cpp b/services/inputflinger/reader/mapper/MultiTouchInputMapper.cpp index 2dd05f5c66..5a74a42446 100644 --- a/services/inputflinger/reader/mapper/MultiTouchInputMapper.cpp +++ b/services/inputflinger/reader/mapper/MultiTouchInputMapper.cpp @@ -35,12 +35,10 @@ 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. + // TODO(b/291626046): Sync the MT state with the kernel using EVIOCGMTSLOTS. + mMultiTouchMotionAccumulator.reset(getDeviceContext()); + mPointerIdBits.clear(); + return TouchInputMapper::reset(when); } diff --git a/services/inputflinger/reader/mapper/accumulator/MultiTouchMotionAccumulator.cpp b/services/inputflinger/reader/mapper/accumulator/MultiTouchMotionAccumulator.cpp index b0fc9035f2..d06514acab 100644 --- a/services/inputflinger/reader/mapper/accumulator/MultiTouchMotionAccumulator.cpp +++ b/services/inputflinger/reader/mapper/accumulator/MultiTouchMotionAccumulator.cpp @@ -30,23 +30,29 @@ void MultiTouchMotionAccumulator::configure(const InputDeviceContext& deviceCont size_t slotCount, bool usingSlotsProtocol) { mUsingSlotsProtocol = usingSlotsProtocol; mSlots = std::vector<Slot>(slotCount); + reset(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(); + + if (!mUsingSlotsProtocol) { + return; + } + + // 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); } } diff --git a/services/inputflinger/reader/mapper/accumulator/MultiTouchMotionAccumulator.h b/services/inputflinger/reader/mapper/accumulator/MultiTouchMotionAccumulator.h index 0e3e2bb365..5b55e3d599 100644 --- a/services/inputflinger/reader/mapper/accumulator/MultiTouchMotionAccumulator.h +++ b/services/inputflinger/reader/mapper/accumulator/MultiTouchMotionAccumulator.h @@ -83,6 +83,7 @@ public: LOG_ALWAYS_FATAL_IF(index < 0 || index >= mSlots.size(), "Invalid index: %zu", index); return mSlots[index]; } + void reset(const InputDeviceContext& deviceContext); private: int32_t mCurrentSlot; diff --git a/services/inputflinger/tests/InputReader_test.cpp b/services/inputflinger/tests/InputReader_test.cpp index e8b779adf7..e0a3e94aad 100644 --- a/services/inputflinger/tests/InputReader_test.cpp +++ b/services/inputflinger/tests/InputReader_test.cpp @@ -232,6 +232,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); @@ -248,6 +253,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); } @@ -2872,6 +2882,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{.deviceId = EVENTHUB_ID, + .readTime = ARBITRARY_TIME, + .when = ARBITRARY_TIME, + .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. + // This should reset the mapper. + event.type = EV_SYN; + event.code = SYN_DROPPED; + event.value = 0; + unused = mDevice->process(&event, /*count=*/1); + mapper.assertProcessWasNotCalled(); + mapper.assertResetWasCalled(); + + // 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. + event.type = EV_SYN; + event.code = SYN_REPORT; + event.value = 0; + unused = mDevice->process(&event, /*count=*/1); + mapper.assertProcessWasNotCalled(); + + // 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 { @@ -10907,7 +10971,7 @@ TEST_F(MultiTouchInputMapperTest, Process_MultiTouch_WithInvalidTrackingId) { ASSERT_EQ(uint32_t(1), motionArgs.getPointerCount()); } -TEST_F(MultiTouchInputMapperTest, Reset_PreservesLastTouchState) { +TEST_F(MultiTouchInputMapperTest, ResetClearsTouchState) { addConfigurationProperty("touch.deviceType", "touchScreen"); prepareDisplay(ui::ROTATION_0); prepareAxes(POSITION | ID | SLOT | PRESSURE); @@ -10930,25 +10994,36 @@ TEST_F(MultiTouchInputMapperTest, Reset_PreservesLastTouchState) { ASSERT_NO_FATAL_FAILURE( mFakeListener->assertNotifyMotionWasCalled(WithMotionAction(ACTION_POINTER_1_DOWN))); - // Reset the mapper. When the mapper is reset, we expect the current multi-touch state to be - // preserved. Resetting should cancel the ongoing gesture. + // Reset the mapper. When the mapper is reset, the touch state is also cleared. resetMapper(mapper, ARBITRARY_TIME); ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled( WithMotionAction(AMOTION_EVENT_ACTION_CANCEL))); - // Send a sync to simulate an empty touch frame where nothing changes. The mapper should use - // the existing touch state to generate a down event. + // Move the second slot pointer, and ensure there are no events, because the touch state was + // cleared and no slots should be in use. processPosition(mapper, 301, 302); processSync(mapper); + ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasNotCalled()); + + // Release both fingers. + processId(mapper, INVALID_TRACKING_ID); + processSlot(mapper, FIRST_SLOT); + processId(mapper, INVALID_TRACKING_ID); + processSync(mapper); + ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasNotCalled()); + + // Start a new gesture, and ensure we get a DOWN event for it. + processId(mapper, FIRST_TRACKING_ID); + processPosition(mapper, 200, 300); + processPressure(mapper, RAW_PRESSURE_MAX); + processSync(mapper); ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled( AllOf(WithMotionAction(AMOTION_EVENT_ACTION_DOWN), WithPressure(1.f)))); - ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled( - AllOf(WithMotionAction(ACTION_POINTER_1_DOWN), WithPressure(1.f)))); ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasNotCalled()); } -TEST_F(MultiTouchInputMapperTest, Reset_PreservesLastTouchState_NoPointersDown) { +TEST_F(MultiTouchInputMapperTest, ResetClearsTouchStateWithNoPointersDown) { addConfigurationProperty("touch.deviceType", "touchScreen"); prepareDisplay(ui::ROTATION_0); prepareAxes(POSITION | ID | SLOT | PRESSURE); @@ -11076,6 +11151,66 @@ TEST_F(MultiTouchInputMapperTest, Process_WhenConfigDisabled_ShouldNotShowDirect ASSERT_FALSE(fakePointerController->isPointerShown()); } +TEST_F(MultiTouchInputMapperTest, SimulateKernelBufferOverflow) { + addConfigurationProperty("touch.deviceType", "touchScreen"); + prepareDisplay(ui::ROTATION_0); + prepareAxes(POSITION | ID | SLOT | PRESSURE); + MultiTouchInputMapper& mapper = constructAndAddMapper<MultiTouchInputMapper>(); + + // First finger down. + processId(mapper, FIRST_TRACKING_ID); + processPosition(mapper, 100, 200); + processPressure(mapper, RAW_PRESSURE_MAX); + processSync(mapper); + ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled( + WithMotionAction(AMOTION_EVENT_ACTION_DOWN))); + + // Assume the kernel buffer overflows, and we get a SYN_DROPPED event. + // This will reset the mapper, and thus also reset the touch state. + process(mapper, ARBITRARY_TIME, READ_TIME, EV_SYN, SYN_DROPPED, 0); + resetMapper(mapper, ARBITRARY_TIME); + ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled( + WithMotionAction(AMOTION_EVENT_ACTION_CANCEL))); + + // Since the touch state was reset, it doesn't know which slots are active, so any movements + // are ignored. + processPosition(mapper, 101, 201); + processSync(mapper); + + ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasNotCalled()); + + // Second finger goes down. This is the first active finger, so we get a DOWN event. + processSlot(mapper, SECOND_SLOT); + processId(mapper, SECOND_TRACKING_ID); + processPosition(mapper, 400, 500); + processPressure(mapper, RAW_PRESSURE_MAX); + processSync(mapper); + + ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled( + WithMotionAction(AMOTION_EVENT_ACTION_DOWN))); + + // First slot is still ignored, only the second one is active. + processSlot(mapper, FIRST_SLOT); + processPosition(mapper, 102, 202); + processSlot(mapper, SECOND_SLOT); + processPosition(mapper, 401, 501); + processSync(mapper); + + ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled( + WithMotionAction(AMOTION_EVENT_ACTION_MOVE))); + + // Both slots up, so we get the UP event for the active pointer. + processSlot(mapper, FIRST_SLOT); + processId(mapper, INVALID_TRACKING_ID); + processSlot(mapper, SECOND_SLOT); + processId(mapper, INVALID_TRACKING_ID); + processSync(mapper); + + ASSERT_NO_FATAL_FAILURE( + mFakeListener->assertNotifyMotionWasCalled(WithMotionAction(AMOTION_EVENT_ACTION_UP))); + ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasNotCalled()); +} + // --- MultiTouchInputMapperTest_ExternalDevice --- class MultiTouchInputMapperTest_ExternalDevice : public MultiTouchInputMapperTest { |