Loading services/inputflinger/reader/mapper/MultiTouchInputMapper.cpp +4 −6 Original line number Diff line number Diff line Loading @@ -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); } Loading services/inputflinger/reader/mapper/accumulator/MultiTouchMotionAccumulator.cpp +22 −16 Original line number Diff line number Diff line Loading @@ -30,9 +30,16 @@ void MultiTouchMotionAccumulator::configure(const InputDeviceContext& deviceCont size_t slotCount, bool usingSlotsProtocol) { mUsingSlotsProtocol = usingSlotsProtocol; mSlots = std::vector<Slot>(slotCount); reset(deviceContext); } void MultiTouchMotionAccumulator::reset(const InputDeviceContext& deviceContext) { resetSlots(); if (!mUsingSlotsProtocol) { return; } 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 Loading @@ -48,7 +55,6 @@ void MultiTouchMotionAccumulator::configure(const InputDeviceContext& deviceCont ALOGD("Could not retrieve current multi-touch slot index. status=%d", status); } } } void MultiTouchMotionAccumulator::resetSlots() { for (Slot& slot : mSlots) { Loading services/inputflinger/reader/mapper/accumulator/MultiTouchMotionAccumulator.h +1 −0 Original line number Diff line number Diff line Loading @@ -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; Loading services/inputflinger/tests/InputReader_test.cpp +143 −8 Original line number Diff line number Diff line Loading @@ -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); Loading @@ -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); } Loading Loading @@ -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 { Loading Loading @@ -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); Loading @@ -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); Loading Loading @@ -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 { Loading
services/inputflinger/reader/mapper/MultiTouchInputMapper.cpp +4 −6 Original line number Diff line number Diff line Loading @@ -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); } Loading
services/inputflinger/reader/mapper/accumulator/MultiTouchMotionAccumulator.cpp +22 −16 Original line number Diff line number Diff line Loading @@ -30,9 +30,16 @@ void MultiTouchMotionAccumulator::configure(const InputDeviceContext& deviceCont size_t slotCount, bool usingSlotsProtocol) { mUsingSlotsProtocol = usingSlotsProtocol; mSlots = std::vector<Slot>(slotCount); reset(deviceContext); } void MultiTouchMotionAccumulator::reset(const InputDeviceContext& deviceContext) { resetSlots(); if (!mUsingSlotsProtocol) { return; } 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 Loading @@ -48,7 +55,6 @@ void MultiTouchMotionAccumulator::configure(const InputDeviceContext& deviceCont ALOGD("Could not retrieve current multi-touch slot index. status=%d", status); } } } void MultiTouchMotionAccumulator::resetSlots() { for (Slot& slot : mSlots) { Loading
services/inputflinger/reader/mapper/accumulator/MultiTouchMotionAccumulator.h +1 −0 Original line number Diff line number Diff line Loading @@ -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; Loading
services/inputflinger/tests/InputReader_test.cpp +143 −8 Original line number Diff line number Diff line Loading @@ -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); Loading @@ -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); } Loading Loading @@ -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 { Loading Loading @@ -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); Loading @@ -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); Loading Loading @@ -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 {