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