Siarhei Vishniakou | a3c8e51 | 2022-02-10 19:46:34 -0800 | [diff] [blame] | 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; |
Siarhei Vishniakou | a6a660f | 2022-03-04 15:12:16 -0800 | [diff] [blame^] | 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; |
Siarhei Vishniakou | a3c8e51 | 2022-02-10 19:46:34 -0800 | [diff] [blame] | 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; |
Siarhei Vishniakou | a6a660f | 2022-03-04 15:12:16 -0800 | [diff] [blame^] | 31 | static constexpr int32_t POINTER_1_DOWN = |
| 32 | AMOTION_EVENT_ACTION_POINTER_DOWN | (1 << AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT); |
Siarhei Vishniakou | a3c8e51 | 2022-02-10 19:46:34 -0800 | [diff] [blame] | 33 | constexpr int32_t TOUCHSCREEN = AINPUT_SOURCE_TOUCHSCREEN; |
| 34 | constexpr int32_t STYLUS = AINPUT_SOURCE_STYLUS; |
| 35 | |
| 36 | struct PointerData { |
| 37 | float x; |
| 38 | float y; |
| 39 | }; |
| 40 | |
| 41 | static 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 | |
| 83 | class PreferStylusOverTouchTest : public testing::Test { |
| 84 | protected: |
Siarhei Vishniakou | a6a660f | 2022-03-04 15:12:16 -0800 | [diff] [blame^] | 85 | 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 Vishniakou | a3c8e51 | 2022-02-10 19:46:34 -0800 | [diff] [blame] | 102 | } |
| 103 | |
Siarhei Vishniakou | a6a660f | 2022-03-04 15:12:16 -0800 | [diff] [blame^] | 104 | void notifyInputDevicesChanged(const std::vector<InputDeviceInfo>& devices) { |
| 105 | mBlocker.notifyInputDevicesChanged(devices); |
Siarhei Vishniakou | a3c8e51 | 2022-02-10 19:46:34 -0800 | [diff] [blame] | 106 | } |
| 107 | |
Siarhei Vishniakou | a6a660f | 2022-03-04 15:12:16 -0800 | [diff] [blame^] | 108 | void dump() const { ALOGI("Blocker: \n%s\n", mBlocker.dump().c_str()); } |
Siarhei Vishniakou | a3c8e51 | 2022-02-10 19:46:34 -0800 | [diff] [blame] | 109 | |
| 110 | private: |
| 111 | PreferStylusOverTouchBlocker mBlocker; |
| 112 | }; |
| 113 | |
| 114 | TEST_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 | |
| 127 | TEST_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 | */ |
| 144 | TEST_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 Vishniakou | a6a660f | 2022-03-04 15:12:16 -0800 | [diff] [blame^] | 156 | cancelArgs.flags |= AMOTION_EVENT_FLAG_CANCELED; |
| 157 | assertResponse(args, {cancelArgs, args}); |
Siarhei Vishniakou | a3c8e51 | 2022-02-10 19:46:34 -0800 | [diff] [blame] | 158 | |
| 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 Vishniakou | a6a660f | 2022-03-04 15:12:16 -0800 | [diff] [blame^] | 169 | * Stylus goes down after touch gesture. |
| 170 | */ |
| 171 | TEST_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 Vishniakou | a3c8e51 | 2022-02-10 19:46:34 -0800 | [diff] [blame] | 189 | * New touch events should be simply blocked (dropped) when stylus is down. No CANCEL event should |
| 190 | * be generated. |
| 191 | */ |
| 192 | TEST_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 | */ |
| 219 | TEST_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 | */ |
| 249 | TEST_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 Vishniakou | a6a660f | 2022-03-04 15:12:16 -0800 | [diff] [blame^] | 276 | /** |
| 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 | */ |
| 280 | TEST_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 | */ |
| 306 | TEST_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 | */ |
| 333 | TEST_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 | */ |
| 408 | TEST_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 | */ |
| 460 | TEST_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 Vishniakou | a3c8e51 | 2022-02-10 19:46:34 -0800 | [diff] [blame] | 502 | } // namespace android |