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.
Test: atest inputflinger_tests
Bug: b/291626046
Change-Id: I8195406ce1f9e3e704381b47e282c65463537745
diff --git a/services/inputflinger/reader/EventHub.cpp b/services/inputflinger/reader/EventHub.cpp
index f7bbc51..40e8628 100644
--- a/services/inputflinger/reader/EventHub.cpp
+++ b/services/inputflinger/reader/EventHub.cpp
@@ -1141,6 +1141,22 @@
return OK;
}
+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 fb32f96..a41064b 100644
--- a/services/inputflinger/reader/InputDevice.cpp
+++ b/services/inputflinger/reader/InputDevice.cpp
@@ -350,6 +350,7 @@
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 {
@@ -359,7 +360,6 @@
} 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 0bcab42..a7e0675 100644
--- a/services/inputflinger/reader/include/EventHub.h
+++ b/services/inputflinger/reader/include/EventHub.h
@@ -336,6 +336,10 @@
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;
/*
@@ -552,6 +556,8 @@
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 31dcb2e..c076d33 100644
--- a/services/inputflinger/reader/include/InputDevice.h
+++ b/services/inputflinger/reader/include/InputDevice.h
@@ -372,6 +372,10 @@
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 2dd05f5..0c58dab 100644
--- a/services/inputflinger/reader/mapper/MultiTouchInputMapper.cpp
+++ b/services/inputflinger/reader/mapper/MultiTouchInputMapper.cpp
@@ -35,12 +35,8 @@
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 b0fc903..b3f1700 100644
--- a/services/inputflinger/reader/mapper/accumulator/MultiTouchMotionAccumulator.cpp
+++ b/services/inputflinger/reader/mapper/accumulator/MultiTouchMotionAccumulator.cpp
@@ -30,24 +30,12 @@
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 @@
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::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();
@@ -160,6 +134,21 @@
[](const Slot& slot) { return slot.mInUse; });
}
+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 {
@@ -176,4 +165,52 @@
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 0e3e2bb..a0f2147 100644
--- a/services/inputflinger/reader/mapper/accumulator/MultiTouchMotionAccumulator.h
+++ b/services/inputflinger/reader/mapper/accumulator/MultiTouchMotionAccumulator.h
@@ -68,12 +68,14 @@
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();
@@ -85,12 +87,14 @@
}
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 db31ded..55aa226 100644
--- a/services/inputflinger/tests/Android.bp
+++ b/services/inputflinger/tests/Android.bp
@@ -68,6 +68,7 @@
"TimerProvider_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 212fceb..daa000f 100644
--- a/services/inputflinger/tests/FakeEventHub.cpp
+++ b/services/inputflinger/tests/FakeEventHub.cpp
@@ -431,6 +431,38 @@
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 8e06940..f07b344 100644
--- a/services/inputflinger/tests/FakeEventHub.h
+++ b/services/inputflinger/tests/FakeEventHub.h
@@ -65,6 +65,7 @@
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 @@
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 91aa0ca..81f57bd 100644
--- a/services/inputflinger/tests/InputReader_test.cpp
+++ b/services/inputflinger/tests/InputReader_test.cpp
@@ -2904,13 +2904,11 @@
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;
@@ -2920,11 +2918,13 @@
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;
@@ -9796,15 +9796,16 @@
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(
@@ -9813,14 +9814,32 @@
// 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 9de80af..db89168 100644
--- a/services/inputflinger/tests/InterfaceMocks.h
+++ b/services/inputflinger/tests/InterfaceMocks.h
@@ -132,6 +132,9 @@
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 0000000..d726385
--- /dev/null
+++ b/services/inputflinger/tests/MultiTouchInputMapper_test.cpp
@@ -0,0 +1,276 @@
+/*
+ * 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 "TestEventMatchers.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:
+ 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->setDefaultPointerDisplayId(DISPLAY_ID);
+ mFakePolicy->addDisplayViewport(DISPLAY_ID, DISPLAY_WIDTH, DISPLAY_HEIGHT, ui::ROTATION_0,
+ /*isActive=*/true, "local:0", NO_PORT,
+ ViewportType::INTERNAL);
+ createDevice();
+ 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 4be1e8c..fbafbad 100644
--- a/services/inputflinger/tests/TouchpadInputMapper_test.cpp
+++ b/services/inputflinger/tests/TouchpadInputMapper_test.cpp
@@ -103,12 +103,19 @@
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);
+ });
createDevice();
mMapper = createInputMapper<TouchpadInputMapper>(*mDeviceContext, mReaderConfiguration);
}
diff --git a/services/inputflinger/tests/fuzzers/MapperHelpers.h b/services/inputflinger/tests/fuzzers/MapperHelpers.h
index 81c570d..7898126 100644
--- a/services/inputflinger/tests/fuzzers/MapperHelpers.h
+++ b/services/inputflinger/tests/fuzzers/MapperHelpers.h
@@ -201,6 +201,18 @@
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();