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