blob: bd053603df32d355e53b05732104a300b1200d30 [file] [log] [blame]
Siarhei Vishniakoua3c8e512022-02-10 19:46:34 -08001/*
2 * Copyright (C) 2022 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 <gtest/gtest.h>
18#include "../PreferStylusOverTouchBlocker.h"
19
20namespace android {
21
22constexpr int32_t TOUCH_DEVICE_ID = 3;
Siarhei Vishniakoua6a660f2022-03-04 15:12:16 -080023constexpr int32_t SECOND_TOUCH_DEVICE_ID = 4;
24constexpr int32_t STYLUS_DEVICE_ID = 5;
25constexpr int32_t SECOND_STYLUS_DEVICE_ID = 6;
Siarhei Vishniakoua3c8e512022-02-10 19:46:34 -080026
27constexpr int DOWN = AMOTION_EVENT_ACTION_DOWN;
28constexpr int MOVE = AMOTION_EVENT_ACTION_MOVE;
29constexpr int UP = AMOTION_EVENT_ACTION_UP;
30constexpr int CANCEL = AMOTION_EVENT_ACTION_CANCEL;
Siarhei Vishniakoua6a660f2022-03-04 15:12:16 -080031static constexpr int32_t POINTER_1_DOWN =
32 AMOTION_EVENT_ACTION_POINTER_DOWN | (1 << AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT);
Siarhei Vishniakoua3c8e512022-02-10 19:46:34 -080033constexpr int32_t TOUCHSCREEN = AINPUT_SOURCE_TOUCHSCREEN;
34constexpr int32_t STYLUS = AINPUT_SOURCE_STYLUS;
35
Siarhei Vishniakoua3c8e512022-02-10 19:46:34 -080036static NotifyMotionArgs generateMotionArgs(nsecs_t downTime, nsecs_t eventTime, int32_t action,
Siarhei Vishniakoub8fd3ef2022-10-10 08:03:54 -070037 const std::vector<Point>& points, uint32_t source) {
Siarhei Vishniakoua3c8e512022-02-10 19:46:34 -080038 size_t pointerCount = points.size();
39 if (action == DOWN || action == UP) {
40 EXPECT_EQ(1U, pointerCount) << "Actions DOWN and UP can only contain a single pointer";
41 }
42
43 PointerProperties pointerProperties[pointerCount];
44 PointerCoords pointerCoords[pointerCount];
45
46 const int32_t deviceId = isFromSource(source, TOUCHSCREEN) ? TOUCH_DEVICE_ID : STYLUS_DEVICE_ID;
47 const int32_t toolType = isFromSource(source, TOUCHSCREEN) ? AMOTION_EVENT_TOOL_TYPE_FINGER
48 : AMOTION_EVENT_TOOL_TYPE_STYLUS;
49 for (size_t i = 0; i < pointerCount; i++) {
50 pointerProperties[i].clear();
51 pointerProperties[i].id = i;
52 pointerProperties[i].toolType = toolType;
53
54 pointerCoords[i].clear();
55 pointerCoords[i].setAxisValue(AMOTION_EVENT_AXIS_X, points[i].x);
56 pointerCoords[i].setAxisValue(AMOTION_EVENT_AXIS_Y, points[i].y);
57 }
58
59 // Currently, can't have STYLUS source without it also being a TOUCH source. Update the source
60 // accordingly.
61 if (isFromSource(source, STYLUS)) {
62 source |= TOUCHSCREEN;
63 }
64
65 // Define a valid motion event.
66 NotifyMotionArgs args(/* id */ 0, eventTime, 0 /*readTime*/, deviceId, source, 0 /*displayId*/,
67 POLICY_FLAG_PASS_TO_USER, action, /* actionButton */ 0,
68 /* flags */ 0, AMETA_NONE, /* buttonState */ 0,
69 MotionClassification::NONE, AMOTION_EVENT_EDGE_FLAG_NONE, pointerCount,
70 pointerProperties, pointerCoords, /* xPrecision */ 0, /* yPrecision */ 0,
71 AMOTION_EVENT_INVALID_CURSOR_POSITION,
72 AMOTION_EVENT_INVALID_CURSOR_POSITION, downTime, /* videoFrames */ {});
73
74 return args;
75}
76
77class PreferStylusOverTouchTest : public testing::Test {
78protected:
Siarhei Vishniakoua6a660f2022-03-04 15:12:16 -080079 void assertNotBlocked(const NotifyMotionArgs& args) { assertResponse(args, {args}); }
80
81 void assertDropped(const NotifyMotionArgs& args) { assertResponse(args, {}); }
82
83 void assertResponse(const NotifyMotionArgs& args,
84 const std::vector<NotifyMotionArgs>& expected) {
85 std::vector<NotifyMotionArgs> receivedArgs = mBlocker.processMotion(args);
86 ASSERT_EQ(expected.size(), receivedArgs.size());
87 for (size_t i = 0; i < expected.size(); i++) {
88 // The 'eventTime' of CANCEL events is dynamically generated. Don't check this field.
89 if (expected[i].action == CANCEL && receivedArgs[i].action == CANCEL) {
90 receivedArgs[i].eventTime = expected[i].eventTime;
91 }
92
93 ASSERT_EQ(expected[i], receivedArgs[i])
94 << expected[i].dump() << " vs " << receivedArgs[i].dump();
95 }
Siarhei Vishniakoua3c8e512022-02-10 19:46:34 -080096 }
97
Siarhei Vishniakoua6a660f2022-03-04 15:12:16 -080098 void notifyInputDevicesChanged(const std::vector<InputDeviceInfo>& devices) {
99 mBlocker.notifyInputDevicesChanged(devices);
Siarhei Vishniakoua3c8e512022-02-10 19:46:34 -0800100 }
101
Siarhei Vishniakoua6a660f2022-03-04 15:12:16 -0800102 void dump() const { ALOGI("Blocker: \n%s\n", mBlocker.dump().c_str()); }
Siarhei Vishniakoua3c8e512022-02-10 19:46:34 -0800103
104private:
105 PreferStylusOverTouchBlocker mBlocker;
106};
107
108TEST_F(PreferStylusOverTouchTest, TouchGestureIsNotBlocked) {
109 NotifyMotionArgs args;
110
111 args = generateMotionArgs(0 /*downTime*/, 0 /*eventTime*/, DOWN, {{1, 2}}, TOUCHSCREEN);
112 assertNotBlocked(args);
113
114 args = generateMotionArgs(0 /*downTime*/, 1 /*eventTime*/, MOVE, {{1, 3}}, TOUCHSCREEN);
115 assertNotBlocked(args);
116
117 args = generateMotionArgs(0 /*downTime*/, 2 /*eventTime*/, UP, {{1, 3}}, TOUCHSCREEN);
118 assertNotBlocked(args);
119}
120
121TEST_F(PreferStylusOverTouchTest, StylusGestureIsNotBlocked) {
122 NotifyMotionArgs args;
123
124 args = generateMotionArgs(0 /*downTime*/, 0 /*eventTime*/, DOWN, {{1, 2}}, STYLUS);
125 assertNotBlocked(args);
126
127 args = generateMotionArgs(0 /*downTime*/, 1 /*eventTime*/, MOVE, {{1, 3}}, STYLUS);
128 assertNotBlocked(args);
129
130 args = generateMotionArgs(0 /*downTime*/, 2 /*eventTime*/, UP, {{1, 3}}, STYLUS);
131 assertNotBlocked(args);
132}
133
134/**
135 * Existing touch gesture should be canceled when stylus goes down. There should be an ACTION_CANCEL
136 * event generated.
137 */
138TEST_F(PreferStylusOverTouchTest, TouchIsCanceledWhenStylusGoesDown) {
139 NotifyMotionArgs args;
140
141 args = generateMotionArgs(0 /*downTime*/, 0 /*eventTime*/, DOWN, {{1, 2}}, TOUCHSCREEN);
142 assertNotBlocked(args);
143
144 args = generateMotionArgs(0 /*downTime*/, 1 /*eventTime*/, MOVE, {{1, 3}}, TOUCHSCREEN);
145 assertNotBlocked(args);
146
147 args = generateMotionArgs(3 /*downTime*/, 3 /*eventTime*/, DOWN, {{10, 30}}, STYLUS);
148 NotifyMotionArgs cancelArgs =
149 generateMotionArgs(0 /*downTime*/, 1 /*eventTime*/, CANCEL, {{1, 3}}, TOUCHSCREEN);
Siarhei Vishniakoua6a660f2022-03-04 15:12:16 -0800150 cancelArgs.flags |= AMOTION_EVENT_FLAG_CANCELED;
151 assertResponse(args, {cancelArgs, args});
Siarhei Vishniakoua3c8e512022-02-10 19:46:34 -0800152
153 // Both stylus and touch events continue. Stylus should be not blocked, and touch should be
154 // blocked
155 args = generateMotionArgs(3 /*downTime*/, 4 /*eventTime*/, MOVE, {{10, 31}}, STYLUS);
156 assertNotBlocked(args);
157
158 args = generateMotionArgs(0 /*downTime*/, 5 /*eventTime*/, MOVE, {{1, 4}}, TOUCHSCREEN);
159 assertDropped(args);
160}
161
162/**
Siarhei Vishniakoua6a660f2022-03-04 15:12:16 -0800163 * Stylus goes down after touch gesture.
164 */
165TEST_F(PreferStylusOverTouchTest, StylusDownAfterTouch) {
166 NotifyMotionArgs args;
167
168 args = generateMotionArgs(0 /*downTime*/, 0 /*eventTime*/, DOWN, {{1, 2}}, TOUCHSCREEN);
169 assertNotBlocked(args);
170
171 args = generateMotionArgs(0 /*downTime*/, 1 /*eventTime*/, MOVE, {{1, 3}}, TOUCHSCREEN);
172 assertNotBlocked(args);
173
174 args = generateMotionArgs(0 /*downTime*/, 2 /*eventTime*/, UP, {{1, 3}}, TOUCHSCREEN);
175 assertNotBlocked(args);
176
177 // Stylus goes down
178 args = generateMotionArgs(3 /*downTime*/, 3 /*eventTime*/, DOWN, {{10, 30}}, STYLUS);
179 assertNotBlocked(args);
180}
181
182/**
Siarhei Vishniakoua3c8e512022-02-10 19:46:34 -0800183 * New touch events should be simply blocked (dropped) when stylus is down. No CANCEL event should
184 * be generated.
185 */
186TEST_F(PreferStylusOverTouchTest, NewTouchIsBlockedWhenStylusIsDown) {
187 NotifyMotionArgs args;
188 constexpr nsecs_t stylusDownTime = 0;
189 constexpr nsecs_t touchDownTime = 1;
190
191 args = generateMotionArgs(stylusDownTime, 0 /*eventTime*/, DOWN, {{10, 30}}, STYLUS);
192 assertNotBlocked(args);
193
194 args = generateMotionArgs(touchDownTime, 1 /*eventTime*/, DOWN, {{1, 2}}, TOUCHSCREEN);
195 assertDropped(args);
196
197 // Stylus should continue to work
198 args = generateMotionArgs(stylusDownTime, 2 /*eventTime*/, MOVE, {{10, 31}}, STYLUS);
199 assertNotBlocked(args);
200
201 // Touch should continue to be blocked
202 args = generateMotionArgs(touchDownTime, 1 /*eventTime*/, MOVE, {{1, 3}}, TOUCHSCREEN);
203 assertDropped(args);
204
205 args = generateMotionArgs(0 /*downTime*/, 5 /*eventTime*/, MOVE, {{1, 4}}, TOUCHSCREEN);
206 assertDropped(args);
207}
208
209/**
210 * New touch events should be simply blocked (dropped) when stylus is down. No CANCEL event should
211 * be generated.
212 */
213TEST_F(PreferStylusOverTouchTest, NewTouchWorksAfterStylusIsLifted) {
214 NotifyMotionArgs args;
215 constexpr nsecs_t stylusDownTime = 0;
216 constexpr nsecs_t touchDownTime = 4;
217
218 // Stylus goes down and up
219 args = generateMotionArgs(stylusDownTime, 0 /*eventTime*/, DOWN, {{10, 30}}, STYLUS);
220 assertNotBlocked(args);
221
222 args = generateMotionArgs(stylusDownTime, 2 /*eventTime*/, MOVE, {{10, 31}}, STYLUS);
223 assertNotBlocked(args);
224
225 args = generateMotionArgs(stylusDownTime, 3 /*eventTime*/, UP, {{10, 31}}, STYLUS);
226 assertNotBlocked(args);
227
228 // New touch goes down. It should not be blocked
229 args = generateMotionArgs(touchDownTime, touchDownTime, DOWN, {{1, 2}}, TOUCHSCREEN);
230 assertNotBlocked(args);
231
232 args = generateMotionArgs(touchDownTime, 5 /*eventTime*/, MOVE, {{1, 3}}, TOUCHSCREEN);
233 assertNotBlocked(args);
234
235 args = generateMotionArgs(touchDownTime, 6 /*eventTime*/, UP, {{1, 3}}, TOUCHSCREEN);
236 assertNotBlocked(args);
237}
238
239/**
240 * Once a touch gesture is canceled, it should continue to be canceled, even if the stylus has been
241 * lifted.
242 */
243TEST_F(PreferStylusOverTouchTest, AfterStylusIsLiftedCurrentTouchIsBlocked) {
244 NotifyMotionArgs args;
245 constexpr nsecs_t stylusDownTime = 0;
246 constexpr nsecs_t touchDownTime = 1;
247
248 assertNotBlocked(generateMotionArgs(stylusDownTime, 0 /*eventTime*/, DOWN, {{10, 30}}, STYLUS));
249
250 args = generateMotionArgs(touchDownTime, 1 /*eventTime*/, DOWN, {{1, 2}}, TOUCHSCREEN);
251 assertDropped(args);
252
253 // Lift the stylus
254 args = generateMotionArgs(stylusDownTime, 2 /*eventTime*/, UP, {{10, 30}}, STYLUS);
255 assertNotBlocked(args);
256
257 // Touch should continue to be blocked
258 args = generateMotionArgs(touchDownTime, 3 /*eventTime*/, MOVE, {{1, 3}}, TOUCHSCREEN);
259 assertDropped(args);
260
261 args = generateMotionArgs(touchDownTime, 4 /*eventTime*/, UP, {{1, 3}}, TOUCHSCREEN);
262 assertDropped(args);
263
264 // New touch should go through, though.
265 constexpr nsecs_t newTouchDownTime = 5;
266 args = generateMotionArgs(newTouchDownTime, 5 /*eventTime*/, DOWN, {{10, 20}}, TOUCHSCREEN);
267 assertNotBlocked(args);
268}
269
Siarhei Vishniakoua6a660f2022-03-04 15:12:16 -0800270/**
271 * If an event with mixed stylus and touch pointers is encountered, it should be ignored. Touches
272 * from such should pass, even if stylus from the same device goes down.
273 */
274TEST_F(PreferStylusOverTouchTest, MixedStylusAndTouchPointersAreIgnored) {
275 NotifyMotionArgs args;
276
277 // Event from a stylus device, but with finger tool type
278 args = generateMotionArgs(1 /*downTime*/, 1 /*eventTime*/, DOWN, {{1, 2}}, STYLUS);
279 // Keep source stylus, but make the tool type touch
280 args.pointerProperties[0].toolType = AMOTION_EVENT_TOOL_TYPE_FINGER;
281 assertNotBlocked(args);
282
283 // Second pointer (stylus pointer) goes down, from the same device
284 args = generateMotionArgs(1 /*downTime*/, 2 /*eventTime*/, POINTER_1_DOWN, {{1, 2}, {10, 20}},
285 STYLUS);
286 // Keep source stylus, but make the tool type touch
287 args.pointerProperties[0].toolType = AMOTION_EVENT_TOOL_TYPE_STYLUS;
288 assertNotBlocked(args);
289
290 // Second pointer (stylus pointer) goes down, from the same device
291 args = generateMotionArgs(1 /*downTime*/, 3 /*eventTime*/, MOVE, {{2, 3}, {11, 21}}, STYLUS);
292 // Keep source stylus, but make the tool type touch
293 args.pointerProperties[0].toolType = AMOTION_EVENT_TOOL_TYPE_FINGER;
294 assertNotBlocked(args);
295}
296
297/**
298 * When there are two touch devices, stylus down should cancel all current touch streams.
299 */
300TEST_F(PreferStylusOverTouchTest, TouchFromTwoDevicesAndStylus) {
301 NotifyMotionArgs touch1Down =
302 generateMotionArgs(1 /*downTime*/, 1 /*eventTime*/, DOWN, {{1, 2}}, TOUCHSCREEN);
303 assertNotBlocked(touch1Down);
304
305 NotifyMotionArgs touch2Down =
306 generateMotionArgs(2 /*downTime*/, 2 /*eventTime*/, DOWN, {{3, 4}}, TOUCHSCREEN);
307 touch2Down.deviceId = SECOND_TOUCH_DEVICE_ID;
308 assertNotBlocked(touch2Down);
309
310 NotifyMotionArgs stylusDown =
311 generateMotionArgs(3 /*downTime*/, 3 /*eventTime*/, DOWN, {{10, 30}}, STYLUS);
312 NotifyMotionArgs cancelArgs1 = touch1Down;
313 cancelArgs1.action = CANCEL;
314 cancelArgs1.flags |= AMOTION_EVENT_FLAG_CANCELED;
315 NotifyMotionArgs cancelArgs2 = touch2Down;
316 cancelArgs2.action = CANCEL;
317 cancelArgs2.flags |= AMOTION_EVENT_FLAG_CANCELED;
318 assertResponse(stylusDown, {cancelArgs1, cancelArgs2, stylusDown});
319}
320
321/**
322 * Touch should be canceled when stylus goes down. After the stylus lifts up, the touch from that
323 * device should continue to be canceled.
324 * If one of the devices is already canceled, it should remain canceled, but new touches from a
325 * different device should go through.
326 */
327TEST_F(PreferStylusOverTouchTest, AllTouchMustLiftAfterCanceledByStylus) {
328 // First device touches down
329 NotifyMotionArgs touch1Down =
330 generateMotionArgs(1 /*downTime*/, 1 /*eventTime*/, DOWN, {{1, 2}}, TOUCHSCREEN);
331 assertNotBlocked(touch1Down);
332
333 // Stylus goes down - touch should be canceled
334 NotifyMotionArgs stylusDown =
335 generateMotionArgs(2 /*downTime*/, 2 /*eventTime*/, DOWN, {{10, 30}}, STYLUS);
336 NotifyMotionArgs cancelArgs1 = touch1Down;
337 cancelArgs1.action = CANCEL;
338 cancelArgs1.flags |= AMOTION_EVENT_FLAG_CANCELED;
339 assertResponse(stylusDown, {cancelArgs1, stylusDown});
340
341 // Stylus goes up
342 NotifyMotionArgs stylusUp =
343 generateMotionArgs(2 /*downTime*/, 3 /*eventTime*/, UP, {{10, 30}}, STYLUS);
344 assertNotBlocked(stylusUp);
345
346 // Touch from the first device remains blocked
347 NotifyMotionArgs touch1Move =
348 generateMotionArgs(1 /*downTime*/, 4 /*eventTime*/, MOVE, {{2, 3}}, TOUCHSCREEN);
349 assertDropped(touch1Move);
350
351 // Second touch goes down. It should not be blocked because stylus has already lifted.
352 NotifyMotionArgs touch2Down =
353 generateMotionArgs(5 /*downTime*/, 5 /*eventTime*/, DOWN, {{31, 32}}, TOUCHSCREEN);
354 touch2Down.deviceId = SECOND_TOUCH_DEVICE_ID;
355 assertNotBlocked(touch2Down);
356
357 // First device is lifted up. It's already been canceled, so the UP event should be dropped.
358 NotifyMotionArgs touch1Up =
359 generateMotionArgs(1 /*downTime*/, 6 /*eventTime*/, UP, {{2, 3}}, TOUCHSCREEN);
360 assertDropped(touch1Up);
361
362 // Touch from second device touch should continue to work
363 NotifyMotionArgs touch2Move =
364 generateMotionArgs(5 /*downTime*/, 7 /*eventTime*/, MOVE, {{32, 33}}, TOUCHSCREEN);
365 touch2Move.deviceId = SECOND_TOUCH_DEVICE_ID;
366 assertNotBlocked(touch2Move);
367
368 // Second touch lifts up
369 NotifyMotionArgs touch2Up =
370 generateMotionArgs(5 /*downTime*/, 8 /*eventTime*/, UP, {{32, 33}}, TOUCHSCREEN);
371 touch2Up.deviceId = SECOND_TOUCH_DEVICE_ID;
372 assertNotBlocked(touch2Up);
373
374 // Now that all touch has been lifted, new touch from either first or second device should work
375 NotifyMotionArgs touch3Down =
376 generateMotionArgs(9 /*downTime*/, 9 /*eventTime*/, DOWN, {{1, 2}}, TOUCHSCREEN);
377 assertNotBlocked(touch3Down);
378
379 NotifyMotionArgs touch4Down =
380 generateMotionArgs(10 /*downTime*/, 10 /*eventTime*/, DOWN, {{100, 200}}, TOUCHSCREEN);
381 touch4Down.deviceId = SECOND_TOUCH_DEVICE_ID;
382 assertNotBlocked(touch4Down);
383}
384
385/**
386 * When we don't know that a specific device does both stylus and touch, and we only see touch
387 * pointers from it, we should treat it as a touch device. That means, the device events should be
388 * canceled when stylus from another device goes down. When we detect simultaneous touch and stylus
389 * from this device though, we should just pass this device through without canceling anything.
390 *
391 * In this test:
392 * 1. Start by touching down with device 1
393 * 2. Device 2 has stylus going down
394 * 3. Device 1 should be canceled.
395 * 4. When we add stylus pointers to the device 1, they should continue to be canceled.
396 * 5. Device 1 lifts up.
397 * 6. Subsequent events from device 1 should not be canceled even if stylus is down.
398 * 7. If a reset happens, and such device is no longer there, then we should
399 * Therefore, the device 1 is "ignored" and does not participate into "prefer stylus over touch"
400 * behaviour.
401 */
402TEST_F(PreferStylusOverTouchTest, MixedStylusAndTouchDeviceIsCanceledAtFirst) {
403 // Touch from device 1 goes down
404 NotifyMotionArgs touchDown =
405 generateMotionArgs(1 /*downTime*/, 1 /*eventTime*/, DOWN, {{1, 2}}, TOUCHSCREEN);
406 touchDown.source = STYLUS;
407 assertNotBlocked(touchDown);
408
409 // Stylus from device 2 goes down. Touch should be canceled.
410 NotifyMotionArgs args =
411 generateMotionArgs(2 /*downTime*/, 2 /*eventTime*/, DOWN, {{10, 20}}, STYLUS);
412 NotifyMotionArgs cancelTouchArgs = touchDown;
413 cancelTouchArgs.action = CANCEL;
414 cancelTouchArgs.flags |= AMOTION_EVENT_FLAG_CANCELED;
415 assertResponse(args, {cancelTouchArgs, args});
416
417 // Introduce a stylus pointer into the device 1 stream. It should be ignored.
418 args = generateMotionArgs(1 /*downTime*/, 3 /*eventTime*/, POINTER_1_DOWN, {{1, 2}, {3, 4}},
419 TOUCHSCREEN);
420 args.pointerProperties[1].toolType = AMOTION_EVENT_TOOL_TYPE_STYLUS;
421 args.source = STYLUS;
422 assertDropped(args);
423
424 // Lift up touch from the mixed touch/stylus device
425 args = generateMotionArgs(1 /*downTime*/, 4 /*eventTime*/, CANCEL, {{1, 2}, {3, 4}},
426 TOUCHSCREEN);
427 args.pointerProperties[1].toolType = AMOTION_EVENT_TOOL_TYPE_STYLUS;
428 args.source = STYLUS;
429 assertDropped(args);
430
431 // Stylus from device 2 is still down. Since the device 1 is now identified as a mixed
432 // touch/stylus device, its events should go through, even if they are touch.
433 args = generateMotionArgs(5 /*downTime*/, 5 /*eventTime*/, DOWN, {{21, 22}}, TOUCHSCREEN);
434 touchDown.source = STYLUS;
435 assertResponse(args, {args});
436
437 // Reconfigure such that only the stylus device remains
438 InputDeviceInfo stylusDevice;
439 stylusDevice.initialize(STYLUS_DEVICE_ID, 1 /*generation*/, 1 /*controllerNumber*/,
440 {} /*identifier*/, "stylus device", false /*external*/,
441 false /*hasMic*/);
442 notifyInputDevicesChanged({stylusDevice});
443 // The touchscreen device was removed, so we no longer remember anything about it. We should
444 // again start blocking touch events from it.
445 args = generateMotionArgs(6 /*downTime*/, 6 /*eventTime*/, DOWN, {{1, 2}}, TOUCHSCREEN);
446 args.source = STYLUS;
447 assertDropped(args);
448}
449
450/**
451 * If two styli are active at the same time, touch should be blocked until both of them are lifted.
452 * If one of them lifts, touch should continue to be blocked.
453 */
454TEST_F(PreferStylusOverTouchTest, TouchIsBlockedWhenTwoStyliAreUsed) {
455 NotifyMotionArgs args;
456
457 // First stylus is down
458 assertNotBlocked(generateMotionArgs(0 /*downTime*/, 0 /*eventTime*/, DOWN, {{10, 30}}, STYLUS));
459
460 // Second stylus is down
461 args = generateMotionArgs(1 /*downTime*/, 1 /*eventTime*/, DOWN, {{20, 40}}, STYLUS);
462 args.deviceId = SECOND_STYLUS_DEVICE_ID;
463 assertNotBlocked(args);
464
465 // Touch goes down. It should be ignored.
466 args = generateMotionArgs(2 /*downTime*/, 2 /*eventTime*/, DOWN, {{1, 2}}, TOUCHSCREEN);
467 assertDropped(args);
468
469 // Lift the first stylus
470 args = generateMotionArgs(0 /*downTime*/, 3 /*eventTime*/, UP, {{10, 30}}, STYLUS);
471 assertNotBlocked(args);
472
473 // Touch should continue to be blocked
474 args = generateMotionArgs(2 /*downTime*/, 4 /*eventTime*/, UP, {{1, 2}}, TOUCHSCREEN);
475 assertDropped(args);
476
477 // New touch should be blocked because second stylus is still down
478 args = generateMotionArgs(5 /*downTime*/, 5 /*eventTime*/, DOWN, {{5, 6}}, TOUCHSCREEN);
479 assertDropped(args);
480
481 // Second stylus goes up
482 args = generateMotionArgs(1 /*downTime*/, 6 /*eventTime*/, UP, {{20, 40}}, STYLUS);
483 args.deviceId = SECOND_STYLUS_DEVICE_ID;
484 assertNotBlocked(args);
485
486 // Current touch gesture should continue to be blocked
487 // Touch should continue to be blocked
488 args = generateMotionArgs(5 /*downTime*/, 7 /*eventTime*/, UP, {{5, 6}}, TOUCHSCREEN);
489 assertDropped(args);
490
491 // Now that all styli were lifted, new touch should go through
492 args = generateMotionArgs(8 /*downTime*/, 8 /*eventTime*/, DOWN, {{7, 8}}, TOUCHSCREEN);
493 assertNotBlocked(args);
494}
495
Siarhei Vishniakoua3c8e512022-02-10 19:46:34 -0800496} // namespace android