Loading services/inputflinger/reader/mapper/MultiTouchInputMapper.cpp +15 −19 Original line number Diff line number Diff line Loading @@ -38,13 +38,9 @@ void MultiTouchMotionAccumulator::configure(InputDeviceContext& deviceContext, s bool usingSlotsProtocol) { mUsingSlotsProtocol = usingSlotsProtocol; mHaveStylus = deviceContext.hasAbsoluteAxis(ABS_MT_TOOL_TYPE); mSlots = std::vector<Slot>(slotCount); } void MultiTouchMotionAccumulator::reset(InputDeviceContext& deviceContext) { // Unfortunately there is no way to read the initial contents of the slots. // So when we reset the accumulator, we must assume they are all zeroes. 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 Loading @@ -56,22 +52,20 @@ void MultiTouchMotionAccumulator::reset(InputDeviceContext& deviceContext) { // This can cause the touch point to "jump", but at least there will be // no stuck touches. int32_t initialSlot; status_t status = deviceContext.getAbsoluteAxisValue(ABS_MT_SLOT, &initialSlot); if (status) { ALOGD("Could not retrieve current multitouch slot index. status=%d", status); initialSlot = -1; } clearSlots(initialSlot); if (const auto status = deviceContext.getAbsoluteAxisValue(ABS_MT_SLOT, &initialSlot); status == OK) { mCurrentSlot = initialSlot; } else { clearSlots(-1); ALOGD("Could not retrieve current multi-touch slot index. status=%d", status); } } } void MultiTouchMotionAccumulator::clearSlots(int32_t initialSlot) { void MultiTouchMotionAccumulator::resetSlots() { for (Slot& slot : mSlots) { slot.clear(); } mCurrentSlot = initialSlot; mCurrentSlot = -1; } void MultiTouchMotionAccumulator::process(const RawEvent* rawEvent) { Loading Loading @@ -159,7 +153,7 @@ void MultiTouchMotionAccumulator::process(const RawEvent* rawEvent) { void MultiTouchMotionAccumulator::finishSync() { if (!mUsingSlotsProtocol) { clearSlots(-1); resetSlots(); } } Loading Loading @@ -198,10 +192,12 @@ MultiTouchInputMapper::MultiTouchInputMapper(InputDeviceContext& deviceContext) MultiTouchInputMapper::~MultiTouchInputMapper() {} void MultiTouchInputMapper::reset(nsecs_t when) { mMultiTouchMotionAccumulator.reset(getDeviceContext()); mPointerIdBits.clear(); // 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. TouchInputMapper::reset(when); } Loading services/inputflinger/reader/mapper/MultiTouchInputMapper.h +1 −2 Original line number Diff line number Diff line Loading @@ -68,7 +68,6 @@ public: MultiTouchMotionAccumulator(); void configure(InputDeviceContext& deviceContext, size_t slotCount, bool usingSlotsProtocol); void reset(InputDeviceContext& deviceContext); void process(const RawEvent* rawEvent); void finishSync(); bool hasStylus() const; Loading @@ -85,7 +84,7 @@ private: bool mUsingSlotsProtocol; bool mHaveStylus; void clearSlots(int32_t initialSlot); void resetSlots(); void warnIfNotInUse(const RawEvent& event, const Slot& slot); }; Loading services/inputflinger/reader/mapper/TouchInputMapper.cpp +4 −0 Original line number Diff line number Diff line Loading @@ -1495,6 +1495,10 @@ void TouchInputMapper::process(const RawEvent* rawEvent) { } void TouchInputMapper::sync(nsecs_t when, nsecs_t readTime) { if (mDeviceMode == DeviceMode::DISABLED) { // Only save the last pending state when the device is disabled. mRawStatesPending.clear(); } // Push a new state. mRawStatesPending.emplace_back(); Loading services/inputflinger/reader/mapper/TouchInputMapper.h +2 −0 Original line number Diff line number Diff line Loading @@ -320,6 +320,8 @@ protected: int32_t rawVScroll; int32_t rawHScroll; explicit inline RawState() { clear(); } void copyFrom(const RawState& other) { when = other.when; readTime = other.readTime; Loading services/inputflinger/tests/InputReader_test.cpp +105 −5 Original line number Diff line number Diff line Loading @@ -6835,6 +6835,32 @@ TEST_F(SingleTouchInputMapperTest, Process_WhenAbsPressureIsPresent_HoversIfItsV toDisplayX(150), toDisplayY(250), 0, 0, 0, 0, 0, 0, 0, 0)); } TEST_F(SingleTouchInputMapperTest, Reset_RecreatesTouchState) { addConfigurationProperty("touch.deviceType", "touchScreen"); prepareDisplay(DISPLAY_ORIENTATION_0); prepareButtons(); prepareAxes(POSITION | PRESSURE); SingleTouchInputMapper& mapper = addMapperAndConfigure<SingleTouchInputMapper>(); // Set the initial state for the touch pointer. mFakeEventHub->setAbsoluteAxisValue(EVENTHUB_ID, ABS_X, 100); mFakeEventHub->setAbsoluteAxisValue(EVENTHUB_ID, ABS_Y, 200); mFakeEventHub->setAbsoluteAxisValue(EVENTHUB_ID, ABS_PRESSURE, RAW_PRESSURE_MAX); mFakeEventHub->setScanCodeState(EVENTHUB_ID, BTN_TOUCH, 1); // Reset the mapper. When the mapper is reset, we expect it to attempt to recreate the touch // state by reading the current axis values. mapper.reset(ARBITRARY_TIME); // Send a sync to simulate an empty touch frame where nothing changes. The mapper should use // the recreated touch state to generate a down event. processSync(mapper); ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled( AllOf(WithMotionAction(AMOTION_EVENT_ACTION_DOWN), WithPressure(1.f)))); ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasNotCalled()); } TEST_F(SingleTouchInputMapperTest, Process_WhenViewportDisplayIdChanged_TouchIsCanceledAndDeviceIsReset) { addConfigurationProperty("touch.deviceType", "touchScreen"); Loading Loading @@ -6894,10 +6920,17 @@ TEST_F(SingleTouchInputMapperTest, // No events are generated while the viewport is inactive. processMove(mapper, 101, 201); processSync(mapper); processDown(mapper, 102, 202); processUp(mapper); processSync(mapper); ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasNotCalled()); // Start a new gesture while the viewport is still inactive. processDown(mapper, 300, 400); mFakeEventHub->setAbsoluteAxisValue(EVENTHUB_ID, ABS_X, 300); mFakeEventHub->setAbsoluteAxisValue(EVENTHUB_ID, ABS_Y, 400); mFakeEventHub->setScanCodeState(EVENTHUB_ID, BTN_TOUCH, 1); processSync(mapper); // Make the viewport active again. The device should resume processing events. viewport->isActive = true; mFakePolicy->updateViewport(*viewport); Loading @@ -6907,11 +6940,10 @@ TEST_F(SingleTouchInputMapperTest, ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyDeviceResetWasCalled()); ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasNotCalled()); // Start a new gesture. processDown(mapper, 100, 200); // In the next sync, the touch state that was recreated when the device was reset is reported. processSync(mapper); ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs)); ASSERT_EQ(AMOTION_EVENT_ACTION_DOWN, motionArgs.action); ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled( WithMotionAction(AMOTION_EVENT_ACTION_DOWN))); // No more events. ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasNotCalled()); Loading Loading @@ -9443,6 +9475,74 @@ TEST_F(MultiTouchInputMapperTest, Process_MultiTouch_WithInvalidTrackingId) { ASSERT_EQ(uint32_t(1), motionArgs.pointerCount); } TEST_F(MultiTouchInputMapperTest, Reset_PreservesLastTouchState) { addConfigurationProperty("touch.deviceType", "touchScreen"); prepareDisplay(DISPLAY_ORIENTATION_0); prepareAxes(POSITION | ID | SLOT | PRESSURE); MultiTouchInputMapper& mapper = addMapperAndConfigure<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))); // Second finger down. processSlot(mapper, SECOND_SLOT); processId(mapper, SECOND_TRACKING_ID); processPosition(mapper, 300, 400); processPressure(mapper, RAW_PRESSURE_MAX); processSync(mapper); 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 not generate any events. mapper.reset(ARBITRARY_TIME); ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasNotCalled()); // 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. processPosition(mapper, 301, 302); 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) { addConfigurationProperty("touch.deviceType", "touchScreen"); prepareDisplay(DISPLAY_ORIENTATION_0); prepareAxes(POSITION | ID | SLOT | PRESSURE); MultiTouchInputMapper& mapper = addMapperAndConfigure<MultiTouchInputMapper>(); // First finger touches down and releases. 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))); processId(mapper, INVALID_TRACKING_ID); processSync(mapper); ASSERT_NO_FATAL_FAILURE( mFakeListener->assertNotifyMotionWasCalled(WithMotionAction(AMOTION_EVENT_ACTION_UP))); // Reset the mapper. When the mapper is reset, we expect it to restore the latest // raw state where no pointers are down. mapper.reset(ARBITRARY_TIME); ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasNotCalled()); // Send an empty sync frame. Since there are no pointers, no events are generated. processSync(mapper); ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasNotCalled()); } // --- MultiTouchInputMapperTest_ExternalDevice --- class MultiTouchInputMapperTest_ExternalDevice : public MultiTouchInputMapperTest { Loading Loading
services/inputflinger/reader/mapper/MultiTouchInputMapper.cpp +15 −19 Original line number Diff line number Diff line Loading @@ -38,13 +38,9 @@ void MultiTouchMotionAccumulator::configure(InputDeviceContext& deviceContext, s bool usingSlotsProtocol) { mUsingSlotsProtocol = usingSlotsProtocol; mHaveStylus = deviceContext.hasAbsoluteAxis(ABS_MT_TOOL_TYPE); mSlots = std::vector<Slot>(slotCount); } void MultiTouchMotionAccumulator::reset(InputDeviceContext& deviceContext) { // Unfortunately there is no way to read the initial contents of the slots. // So when we reset the accumulator, we must assume they are all zeroes. 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 Loading @@ -56,22 +52,20 @@ void MultiTouchMotionAccumulator::reset(InputDeviceContext& deviceContext) { // This can cause the touch point to "jump", but at least there will be // no stuck touches. int32_t initialSlot; status_t status = deviceContext.getAbsoluteAxisValue(ABS_MT_SLOT, &initialSlot); if (status) { ALOGD("Could not retrieve current multitouch slot index. status=%d", status); initialSlot = -1; } clearSlots(initialSlot); if (const auto status = deviceContext.getAbsoluteAxisValue(ABS_MT_SLOT, &initialSlot); status == OK) { mCurrentSlot = initialSlot; } else { clearSlots(-1); ALOGD("Could not retrieve current multi-touch slot index. status=%d", status); } } } void MultiTouchMotionAccumulator::clearSlots(int32_t initialSlot) { void MultiTouchMotionAccumulator::resetSlots() { for (Slot& slot : mSlots) { slot.clear(); } mCurrentSlot = initialSlot; mCurrentSlot = -1; } void MultiTouchMotionAccumulator::process(const RawEvent* rawEvent) { Loading Loading @@ -159,7 +153,7 @@ void MultiTouchMotionAccumulator::process(const RawEvent* rawEvent) { void MultiTouchMotionAccumulator::finishSync() { if (!mUsingSlotsProtocol) { clearSlots(-1); resetSlots(); } } Loading Loading @@ -198,10 +192,12 @@ MultiTouchInputMapper::MultiTouchInputMapper(InputDeviceContext& deviceContext) MultiTouchInputMapper::~MultiTouchInputMapper() {} void MultiTouchInputMapper::reset(nsecs_t when) { mMultiTouchMotionAccumulator.reset(getDeviceContext()); mPointerIdBits.clear(); // 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. TouchInputMapper::reset(when); } Loading
services/inputflinger/reader/mapper/MultiTouchInputMapper.h +1 −2 Original line number Diff line number Diff line Loading @@ -68,7 +68,6 @@ public: MultiTouchMotionAccumulator(); void configure(InputDeviceContext& deviceContext, size_t slotCount, bool usingSlotsProtocol); void reset(InputDeviceContext& deviceContext); void process(const RawEvent* rawEvent); void finishSync(); bool hasStylus() const; Loading @@ -85,7 +84,7 @@ private: bool mUsingSlotsProtocol; bool mHaveStylus; void clearSlots(int32_t initialSlot); void resetSlots(); void warnIfNotInUse(const RawEvent& event, const Slot& slot); }; Loading
services/inputflinger/reader/mapper/TouchInputMapper.cpp +4 −0 Original line number Diff line number Diff line Loading @@ -1495,6 +1495,10 @@ void TouchInputMapper::process(const RawEvent* rawEvent) { } void TouchInputMapper::sync(nsecs_t when, nsecs_t readTime) { if (mDeviceMode == DeviceMode::DISABLED) { // Only save the last pending state when the device is disabled. mRawStatesPending.clear(); } // Push a new state. mRawStatesPending.emplace_back(); Loading
services/inputflinger/reader/mapper/TouchInputMapper.h +2 −0 Original line number Diff line number Diff line Loading @@ -320,6 +320,8 @@ protected: int32_t rawVScroll; int32_t rawHScroll; explicit inline RawState() { clear(); } void copyFrom(const RawState& other) { when = other.when; readTime = other.readTime; Loading
services/inputflinger/tests/InputReader_test.cpp +105 −5 Original line number Diff line number Diff line Loading @@ -6835,6 +6835,32 @@ TEST_F(SingleTouchInputMapperTest, Process_WhenAbsPressureIsPresent_HoversIfItsV toDisplayX(150), toDisplayY(250), 0, 0, 0, 0, 0, 0, 0, 0)); } TEST_F(SingleTouchInputMapperTest, Reset_RecreatesTouchState) { addConfigurationProperty("touch.deviceType", "touchScreen"); prepareDisplay(DISPLAY_ORIENTATION_0); prepareButtons(); prepareAxes(POSITION | PRESSURE); SingleTouchInputMapper& mapper = addMapperAndConfigure<SingleTouchInputMapper>(); // Set the initial state for the touch pointer. mFakeEventHub->setAbsoluteAxisValue(EVENTHUB_ID, ABS_X, 100); mFakeEventHub->setAbsoluteAxisValue(EVENTHUB_ID, ABS_Y, 200); mFakeEventHub->setAbsoluteAxisValue(EVENTHUB_ID, ABS_PRESSURE, RAW_PRESSURE_MAX); mFakeEventHub->setScanCodeState(EVENTHUB_ID, BTN_TOUCH, 1); // Reset the mapper. When the mapper is reset, we expect it to attempt to recreate the touch // state by reading the current axis values. mapper.reset(ARBITRARY_TIME); // Send a sync to simulate an empty touch frame where nothing changes. The mapper should use // the recreated touch state to generate a down event. processSync(mapper); ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled( AllOf(WithMotionAction(AMOTION_EVENT_ACTION_DOWN), WithPressure(1.f)))); ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasNotCalled()); } TEST_F(SingleTouchInputMapperTest, Process_WhenViewportDisplayIdChanged_TouchIsCanceledAndDeviceIsReset) { addConfigurationProperty("touch.deviceType", "touchScreen"); Loading Loading @@ -6894,10 +6920,17 @@ TEST_F(SingleTouchInputMapperTest, // No events are generated while the viewport is inactive. processMove(mapper, 101, 201); processSync(mapper); processDown(mapper, 102, 202); processUp(mapper); processSync(mapper); ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasNotCalled()); // Start a new gesture while the viewport is still inactive. processDown(mapper, 300, 400); mFakeEventHub->setAbsoluteAxisValue(EVENTHUB_ID, ABS_X, 300); mFakeEventHub->setAbsoluteAxisValue(EVENTHUB_ID, ABS_Y, 400); mFakeEventHub->setScanCodeState(EVENTHUB_ID, BTN_TOUCH, 1); processSync(mapper); // Make the viewport active again. The device should resume processing events. viewport->isActive = true; mFakePolicy->updateViewport(*viewport); Loading @@ -6907,11 +6940,10 @@ TEST_F(SingleTouchInputMapperTest, ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyDeviceResetWasCalled()); ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasNotCalled()); // Start a new gesture. processDown(mapper, 100, 200); // In the next sync, the touch state that was recreated when the device was reset is reported. processSync(mapper); ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs)); ASSERT_EQ(AMOTION_EVENT_ACTION_DOWN, motionArgs.action); ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled( WithMotionAction(AMOTION_EVENT_ACTION_DOWN))); // No more events. ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasNotCalled()); Loading Loading @@ -9443,6 +9475,74 @@ TEST_F(MultiTouchInputMapperTest, Process_MultiTouch_WithInvalidTrackingId) { ASSERT_EQ(uint32_t(1), motionArgs.pointerCount); } TEST_F(MultiTouchInputMapperTest, Reset_PreservesLastTouchState) { addConfigurationProperty("touch.deviceType", "touchScreen"); prepareDisplay(DISPLAY_ORIENTATION_0); prepareAxes(POSITION | ID | SLOT | PRESSURE); MultiTouchInputMapper& mapper = addMapperAndConfigure<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))); // Second finger down. processSlot(mapper, SECOND_SLOT); processId(mapper, SECOND_TRACKING_ID); processPosition(mapper, 300, 400); processPressure(mapper, RAW_PRESSURE_MAX); processSync(mapper); 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 not generate any events. mapper.reset(ARBITRARY_TIME); ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasNotCalled()); // 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. processPosition(mapper, 301, 302); 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) { addConfigurationProperty("touch.deviceType", "touchScreen"); prepareDisplay(DISPLAY_ORIENTATION_0); prepareAxes(POSITION | ID | SLOT | PRESSURE); MultiTouchInputMapper& mapper = addMapperAndConfigure<MultiTouchInputMapper>(); // First finger touches down and releases. 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))); processId(mapper, INVALID_TRACKING_ID); processSync(mapper); ASSERT_NO_FATAL_FAILURE( mFakeListener->assertNotifyMotionWasCalled(WithMotionAction(AMOTION_EVENT_ACTION_UP))); // Reset the mapper. When the mapper is reset, we expect it to restore the latest // raw state where no pointers are down. mapper.reset(ARBITRARY_TIME); ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasNotCalled()); // Send an empty sync frame. Since there are no pointers, no events are generated. processSync(mapper); ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasNotCalled()); } // --- MultiTouchInputMapperTest_ExternalDevice --- class MultiTouchInputMapperTest_ExternalDevice : public MultiTouchInputMapperTest { Loading