TouchInputMapper: Cancel ongoing gesture when resetting
Since reset is called when a device is being enabled or disabled, if it
doesn't cancel ongoing gestures, there is a possiblity of seeing an
inconsistent gesture stream.
Also, make sure we send FLAG_CANCELED when sending ACTION_CANCEL from
TouchInputMapper.
Bug: 245989146
Test: atest inputflinger_tests
Change-Id: I9921eee9acf365b28d97f3fbe9b4d6cd15fe7087
diff --git a/services/inputflinger/reader/mapper/TouchInputMapper.cpp b/services/inputflinger/reader/mapper/TouchInputMapper.cpp
index 6980144..c2454ac 100644
--- a/services/inputflinger/reader/mapper/TouchInputMapper.cpp
+++ b/services/inputflinger/reader/mapper/TouchInputMapper.cpp
@@ -395,13 +395,11 @@
}
if (changes && resetNeeded) {
- // If the device needs to be reset, cancel any ongoing gestures and reset the state.
- out += cancelTouch(when, when);
out += reset(when);
// Send reset, unless this is the first time the device has been configured,
// in which case the reader will call reset itself after all mappers are ready.
- out.push_back(NotifyDeviceResetArgs(getContext()->getNextId(), when, getDeviceId()));
+ out.emplace_back(NotifyDeviceResetArgs(getContext()->getNextId(), when, getDeviceId()));
}
return out;
}
@@ -1440,6 +1438,9 @@
}
std::list<NotifyArgs> TouchInputMapper::reset(nsecs_t when) {
+ std::list<NotifyArgs> out = cancelTouch(when, when);
+ updateTouchSpots();
+
mCursorButtonAccumulator.reset(getDeviceContext());
mCursorScrollAccumulator.reset(getDeviceContext());
mTouchButtonAccumulator.reset(getDeviceContext());
@@ -1470,7 +1471,7 @@
mPointerController->clearSpots();
}
- return InputMapper::reset(when);
+ return out += InputMapper::reset(when);
}
void TouchInputMapper::resetExternalStylus() {
@@ -1554,10 +1555,7 @@
std::list<NotifyArgs> TouchInputMapper::processRawTouches(bool timeout) {
std::list<NotifyArgs> out;
if (mDeviceMode == DeviceMode::DISABLED) {
- // Drop all input if the device is disabled.
- out += cancelTouch(mCurrentRawState.when, mCurrentRawState.readTime);
- mCurrentCookedState.clear();
- updateTouchSpots();
+ // Do not process raw event while the device is disabled.
return out;
}
@@ -1976,8 +1974,8 @@
int32_t metaState = getContext()->getGlobalMetaState();
int32_t buttonState = mCurrentCookedState.buttonState;
out.push_back(dispatchMotion(when, readTime, policyFlags, mSource,
- AMOTION_EVENT_ACTION_CANCEL, 0, 0, metaState, buttonState,
- AMOTION_EVENT_EDGE_FLAG_NONE,
+ AMOTION_EVENT_ACTION_CANCEL, 0, AMOTION_EVENT_FLAG_CANCELED,
+ metaState, buttonState, AMOTION_EVENT_EDGE_FLAG_NONE,
mCurrentCookedState.cookedPointerData.pointerProperties,
mCurrentCookedState.cookedPointerData.pointerCoords,
mCurrentCookedState.cookedPointerData.idToIndex, currentIdBits,
@@ -2617,8 +2615,9 @@
BitSet32 dispatchedGestureIdBits(mPointerGesture.lastGestureIdBits);
if (!dispatchedGestureIdBits.isEmpty()) {
if (cancelPreviousGesture) {
+ const uint32_t cancelFlags = flags | AMOTION_EVENT_FLAG_CANCELED;
out.push_back(dispatchMotion(when, readTime, policyFlags, mSource,
- AMOTION_EVENT_ACTION_CANCEL, 0, flags, metaState,
+ AMOTION_EVENT_ACTION_CANCEL, 0, cancelFlags, metaState,
buttonState, AMOTION_EVENT_EDGE_FLAG_NONE,
mPointerGesture.lastGestureProperties,
mPointerGesture.lastGestureCoords,
@@ -2754,8 +2753,8 @@
int32_t metaState = getContext()->getGlobalMetaState();
int32_t buttonState = mCurrentRawState.buttonState;
out.push_back(dispatchMotion(when, readTime, policyFlags, mSource,
- AMOTION_EVENT_ACTION_CANCEL, 0, 0, metaState, buttonState,
- AMOTION_EVENT_EDGE_FLAG_NONE,
+ AMOTION_EVENT_ACTION_CANCEL, 0, AMOTION_EVENT_FLAG_CANCELED,
+ metaState, buttonState, AMOTION_EVENT_EDGE_FLAG_NONE,
mPointerGesture.lastGestureProperties,
mPointerGesture.lastGestureCoords,
mPointerGesture.lastGestureIdToIndex,
diff --git a/services/inputflinger/tests/InputReader_test.cpp b/services/inputflinger/tests/InputReader_test.cpp
index dded6a1..8ac8dfc 100644
--- a/services/inputflinger/tests/InputReader_test.cpp
+++ b/services/inputflinger/tests/InputReader_test.cpp
@@ -3112,6 +3112,15 @@
return processArgList;
}
+ void resetMapper(InputMapper& mapper, nsecs_t when) {
+ const auto resetArgs = mapper.reset(when);
+ for (const auto args : resetArgs) {
+ mFakeListener->notify(args);
+ }
+ // Loop the reader to flush the input listener queue.
+ mReader->loopOnce();
+ }
+
static void assertMotionRange(const InputDeviceInfo& info,
int32_t axis, uint32_t source, float min, float max, float flat, float fuzz) {
const InputDeviceInfo::MotionRange* range = info.getMotionRange(axis, source);
@@ -6864,6 +6873,28 @@
toDisplayX(150), toDisplayY(250), 0, 0, 0, 0, 0, 0, 0, 0));
}
+TEST_F(SingleTouchInputMapperTest, Reset_CancelsOngoingGesture) {
+ addConfigurationProperty("touch.deviceType", "touchScreen");
+ prepareDisplay(DISPLAY_ORIENTATION_0);
+ prepareButtons();
+ prepareAxes(POSITION | PRESSURE);
+ SingleTouchInputMapper& mapper = addMapperAndConfigure<SingleTouchInputMapper>();
+
+ // Touch down.
+ processDown(mapper, 100, 200);
+ processPressure(mapper, 1);
+ processSync(mapper);
+ ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(
+ WithMotionAction(AMOTION_EVENT_ACTION_DOWN)));
+
+ // Reset the mapper. This should cancel the ongoing gesture.
+ resetMapper(mapper, ARBITRARY_TIME);
+ ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(
+ WithMotionAction(AMOTION_EVENT_ACTION_CANCEL)));
+
+ ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasNotCalled());
+}
+
TEST_F(SingleTouchInputMapperTest, Reset_RecreatesTouchState) {
addConfigurationProperty("touch.deviceType", "touchScreen");
prepareDisplay(DISPLAY_ORIENTATION_0);
@@ -6878,8 +6909,9 @@
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.
- std::list<NotifyArgs> unused = mapper.reset(ARBITRARY_TIME);
+ // state by reading the current axis values. Since there was no ongoing gesture, calling reset
+ // does not generate any events.
+ resetMapper(mapper, 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.
@@ -9530,9 +9562,10 @@
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.
- std::list<NotifyArgs> unused = mapper.reset(ARBITRARY_TIME);
- ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasNotCalled());
+ // preserved. Resetting should cancel the ongoing gesture.
+ 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.
@@ -9566,7 +9599,7 @@
// Reset the mapper. When the mapper is reset, we expect it to restore the latest
// raw state where no pointers are down.
- std::list<NotifyArgs> unused = mapper.reset(ARBITRARY_TIME);
+ resetMapper(mapper, ARBITRARY_TIME);
ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasNotCalled());
// Send an empty sync frame. Since there are no pointers, no events are generated.
diff --git a/services/inputflinger/tests/TestInputListenerMatchers.h b/services/inputflinger/tests/TestInputListenerMatchers.h
index 2580405..ff7455b 100644
--- a/services/inputflinger/tests/TestInputListenerMatchers.h
+++ b/services/inputflinger/tests/TestInputListenerMatchers.h
@@ -23,13 +23,19 @@
namespace android {
MATCHER_P(WithMotionAction, action, "InputEvent with specified action") {
- if (action == AMOTION_EVENT_ACTION_CANCEL) {
- *result_listener << "expected FLAG_CANCELED to be set with ACTION_CANCEL, but was not set";
- return (arg.flags & AMOTION_EVENT_FLAG_CANCELED) != 0;
+ bool matches = action == arg.action;
+ if (!matches) {
+ *result_listener << "expected action " << MotionEvent::actionToString(action)
+ << ", but got " << MotionEvent::actionToString(arg.action);
}
- *result_listener << "expected action " << MotionEvent::actionToString(action) << ", but got "
- << MotionEvent::actionToString(arg.action);
- return action == arg.action;
+ if (action == AMOTION_EVENT_ACTION_CANCEL) {
+ if (!matches) {
+ *result_listener << "; ";
+ }
+ *result_listener << "expected FLAG_CANCELED to be set with ACTION_CANCEL, but was not set";
+ matches &= (arg.flags & AMOTION_EVENT_FLAG_CANCELED) != 0;
+ }
+ return matches;
}
MATCHER_P(WithSource, source, "InputEvent with specified source") {