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 "ScopedFlagOverride.h"
27 #include "TestEventMatchers.h"
28
29 #define TAG "MultiTouchpadInputMapperUnit_test"
30
31 namespace android {
32
33 using testing::_;
34 using testing::AllOf;
35 using testing::IsEmpty;
36 using testing::Return;
37 using testing::SetArgPointee;
38 using testing::VariantWith;
39
40 namespace {
41
42 constexpr ui::LogicalDisplayId DISPLAY_ID = ui::LogicalDisplayId::DEFAULT;
43 constexpr ui::LogicalDisplayId SECOND_DISPLAY_ID = ui::LogicalDisplayId{DISPLAY_ID.val() + 1};
44 constexpr int32_t DISPLAY_WIDTH = 480;
45 constexpr int32_t DISPLAY_HEIGHT = 800;
46 constexpr std::optional<uint8_t> NO_PORT = std::nullopt; // no physical port is specified
47 constexpr int32_t SLOT_COUNT = 5;
48
49 constexpr int32_t ACTION_DOWN = AMOTION_EVENT_ACTION_DOWN;
50 constexpr int32_t ACTION_CANCEL = AMOTION_EVENT_ACTION_CANCEL;
51 constexpr int32_t ACTION_POINTER_0_UP =
52 AMOTION_EVENT_ACTION_POINTER_UP | (0 << AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT);
53 constexpr int32_t ACTION_POINTER_1_DOWN =
54 AMOTION_EVENT_ACTION_POINTER_DOWN | (1 << AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT);
55
56 template <typename... Args>
assertNotifyArgs(const std::list<NotifyArgs> & args,Args...matchers)57 void assertNotifyArgs(const std::list<NotifyArgs>& args, Args... matchers) {
58 ASSERT_THAT(args, ElementsAre(matchers...))
59 << "Got instead: " << dumpContainer(args, streamableToString);
60 }
61
62 } // namespace
63
64 /**
65 * Unit tests for MultiTouchInputMapper.
66 */
67 class MultiTouchInputMapperUnitTest : public InputMapperUnitTest {
68 protected:
SetUp()69 void SetUp() override { SetUp(/*bus=*/0, /*isExternal=*/false); }
SetUp(int bus,bool isExternal)70 void SetUp(int bus, bool isExternal) override {
71 InputMapperUnitTest::SetUp(bus, isExternal);
72
73 // Present scan codes
74 expectScanCodes(/*present=*/true,
75 {BTN_TOUCH, BTN_TOOL_FINGER, BTN_TOOL_DOUBLETAP, BTN_TOOL_TRIPLETAP,
76 BTN_TOOL_QUADTAP, BTN_TOOL_QUINTTAP});
77
78 // Missing scan codes that the mapper checks for.
79 expectScanCodes(/*present=*/false,
80 {BTN_TOOL_PEN, BTN_TOOL_RUBBER, BTN_TOOL_BRUSH, BTN_TOOL_PENCIL,
81 BTN_TOOL_AIRBRUSH});
82
83 // Current scan code state - all keys are UP by default
84 setScanCodeState(KeyState::UP, {BTN_LEFT, BTN_RIGHT, BTN_MIDDLE,
85 BTN_BACK, BTN_SIDE, BTN_FORWARD,
86 BTN_EXTRA, BTN_TASK, BTN_TOUCH,
87 BTN_STYLUS, BTN_STYLUS2, BTN_0,
88 BTN_TOOL_FINGER, BTN_TOOL_PEN, BTN_TOOL_RUBBER,
89 BTN_TOOL_BRUSH, BTN_TOOL_PENCIL, BTN_TOOL_AIRBRUSH,
90 BTN_TOOL_MOUSE, BTN_TOOL_LENS, BTN_TOOL_DOUBLETAP,
91 BTN_TOOL_TRIPLETAP, BTN_TOOL_QUADTAP, BTN_TOOL_QUINTTAP});
92
93 setKeyCodeState(KeyState::UP,
94 {AKEYCODE_STYLUS_BUTTON_PRIMARY, AKEYCODE_STYLUS_BUTTON_SECONDARY});
95
96 // Input properties - only INPUT_PROP_DIRECT for touchscreen
97 EXPECT_CALL(mMockEventHub, hasInputProperty(EVENTHUB_ID, _)).WillRepeatedly(Return(false));
98 EXPECT_CALL(mMockEventHub, hasInputProperty(EVENTHUB_ID, INPUT_PROP_DIRECT))
99 .WillRepeatedly(Return(true));
100 // The following EXPECT_CALL lines are not load-bearing, but without them gtest prints
101 // warnings about "uninteresting mocked call", which are distracting when developing the
102 // tests because this text is interleaved with logs of interest.
103 EXPECT_CALL(mMockEventHub, getVirtualKeyDefinitions(EVENTHUB_ID, _))
104 .WillRepeatedly(Return());
105 EXPECT_CALL(mMockEventHub, hasRelativeAxis(EVENTHUB_ID, _))
106 .WillRepeatedly(testing::Return(false));
107 EXPECT_CALL(mMockEventHub, getVideoFrames(EVENTHUB_ID))
108 .WillRepeatedly(testing::Return(std::vector<TouchVideoFrame>{}));
109 EXPECT_CALL(mMockInputReaderContext, getExternalStylusDevices(_)).WillRepeatedly(Return());
110 EXPECT_CALL(mMockInputReaderContext, getGlobalMetaState()).WillRepeatedly(Return(0));
111
112 // Axes that the device has
113 setupAxis(ABS_MT_SLOT, /*valid=*/true, /*min=*/0, /*max=*/SLOT_COUNT - 1, /*resolution=*/0);
114 setupAxis(ABS_MT_TRACKING_ID, /*valid=*/true, /*min*/ 0, /*max=*/255, /*resolution=*/0);
115 setupAxis(ABS_MT_POSITION_X, /*valid=*/true, /*min=*/0, /*max=*/2000, /*resolution=*/24);
116 setupAxis(ABS_MT_POSITION_Y, /*valid=*/true, /*min=*/0, /*max=*/1000, /*resolution=*/24);
117
118 // Axes that the device does not have
119 setupAxis(ABS_MT_PRESSURE, /*valid=*/false, /*min*/ 0, /*max=*/255, /*resolution=*/0);
120 setupAxis(ABS_MT_ORIENTATION, /*valid=*/false, /*min=*/0, /*max=*/0, /*resolution=*/0);
121 setupAxis(ABS_MT_DISTANCE, /*valid=*/false, /*min=*/0, /*max=*/0, /*resolution=*/0);
122 setupAxis(ABS_MT_TOUCH_MAJOR, /*valid=*/false, /*min=*/0, /*max=*/0, /*resolution=*/0);
123 setupAxis(ABS_MT_TOUCH_MINOR, /*valid=*/false, /*min=*/0, /*max=*/0, /*resolution=*/0);
124 setupAxis(ABS_MT_WIDTH_MAJOR, /*valid=*/false, /*min=*/0, /*max=*/0, /*resolution=*/0);
125 setupAxis(ABS_MT_WIDTH_MINOR, /*valid=*/false, /*min=*/0, /*max=*/0, /*resolution=*/0);
126 setupAxis(ABS_MT_TOOL_TYPE, /*valid=*/false, /*min=*/0, /*max=*/0, /*resolution=*/0);
127
128 // reset current slot at the beginning
129 EXPECT_CALL(mMockEventHub, getAbsoluteAxisValue(EVENTHUB_ID, ABS_MT_SLOT))
130 .WillRepeatedly(Return(0));
131
132 // mark all slots not in use
133 mockSlotValues({});
134
135 mFakePolicy->setDefaultPointerDisplayId(DISPLAY_ID);
136 DisplayViewport internalViewport =
137 createViewport(DISPLAY_ID, DISPLAY_WIDTH, DISPLAY_HEIGHT, ui::ROTATION_0,
138 /*isActive=*/true, "local:0", NO_PORT, ViewportType::INTERNAL);
139 mFakePolicy->addDisplayViewport(internalViewport);
140 mMapper = createInputMapper<MultiTouchInputMapper>(*mDeviceContext,
141 mFakePolicy->getReaderConfiguration());
142 }
143
144 // Mocks position and tracking Ids for the provided slots. Remaining slots will be marked
145 // unused.
mockSlotValues(const std::unordered_map<int32_t,std::pair<Point,int32_t>> & slotValues)146 void mockSlotValues(
147 const std::unordered_map<int32_t /*slotIndex*/,
148 std::pair<Point /*position*/, int32_t /*trackingId*/>>&
149 slotValues) {
150 EXPECT_CALL(mMockEventHub, getMtSlotValues(EVENTHUB_ID, _, SLOT_COUNT))
151 .WillRepeatedly([=](int32_t, int32_t axis,
152 size_t slotCount) -> base::Result<std::vector<int32_t>> {
153 // tracking Id for the unused slots must set to be < 0
154 std::vector<int32_t> outMtSlotValues(slotCount + 1, -1);
155 outMtSlotValues[0] = axis;
156 switch (axis) {
157 case ABS_MT_POSITION_X:
158 for (const auto& [slotIndex, valuePair] : slotValues) {
159 outMtSlotValues[slotIndex] = valuePair.first.x;
160 }
161 return outMtSlotValues;
162 case ABS_MT_POSITION_Y:
163 for (const auto& [slotIndex, valuePair] : slotValues) {
164 outMtSlotValues[slotIndex] = valuePair.first.y;
165 }
166 return outMtSlotValues;
167 case ABS_MT_TRACKING_ID:
168 for (const auto& [slotIndex, valuePair] : slotValues) {
169 outMtSlotValues[slotIndex] = valuePair.second;
170 }
171 return outMtSlotValues;
172 default:
173 return base::ResultError("Axis not supported", NAME_NOT_FOUND);
174 }
175 });
176 }
177
processPosition(int32_t x,int32_t y)178 [[nodiscard]] std::list<NotifyArgs> processPosition(int32_t x, int32_t y) {
179 std::list<NotifyArgs> args;
180 args += process(EV_ABS, ABS_MT_POSITION_X, x);
181 args += process(EV_ABS, ABS_MT_POSITION_Y, y);
182 return args;
183 }
184
processId(int32_t id)185 [[nodiscard]] std::list<NotifyArgs> processId(int32_t id) {
186 return process(EV_ABS, ABS_MT_TRACKING_ID, id);
187 }
188
processKey(int32_t code,int32_t value)189 [[nodiscard]] std::list<NotifyArgs> processKey(int32_t code, int32_t value) {
190 return process(EV_KEY, code, value);
191 }
192
processSlot(int32_t slot)193 [[nodiscard]] std::list<NotifyArgs> processSlot(int32_t slot) {
194 return process(EV_ABS, ABS_MT_SLOT, slot);
195 }
196
processSync()197 [[nodiscard]] std::list<NotifyArgs> processSync() { return process(EV_SYN, SYN_REPORT, 0); }
198 };
199
200 /**
201 * While a gesture is active, change the display that the device is associated with. Make sure that
202 * the CANCEL event that's generated has the display id of the original DOWN event, rather than the
203 * new display id.
204 */
TEST_F(MultiTouchInputMapperUnitTest,ChangeAssociatedDisplayIdWhenTouchIsActive)205 TEST_F(MultiTouchInputMapperUnitTest, ChangeAssociatedDisplayIdWhenTouchIsActive) {
206 std::list<NotifyArgs> args;
207
208 // Add a second viewport that later will be associated with our device.
209 DisplayViewport secondViewport =
210 createViewport(SECOND_DISPLAY_ID, DISPLAY_WIDTH, DISPLAY_HEIGHT, ui::ROTATION_0,
211 /*isActive=*/true, "local:1", NO_PORT, ViewportType::EXTERNAL);
212 mFakePolicy->addDisplayViewport(secondViewport);
213 std::optional<DisplayViewport> firstViewport =
214 mFakePolicy->getDisplayViewportByUniqueId("local:0");
215
216 // InputReaderConfiguration contains information about how devices are associated with displays.
217 // The mapper receives this information. However, it doesn't actually parse it - that's done by
218 // InputDevice. The mapper asks InputDevice about the associated viewport, so that's what we
219 // need to mock here to simulate association. This abstraction is confusing and should be
220 // refactored.
221
222 // Start with the first viewport
223 ON_CALL((*mDevice), getAssociatedViewport).WillByDefault(Return(firstViewport));
224 args += mMapper->reconfigure(systemTime(SYSTEM_TIME_MONOTONIC), mReaderConfiguration,
225 InputReaderConfiguration::Change::DISPLAY_INFO);
226
227 int32_t x1 = 100, y1 = 125;
228 args += processKey(BTN_TOUCH, 1);
229 args += processPosition(x1, y1);
230 args += processId(1);
231 args += processSync();
232 ASSERT_THAT(args,
233 ElementsAre(VariantWith<NotifyMotionArgs>(
234 AllOf(WithMotionAction(ACTION_DOWN), WithDisplayId(DISPLAY_ID)))));
235 args.clear();
236
237 // Now associate with the second viewport, and reconfigure.
238 ON_CALL((*mDevice), getAssociatedViewport).WillByDefault(Return(secondViewport));
239 args += mMapper->reconfigure(systemTime(SYSTEM_TIME_MONOTONIC), mReaderConfiguration,
240 InputReaderConfiguration::Change::DISPLAY_INFO);
241 assertNotifyArgs(args,
242 VariantWith<NotifyMotionArgs>(
243 AllOf(WithMotionAction(ACTION_CANCEL), WithDisplayId(DISPLAY_ID))),
244 VariantWith<NotifyDeviceResetArgs>(WithDeviceId(DEVICE_ID)));
245
246 // The remainder of the gesture is ignored
247 // Move.
248 x1 += 10;
249 y1 += 15;
250 args = processPosition(x1, y1);
251 args += processSync();
252 // Up
253 args += processKey(BTN_TOUCH, 0);
254 args += processId(-1);
255 args += processSync();
256
257 ASSERT_THAT(args, IsEmpty());
258
259 // New touch is delivered with the new display id.
260 args += processId(2);
261 args += processKey(BTN_TOUCH, 1);
262 args += processPosition(x1 + 20, y1 + 40);
263 args += processSync();
264 assertNotifyArgs(args,
265 VariantWith<NotifyMotionArgs>(AllOf(WithMotionAction(ACTION_DOWN),
266 WithDisplayId(SECOND_DISPLAY_ID))));
267 }
268
269 // This test simulates a multi-finger gesture with unexpected reset in between. This might happen
270 // due to buffer overflow and device with report a SYN_DROPPED. In this case we expect mapper to be
271 // reset, MT slot state to be re-populated and the gesture should be cancelled and restarted.
TEST_F(MultiTouchInputMapperUnitTest,MultiFingerGestureWithUnexpectedReset)272 TEST_F(MultiTouchInputMapperUnitTest, MultiFingerGestureWithUnexpectedReset) {
273 std::list<NotifyArgs> args;
274
275 // Two fingers down at once.
276 constexpr int32_t FIRST_TRACKING_ID = 1, SECOND_TRACKING_ID = 2;
277 int32_t x1 = 100, y1 = 125, x2 = 200, y2 = 225;
278 args += processKey(BTN_TOUCH, 1);
279 args += processPosition(x1, y1);
280 args += processId(FIRST_TRACKING_ID);
281 args += processSlot(1);
282 args += processPosition(x2, y2);
283 args += processId(SECOND_TRACKING_ID);
284 ASSERT_THAT(args, IsEmpty());
285
286 args += processSync();
287 ASSERT_THAT(args,
288 ElementsAre(VariantWith<NotifyMotionArgs>(
289 WithMotionAction(AMOTION_EVENT_ACTION_DOWN)),
290 VariantWith<NotifyMotionArgs>(
291 WithMotionAction(ACTION_POINTER_1_DOWN))));
292
293 // Move.
294 x1 += 10;
295 y1 += 15;
296 x2 += 5;
297 y2 -= 10;
298 args = processSlot(0);
299 args += processPosition(x1, y1);
300 args += processSlot(1);
301 args += processPosition(x2, y2);
302 ASSERT_THAT(args, IsEmpty());
303
304 args = processSync();
305 ASSERT_THAT(args,
306 ElementsAre(VariantWith<NotifyMotionArgs>(
307 WithMotionAction(AMOTION_EVENT_ACTION_MOVE))));
308 const auto pointerCoordsBeforeReset = std::get<NotifyMotionArgs>(args.back()).pointerCoords;
309
310 // On buffer overflow mapper will be reset and MT slots data will be repopulated
311 EXPECT_CALL(mMockEventHub, getAbsoluteAxisValue(EVENTHUB_ID, ABS_MT_SLOT))
312 .WillRepeatedly(Return(1));
313
314 mockSlotValues(
315 {{1, {Point{x1, y1}, FIRST_TRACKING_ID}}, {2, {Point{x2, y2}, SECOND_TRACKING_ID}}});
316
317 setScanCodeState(KeyState::DOWN, {BTN_TOUCH});
318
319 args = mMapper->reset(systemTime(SYSTEM_TIME_MONOTONIC));
320 ASSERT_THAT(args,
321 ElementsAre(VariantWith<NotifyMotionArgs>(
322 WithMotionAction(AMOTION_EVENT_ACTION_CANCEL))));
323
324 // SYN_REPORT should restart the gesture again
325 args = processSync();
326 ASSERT_THAT(args,
327 ElementsAre(VariantWith<NotifyMotionArgs>(
328 WithMotionAction(AMOTION_EVENT_ACTION_DOWN)),
329 VariantWith<NotifyMotionArgs>(
330 WithMotionAction(ACTION_POINTER_1_DOWN))));
331 ASSERT_EQ(std::get<NotifyMotionArgs>(args.back()).pointerCoords, pointerCoordsBeforeReset);
332
333 // Move.
334 x1 += 10;
335 y1 += 15;
336 x2 += 5;
337 y2 -= 10;
338 args = processSlot(0);
339 args += processPosition(x1, y1);
340 args += processSlot(1);
341 args += processPosition(x2, y2);
342 ASSERT_THAT(args, IsEmpty());
343
344 args = processSync();
345 ASSERT_THAT(args,
346 ElementsAre(VariantWith<NotifyMotionArgs>(
347 WithMotionAction(AMOTION_EVENT_ACTION_MOVE))));
348
349 // First finger up.
350 args = processSlot(0);
351 args += processId(-1);
352 ASSERT_THAT(args, IsEmpty());
353
354 args = processSync();
355 ASSERT_THAT(args,
356 ElementsAre(VariantWith<NotifyMotionArgs>(WithMotionAction(ACTION_POINTER_0_UP))));
357
358 // Second finger up.
359 args = processKey(BTN_TOUCH, 0);
360 args += processSlot(1);
361 args += processId(-1);
362 ASSERT_THAT(args, IsEmpty());
363
364 args = processSync();
365 ASSERT_THAT(args,
366 ElementsAre(
367 VariantWith<NotifyMotionArgs>(WithMotionAction(AMOTION_EVENT_ACTION_UP))));
368 }
369
370 class ExternalMultiTouchInputMapperTest : public MultiTouchInputMapperUnitTest {
371 protected:
SetUp()372 void SetUp() override { MultiTouchInputMapperUnitTest::SetUp(/*bus=*/0, /*isExternal=*/true); }
373 };
374
375 /**
376 * Expect fallback to internal viewport if device is external and external viewport is not present.
377 */
TEST_F(ExternalMultiTouchInputMapperTest,Viewports_Fallback)378 TEST_F(ExternalMultiTouchInputMapperTest, Viewports_Fallback) {
379 std::list<NotifyArgs> args;
380
381 // Expect the event to be sent to the internal viewport,
382 // because an external viewport is not present.
383 args += processKey(BTN_TOUCH, 1);
384 args += processId(1);
385 args += processPosition(100, 200);
386 args += processSync();
387
388 assertNotifyArgs(args,
389 VariantWith<NotifyMotionArgs>(
390 AllOf(WithMotionAction(ACTION_DOWN), WithDisplayId(DISPLAY_ID))));
391
392 // Expect the event to be sent to the external viewport if it is present.
393 DisplayViewport externalViewport =
394 createViewport(SECOND_DISPLAY_ID, DISPLAY_WIDTH, DISPLAY_HEIGHT, ui::ROTATION_0,
395 /*isActive=*/true, "local:1", NO_PORT, ViewportType::EXTERNAL);
396 mFakePolicy->addDisplayViewport(externalViewport);
397 std::optional<DisplayViewport> internalViewport =
398 mFakePolicy->getDisplayViewportByUniqueId("local:0");
399 mReaderConfiguration.setDisplayViewports({*internalViewport, externalViewport});
400 args = mMapper->reconfigure(systemTime(SYSTEM_TIME_MONOTONIC), mReaderConfiguration,
401 InputReaderConfiguration::Change::DISPLAY_INFO);
402
403 assertNotifyArgs(args,
404 VariantWith<NotifyMotionArgs>(
405 AllOf(WithMotionAction(ACTION_CANCEL), WithDisplayId(DISPLAY_ID))),
406 VariantWith<NotifyDeviceResetArgs>(WithDeviceId(DEVICE_ID)));
407 // Lift up the old pointer.
408 args = processKey(BTN_TOUCH, 0);
409 args += processId(-1);
410 args += processSync();
411
412 // Send new pointer
413 args += processKey(BTN_TOUCH, 1);
414 args += processId(2);
415 args += processPosition(111, 211);
416 args += processSync();
417 assertNotifyArgs(args,
418 VariantWith<NotifyMotionArgs>(AllOf(WithMotionAction(ACTION_DOWN),
419 WithDisplayId(SECOND_DISPLAY_ID))));
420 }
421
422 class MultiTouchInputMapperPointerModeUnitTest : public MultiTouchInputMapperUnitTest {
423 protected:
SetUp()424 void SetUp() override {
425 MultiTouchInputMapperUnitTest::SetUp();
426
427 // TouchInputMapper goes into POINTER mode whenever INPUT_PROP_DIRECT is not set.
428 EXPECT_CALL(mMockEventHub, hasInputProperty(EVENTHUB_ID, INPUT_PROP_DIRECT))
429 .WillRepeatedly(Return(false));
430
431 mMapper = createInputMapper<MultiTouchInputMapper>(*mDeviceContext,
432 mFakePolicy->getReaderConfiguration());
433 }
434 };
435
TEST_F(MultiTouchInputMapperPointerModeUnitTest,MouseToolOnlyDownWhenMouseButtonsAreDown)436 TEST_F(MultiTouchInputMapperPointerModeUnitTest, MouseToolOnlyDownWhenMouseButtonsAreDown) {
437 SCOPED_FLAG_OVERRIDE(disable_touch_input_mapper_pointer_usage, true);
438
439 std::list<NotifyArgs> args;
440
441 // Set the tool type to mouse.
442 args += processKey(BTN_TOOL_MOUSE, 1);
443
444 args += processPosition(100, 100);
445 args += processId(1);
446 ASSERT_THAT(args, IsEmpty());
447
448 args = processSync();
449 ASSERT_THAT(args,
450 ElementsAre(VariantWith<NotifyMotionArgs>(
451 AllOf(WithMotionAction(AMOTION_EVENT_ACTION_HOVER_ENTER),
452 WithToolType(ToolType::MOUSE))),
453 VariantWith<NotifyMotionArgs>(
454 AllOf(WithMotionAction(AMOTION_EVENT_ACTION_HOVER_MOVE),
455 WithToolType(ToolType::MOUSE)))));
456
457 // Setting BTN_TOUCH does not make a mouse pointer go down.
458 args = processKey(BTN_TOUCH, 1);
459 args += processSync();
460 ASSERT_THAT(args,
461 ElementsAre(VariantWith<NotifyMotionArgs>(
462 WithMotionAction(AMOTION_EVENT_ACTION_HOVER_MOVE))));
463
464 // The mouse button is pressed, so the mouse goes down.
465 args = processKey(BTN_MOUSE, 1);
466 args += processSync();
467 ASSERT_THAT(args,
468 ElementsAre(VariantWith<NotifyMotionArgs>(
469 AllOf(WithMotionAction(AMOTION_EVENT_ACTION_HOVER_EXIT),
470 WithToolType(ToolType::MOUSE))),
471 VariantWith<NotifyMotionArgs>(
472 AllOf(WithMotionAction(AMOTION_EVENT_ACTION_DOWN),
473 WithToolType(ToolType::MOUSE),
474 WithButtonState(AMOTION_EVENT_BUTTON_PRIMARY))),
475 VariantWith<NotifyMotionArgs>(
476 AllOf(WithMotionAction(AMOTION_EVENT_ACTION_BUTTON_PRESS),
477 WithToolType(ToolType::MOUSE),
478 WithButtonState(AMOTION_EVENT_BUTTON_PRIMARY),
479 WithActionButton(AMOTION_EVENT_BUTTON_PRIMARY)))));
480
481 // The mouse button is released, so the mouse starts hovering.
482 args = processKey(BTN_MOUSE, 0);
483 args += processSync();
484 ASSERT_THAT(args,
485 ElementsAre(VariantWith<NotifyMotionArgs>(
486 AllOf(WithMotionAction(AMOTION_EVENT_ACTION_BUTTON_RELEASE),
487 WithButtonState(0), WithToolType(ToolType::MOUSE),
488 WithActionButton(AMOTION_EVENT_BUTTON_PRIMARY))),
489 VariantWith<NotifyMotionArgs>(
490 AllOf(WithMotionAction(AMOTION_EVENT_ACTION_UP),
491 WithToolType(ToolType::MOUSE), WithButtonState(0))),
492 VariantWith<NotifyMotionArgs>(
493 AllOf(WithMotionAction(AMOTION_EVENT_ACTION_HOVER_ENTER),
494 WithToolType(ToolType::MOUSE))),
495 VariantWith<NotifyMotionArgs>(
496 AllOf(WithMotionAction(AMOTION_EVENT_ACTION_HOVER_MOVE),
497 WithToolType(ToolType::MOUSE)))));
498
499 // Change the tool type so that it is no longer a mouse.
500 // The default tool type is finger, and the finger is already down.
501 args = processKey(BTN_TOOL_MOUSE, 0);
502 args += processSync();
503 ASSERT_THAT(args,
504 ElementsAre(VariantWith<NotifyMotionArgs>(
505 AllOf(WithMotionAction(AMOTION_EVENT_ACTION_HOVER_EXIT),
506 WithToolType(ToolType::MOUSE))),
507 VariantWith<NotifyMotionArgs>(
508 AllOf(WithMotionAction(AMOTION_EVENT_ACTION_DOWN),
509 WithToolType(ToolType::FINGER)))));
510 }
511
512 } // namespace android
513