1 /*
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 
30 namespace android {
31 
32 using testing::_;
33 using testing::IsEmpty;
34 using testing::Return;
35 using testing::SetArgPointee;
36 using testing::VariantWith;
37 
38 static constexpr ui::LogicalDisplayId DISPLAY_ID = ui::LogicalDisplayId::DEFAULT;
39 static constexpr int32_t DISPLAY_WIDTH = 480;
40 static constexpr int32_t DISPLAY_HEIGHT = 800;
41 static constexpr std::optional<uint8_t> NO_PORT = std::nullopt; // no physical port is specified
42 static constexpr int32_t SLOT_COUNT = 5;
43 
44 static constexpr int32_t ACTION_POINTER_0_UP =
45         AMOTION_EVENT_ACTION_POINTER_UP | (0 << AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT);
46 static 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  */
52 class MultiTouchInputMapperUnitTest : public InputMapperUnitTest {
53 protected:
SetUp()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
102         EXPECT_CALL(mMockEventHub, getAbsoluteAxisValue(EVENTHUB_ID, ABS_MT_SLOT, _))
103                 .WillRepeatedly([](int32_t, int32_t, int32_t* outValue) {
104                     *outValue = 0;
105                     return OK;
106                 });
107 
108         // mark all slots not in use
109         mockSlotValues({});
110 
111         mFakePolicy->setDefaultPointerDisplayId(DISPLAY_ID);
112         mFakePolicy->addDisplayViewport(DISPLAY_ID, DISPLAY_WIDTH, DISPLAY_HEIGHT, ui::ROTATION_0,
113                                         /*isActive=*/true, "local:0", NO_PORT,
114                                         ViewportType::INTERNAL);
115         createDevice();
116         mMapper = createInputMapper<MultiTouchInputMapper>(*mDeviceContext,
117                                                            mFakePolicy->getReaderConfiguration());
118     }
119 
120     // Mocks position and tracking Ids for the provided slots. Remaining slots will be marked
121     // unused.
mockSlotValues(const std::unordered_map<int32_t,std::pair<Point,int32_t>> & slotValues)122     void mockSlotValues(
123             const std::unordered_map<int32_t /*slotIndex*/,
124                                      std::pair<Point /*position*/, int32_t /*trackingId*/>>&
125                     slotValues) {
126         EXPECT_CALL(mMockEventHub, getMtSlotValues(EVENTHUB_ID, _, SLOT_COUNT))
127                 .WillRepeatedly([=](int32_t, int32_t axis,
128                                     size_t slotCount) -> base::Result<std::vector<int32_t>> {
129                     // tracking Id for the unused slots must set to be < 0
130                     std::vector<int32_t> outMtSlotValues(slotCount + 1, -1);
131                     outMtSlotValues[0] = axis;
132                     switch (axis) {
133                         case ABS_MT_POSITION_X:
134                             for (const auto& [slotIndex, valuePair] : slotValues) {
135                                 outMtSlotValues[slotIndex] = valuePair.first.x;
136                             }
137                             return outMtSlotValues;
138                         case ABS_MT_POSITION_Y:
139                             for (const auto& [slotIndex, valuePair] : slotValues) {
140                                 outMtSlotValues[slotIndex] = valuePair.first.y;
141                             }
142                             return outMtSlotValues;
143                         case ABS_MT_TRACKING_ID:
144                             for (const auto& [slotIndex, valuePair] : slotValues) {
145                                 outMtSlotValues[slotIndex] = valuePair.second;
146                             }
147                             return outMtSlotValues;
148                         default:
149                             return base::ResultError("Axis not supported", NAME_NOT_FOUND);
150                     }
151                 });
152     }
153 
processPosition(int32_t x,int32_t y)154     std::list<NotifyArgs> processPosition(int32_t x, int32_t y) {
155         std::list<NotifyArgs> args;
156         args += process(EV_ABS, ABS_MT_POSITION_X, x);
157         args += process(EV_ABS, ABS_MT_POSITION_Y, y);
158         return args;
159     }
160 
processId(int32_t id)161     std::list<NotifyArgs> processId(int32_t id) { return process(EV_ABS, ABS_MT_TRACKING_ID, id); }
162 
processKey(int32_t code,int32_t value)163     std::list<NotifyArgs> processKey(int32_t code, int32_t value) {
164         return process(EV_KEY, code, value);
165     }
166 
processSlot(int32_t slot)167     std::list<NotifyArgs> processSlot(int32_t slot) { return process(EV_ABS, ABS_MT_SLOT, slot); }
168 
processSync()169     std::list<NotifyArgs> processSync() { return process(EV_SYN, SYN_REPORT, 0); }
170 };
171 
172 // This test simulates a multi-finger gesture with unexpected reset in between. This might happen
173 // due to buffer overflow and device with report a SYN_DROPPED. In this case we expect mapper to be
174 // reset, MT slot state to be re-populated and the gesture should be cancelled and restarted.
TEST_F(MultiTouchInputMapperUnitTest,MultiFingerGestureWithUnexpectedReset)175 TEST_F(MultiTouchInputMapperUnitTest, MultiFingerGestureWithUnexpectedReset) {
176     std::list<NotifyArgs> args;
177 
178     // Two fingers down at once.
179     constexpr int32_t FIRST_TRACKING_ID = 1, SECOND_TRACKING_ID = 2;
180     int32_t x1 = 100, y1 = 125, x2 = 200, y2 = 225;
181     processKey(BTN_TOUCH, 1);
182     args += processPosition(x1, y1);
183     args += processId(FIRST_TRACKING_ID);
184     args += processSlot(1);
185     args += processPosition(x2, y2);
186     args += processId(SECOND_TRACKING_ID);
187     ASSERT_THAT(args, IsEmpty());
188 
189     args = processSync();
190     ASSERT_THAT(args,
191                 ElementsAre(VariantWith<NotifyMotionArgs>(
192                                     WithMotionAction(AMOTION_EVENT_ACTION_DOWN)),
193                             VariantWith<NotifyMotionArgs>(
194                                     WithMotionAction(ACTION_POINTER_1_DOWN))));
195 
196     // Move.
197     x1 += 10;
198     y1 += 15;
199     x2 += 5;
200     y2 -= 10;
201     args = processSlot(0);
202     args += processPosition(x1, y1);
203     args += processSlot(1);
204     args += processPosition(x2, y2);
205     ASSERT_THAT(args, IsEmpty());
206 
207     args = processSync();
208     ASSERT_THAT(args,
209                 ElementsAre(VariantWith<NotifyMotionArgs>(
210                         WithMotionAction(AMOTION_EVENT_ACTION_MOVE))));
211     const auto pointerCoordsBeforeReset = std::get<NotifyMotionArgs>(args.back()).pointerCoords;
212 
213     // On buffer overflow mapper will be reset and MT slots data will be repopulated
214     EXPECT_CALL(mMockEventHub, getAbsoluteAxisValue(EVENTHUB_ID, ABS_MT_SLOT, _))
215             .WillRepeatedly([=](int32_t, int32_t, int32_t* outValue) {
216                 *outValue = 1;
217                 return OK;
218             });
219 
220     mockSlotValues(
221             {{1, {Point{x1, y1}, FIRST_TRACKING_ID}}, {2, {Point{x2, y2}, SECOND_TRACKING_ID}}});
222 
223     setScanCodeState(KeyState::DOWN, {BTN_TOUCH});
224 
225     args = mMapper->reset(systemTime(SYSTEM_TIME_MONOTONIC));
226     ASSERT_THAT(args,
227                 ElementsAre(VariantWith<NotifyMotionArgs>(
228                         WithMotionAction(AMOTION_EVENT_ACTION_CANCEL))));
229 
230     // SYN_REPORT should restart the gesture again
231     args = processSync();
232     ASSERT_THAT(args,
233                 ElementsAre(VariantWith<NotifyMotionArgs>(
234                                     WithMotionAction(AMOTION_EVENT_ACTION_DOWN)),
235                             VariantWith<NotifyMotionArgs>(
236                                     WithMotionAction(ACTION_POINTER_1_DOWN))));
237     ASSERT_EQ(std::get<NotifyMotionArgs>(args.back()).pointerCoords, pointerCoordsBeforeReset);
238 
239     // Move.
240     x1 += 10;
241     y1 += 15;
242     x2 += 5;
243     y2 -= 10;
244     args = processSlot(0);
245     args += processPosition(x1, y1);
246     args += processSlot(1);
247     args += processPosition(x2, y2);
248     ASSERT_THAT(args, IsEmpty());
249 
250     args = processSync();
251     ASSERT_THAT(args,
252                 ElementsAre(VariantWith<NotifyMotionArgs>(
253                         WithMotionAction(AMOTION_EVENT_ACTION_MOVE))));
254 
255     // First finger up.
256     args = processSlot(0);
257     args += processId(-1);
258     ASSERT_THAT(args, IsEmpty());
259 
260     args = processSync();
261     ASSERT_THAT(args,
262                 ElementsAre(VariantWith<NotifyMotionArgs>(WithMotionAction(ACTION_POINTER_0_UP))));
263 
264     // Second finger up.
265     processKey(BTN_TOUCH, 0);
266     args = processSlot(1);
267     args += processId(-1);
268     ASSERT_THAT(args, IsEmpty());
269 
270     args = processSync();
271     ASSERT_THAT(args,
272                 ElementsAre(
273                         VariantWith<NotifyMotionArgs>(WithMotionAction(AMOTION_EVENT_ACTION_UP))));
274 }
275 
276 } // namespace android
277