blob: d4d3c3857f2ea8f2832fd86702e4d16f80ef91ef [file] [log] [blame]
Arpit Singh4b4a4572023-11-24 18:19:56 +00001/*
2 * Copyright 2024 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17#include "MultiTouchInputMapper.h"
18
19#include <android-base/logging.h>
20#include <gtest/gtest.h>
21#include <list>
22#include <optional>
23
24#include "InputMapperTest.h"
25#include "InterfaceMocks.h"
26#include "TestEventMatchers.h"
27
28#define TAG "MultiTouchpadInputMapperUnit_test"
29
30namespace android {
31
32using testing::_;
33using testing::IsEmpty;
34using testing::Return;
35using testing::SetArgPointee;
36using testing::VariantWith;
37
Siarhei Vishniakoucfbee532024-05-10 13:41:35 -070038static constexpr ui::LogicalDisplayId DISPLAY_ID = ui::LogicalDisplayId::DEFAULT;
Arpit Singh4b4a4572023-11-24 18:19:56 +000039static constexpr int32_t DISPLAY_WIDTH = 480;
40static constexpr int32_t DISPLAY_HEIGHT = 800;
41static constexpr std::optional<uint8_t> NO_PORT = std::nullopt; // no physical port is specified
42static constexpr int32_t SLOT_COUNT = 5;
43
44static constexpr int32_t ACTION_POINTER_0_UP =
45 AMOTION_EVENT_ACTION_POINTER_UP | (0 << AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT);
46static constexpr int32_t ACTION_POINTER_1_DOWN =
47 AMOTION_EVENT_ACTION_POINTER_DOWN | (1 << AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT);
48
49/**
50 * Unit tests for MultiTouchInputMapper.
51 */
52class MultiTouchInputMapperUnitTest : public InputMapperUnitTest {
53protected:
54 void SetUp() override {
55 InputMapperUnitTest::SetUp();
56
57 // Present scan codes
58 expectScanCodes(/*present=*/true,
59 {BTN_TOUCH, BTN_TOOL_FINGER, BTN_TOOL_DOUBLETAP, BTN_TOOL_TRIPLETAP,
60 BTN_TOOL_QUADTAP, BTN_TOOL_QUINTTAP});
61
62 // Missing scan codes that the mapper checks for.
63 expectScanCodes(/*present=*/false,
64 {BTN_TOOL_PEN, BTN_TOOL_RUBBER, BTN_TOOL_BRUSH, BTN_TOOL_PENCIL,
65 BTN_TOOL_AIRBRUSH});
66
67 // Current scan code state - all keys are UP by default
68 setScanCodeState(KeyState::UP, {BTN_LEFT, BTN_RIGHT, BTN_MIDDLE,
69 BTN_BACK, BTN_SIDE, BTN_FORWARD,
70 BTN_EXTRA, BTN_TASK, BTN_TOUCH,
71 BTN_STYLUS, BTN_STYLUS2, BTN_0,
72 BTN_TOOL_FINGER, BTN_TOOL_PEN, BTN_TOOL_RUBBER,
73 BTN_TOOL_BRUSH, BTN_TOOL_PENCIL, BTN_TOOL_AIRBRUSH,
74 BTN_TOOL_MOUSE, BTN_TOOL_LENS, BTN_TOOL_DOUBLETAP,
75 BTN_TOOL_TRIPLETAP, BTN_TOOL_QUADTAP, BTN_TOOL_QUINTTAP});
76
77 setKeyCodeState(KeyState::UP,
78 {AKEYCODE_STYLUS_BUTTON_PRIMARY, AKEYCODE_STYLUS_BUTTON_SECONDARY});
79
80 // Input properties - only INPUT_PROP_DIRECT for touchscreen
81 EXPECT_CALL(mMockEventHub, hasInputProperty(EVENTHUB_ID, _)).WillRepeatedly(Return(false));
82 EXPECT_CALL(mMockEventHub, hasInputProperty(EVENTHUB_ID, INPUT_PROP_DIRECT))
83 .WillRepeatedly(Return(true));
84
85 // Axes that the device has
86 setupAxis(ABS_MT_SLOT, /*valid=*/true, /*min=*/0, /*max=*/SLOT_COUNT - 1, /*resolution=*/0);
87 setupAxis(ABS_MT_TRACKING_ID, /*valid=*/true, /*min*/ 0, /*max=*/255, /*resolution=*/0);
88 setupAxis(ABS_MT_POSITION_X, /*valid=*/true, /*min=*/0, /*max=*/2000, /*resolution=*/24);
89 setupAxis(ABS_MT_POSITION_Y, /*valid=*/true, /*min=*/0, /*max=*/1000, /*resolution=*/24);
90
91 // Axes that the device does not have
92 setupAxis(ABS_MT_PRESSURE, /*valid=*/false, /*min*/ 0, /*max=*/255, /*resolution=*/0);
93 setupAxis(ABS_MT_ORIENTATION, /*valid=*/false, /*min=*/0, /*max=*/0, /*resolution=*/0);
94 setupAxis(ABS_MT_DISTANCE, /*valid=*/false, /*min=*/0, /*max=*/0, /*resolution=*/0);
95 setupAxis(ABS_MT_TOUCH_MAJOR, /*valid=*/false, /*min=*/0, /*max=*/0, /*resolution=*/0);
96 setupAxis(ABS_MT_TOUCH_MINOR, /*valid=*/false, /*min=*/0, /*max=*/0, /*resolution=*/0);
97 setupAxis(ABS_MT_WIDTH_MAJOR, /*valid=*/false, /*min=*/0, /*max=*/0, /*resolution=*/0);
98 setupAxis(ABS_MT_WIDTH_MINOR, /*valid=*/false, /*min=*/0, /*max=*/0, /*resolution=*/0);
99 setupAxis(ABS_MT_TOOL_TYPE, /*valid=*/false, /*min=*/0, /*max=*/0, /*resolution=*/0);
100
101 // reset current slot at the beginning
Harry Cuttse2c5e202024-06-21 17:08:48 +0000102 EXPECT_CALL(mMockEventHub, getAbsoluteAxisValue(EVENTHUB_ID, ABS_MT_SLOT))
103 .WillRepeatedly(Return(0));
Arpit Singh4b4a4572023-11-24 18:19:56 +0000104
105 // mark all slots not in use
106 mockSlotValues({});
107
108 mFakePolicy->setDefaultPointerDisplayId(DISPLAY_ID);
109 mFakePolicy->addDisplayViewport(DISPLAY_ID, DISPLAY_WIDTH, DISPLAY_HEIGHT, ui::ROTATION_0,
110 /*isActive=*/true, "local:0", NO_PORT,
111 ViewportType::INTERNAL);
112 createDevice();
113 mMapper = createInputMapper<MultiTouchInputMapper>(*mDeviceContext,
114 mFakePolicy->getReaderConfiguration());
115 }
116
117 // Mocks position and tracking Ids for the provided slots. Remaining slots will be marked
118 // unused.
119 void mockSlotValues(
120 const std::unordered_map<int32_t /*slotIndex*/,
121 std::pair<Point /*position*/, int32_t /*trackingId*/>>&
122 slotValues) {
123 EXPECT_CALL(mMockEventHub, getMtSlotValues(EVENTHUB_ID, _, SLOT_COUNT))
124 .WillRepeatedly([=](int32_t, int32_t axis,
125 size_t slotCount) -> base::Result<std::vector<int32_t>> {
126 // tracking Id for the unused slots must set to be < 0
127 std::vector<int32_t> outMtSlotValues(slotCount + 1, -1);
128 outMtSlotValues[0] = axis;
129 switch (axis) {
130 case ABS_MT_POSITION_X:
131 for (const auto& [slotIndex, valuePair] : slotValues) {
132 outMtSlotValues[slotIndex] = valuePair.first.x;
133 }
134 return outMtSlotValues;
135 case ABS_MT_POSITION_Y:
136 for (const auto& [slotIndex, valuePair] : slotValues) {
137 outMtSlotValues[slotIndex] = valuePair.first.y;
138 }
139 return outMtSlotValues;
140 case ABS_MT_TRACKING_ID:
141 for (const auto& [slotIndex, valuePair] : slotValues) {
142 outMtSlotValues[slotIndex] = valuePair.second;
143 }
144 return outMtSlotValues;
145 default:
146 return base::ResultError("Axis not supported", NAME_NOT_FOUND);
147 }
148 });
149 }
150
151 std::list<NotifyArgs> processPosition(int32_t x, int32_t y) {
152 std::list<NotifyArgs> args;
153 args += process(EV_ABS, ABS_MT_POSITION_X, x);
154 args += process(EV_ABS, ABS_MT_POSITION_Y, y);
155 return args;
156 }
157
158 std::list<NotifyArgs> processId(int32_t id) { return process(EV_ABS, ABS_MT_TRACKING_ID, id); }
159
160 std::list<NotifyArgs> processKey(int32_t code, int32_t value) {
161 return process(EV_KEY, code, value);
162 }
163
164 std::list<NotifyArgs> processSlot(int32_t slot) { return process(EV_ABS, ABS_MT_SLOT, slot); }
165
166 std::list<NotifyArgs> processSync() { return process(EV_SYN, SYN_REPORT, 0); }
167};
168
169// This test simulates a multi-finger gesture with unexpected reset in between. This might happen
170// due to buffer overflow and device with report a SYN_DROPPED. In this case we expect mapper to be
171// reset, MT slot state to be re-populated and the gesture should be cancelled and restarted.
172TEST_F(MultiTouchInputMapperUnitTest, MultiFingerGestureWithUnexpectedReset) {
173 std::list<NotifyArgs> args;
174
175 // Two fingers down at once.
176 constexpr int32_t FIRST_TRACKING_ID = 1, SECOND_TRACKING_ID = 2;
177 int32_t x1 = 100, y1 = 125, x2 = 200, y2 = 225;
178 processKey(BTN_TOUCH, 1);
179 args += processPosition(x1, y1);
180 args += processId(FIRST_TRACKING_ID);
181 args += processSlot(1);
182 args += processPosition(x2, y2);
183 args += processId(SECOND_TRACKING_ID);
184 ASSERT_THAT(args, IsEmpty());
185
186 args = processSync();
187 ASSERT_THAT(args,
188 ElementsAre(VariantWith<NotifyMotionArgs>(
189 WithMotionAction(AMOTION_EVENT_ACTION_DOWN)),
190 VariantWith<NotifyMotionArgs>(
191 WithMotionAction(ACTION_POINTER_1_DOWN))));
192
193 // Move.
194 x1 += 10;
195 y1 += 15;
196 x2 += 5;
197 y2 -= 10;
198 args = processSlot(0);
199 args += processPosition(x1, y1);
200 args += processSlot(1);
201 args += processPosition(x2, y2);
202 ASSERT_THAT(args, IsEmpty());
203
204 args = processSync();
205 ASSERT_THAT(args,
206 ElementsAre(VariantWith<NotifyMotionArgs>(
207 WithMotionAction(AMOTION_EVENT_ACTION_MOVE))));
208 const auto pointerCoordsBeforeReset = std::get<NotifyMotionArgs>(args.back()).pointerCoords;
209
210 // On buffer overflow mapper will be reset and MT slots data will be repopulated
Harry Cuttse2c5e202024-06-21 17:08:48 +0000211 EXPECT_CALL(mMockEventHub, getAbsoluteAxisValue(EVENTHUB_ID, ABS_MT_SLOT))
212 .WillRepeatedly(Return(1));
Arpit Singh4b4a4572023-11-24 18:19:56 +0000213
214 mockSlotValues(
215 {{1, {Point{x1, y1}, FIRST_TRACKING_ID}}, {2, {Point{x2, y2}, SECOND_TRACKING_ID}}});
216
217 setScanCodeState(KeyState::DOWN, {BTN_TOUCH});
218
219 args = mMapper->reset(systemTime(SYSTEM_TIME_MONOTONIC));
220 ASSERT_THAT(args,
221 ElementsAre(VariantWith<NotifyMotionArgs>(
222 WithMotionAction(AMOTION_EVENT_ACTION_CANCEL))));
223
224 // SYN_REPORT should restart the gesture again
225 args = processSync();
226 ASSERT_THAT(args,
227 ElementsAre(VariantWith<NotifyMotionArgs>(
228 WithMotionAction(AMOTION_EVENT_ACTION_DOWN)),
229 VariantWith<NotifyMotionArgs>(
230 WithMotionAction(ACTION_POINTER_1_DOWN))));
231 ASSERT_EQ(std::get<NotifyMotionArgs>(args.back()).pointerCoords, pointerCoordsBeforeReset);
232
233 // Move.
234 x1 += 10;
235 y1 += 15;
236 x2 += 5;
237 y2 -= 10;
238 args = processSlot(0);
239 args += processPosition(x1, y1);
240 args += processSlot(1);
241 args += processPosition(x2, y2);
242 ASSERT_THAT(args, IsEmpty());
243
244 args = processSync();
245 ASSERT_THAT(args,
246 ElementsAre(VariantWith<NotifyMotionArgs>(
247 WithMotionAction(AMOTION_EVENT_ACTION_MOVE))));
248
249 // First finger up.
250 args = processSlot(0);
251 args += processId(-1);
252 ASSERT_THAT(args, IsEmpty());
253
254 args = processSync();
255 ASSERT_THAT(args,
256 ElementsAre(VariantWith<NotifyMotionArgs>(WithMotionAction(ACTION_POINTER_0_UP))));
257
258 // Second finger up.
259 processKey(BTN_TOUCH, 0);
260 args = processSlot(1);
261 args += processId(-1);
262 ASSERT_THAT(args, IsEmpty());
263
264 args = processSync();
265 ASSERT_THAT(args,
266 ElementsAre(
267 VariantWith<NotifyMotionArgs>(WithMotionAction(AMOTION_EVENT_ACTION_UP))));
268}
269
270} // namespace android