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/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