1 /* 2 * Copyright 2017 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 package androidx.recyclerview.selection.testing; 18 19 import android.graphics.Point; 20 import android.view.InputDevice; 21 import android.view.KeyEvent; 22 import android.view.MotionEvent; 23 import android.view.MotionEvent.PointerCoords; 24 import android.view.MotionEvent.PointerProperties; 25 26 import androidx.annotation.IntDef; 27 28 import java.lang.annotation.Retention; 29 import java.lang.annotation.RetentionPolicy; 30 import java.util.HashSet; 31 import java.util.Set; 32 33 /** 34 * Handy-dandy wrapper class to facilitate the creation of MotionEvents. 35 */ 36 public final class TestEvents { 37 38 /** 39 * Common mouse event types...for your convenience. 40 */ 41 public static final class Mouse { 42 public static final MotionEvent CLICK = TestEvents.builder() 43 .mouse() 44 .primary() 45 .down() 46 .build(); 47 48 public static final MotionEvent CTRL_CLICK = TestEvents.builder() 49 .mouse() 50 .primary() 51 .down() 52 .ctrl() 53 .build(); 54 55 public static final MotionEvent ALT_CLICK = TestEvents.builder() 56 .mouse() 57 .primary() 58 .down() 59 .alt() 60 .build(); 61 62 public static final MotionEvent SHIFT_CLICK = TestEvents.builder() 63 .mouse() 64 .primary() 65 .down() 66 .shift() 67 .build(); 68 69 public static final MotionEvent SECONDARY_CLICK = TestEvents.builder() 70 .mouse() 71 .secondary() 72 .down() 73 .build(); 74 75 public static final MotionEvent TERTIARY_CLICK = TestEvents.builder() 76 .mouse() 77 .tertiary() 78 .down() 79 .build(); 80 81 public static final MotionEvent MOVE = TestEvents.builder() 82 .mouse() 83 .move() 84 .build(); 85 86 public static final MotionEvent PRIMARY_DRAG = TestEvents.builder() 87 .mouse() 88 .primary() 89 .move() 90 .build(); 91 92 public static final MotionEvent SECONDARY_DRAG = TestEvents.builder() 93 .mouse() 94 .secondary() 95 .move() 96 .build(); 97 98 public static final MotionEvent TERTIARY_DRAG = TestEvents.builder() 99 .mouse() 100 .tertiary() 101 .move() 102 .build(); 103 104 public static final MotionEvent UP = TestEvents.builder() 105 .mouse() 106 .up() 107 .build(); 108 109 public static final MotionEvent DOWN = TestEvents.builder() 110 .mouse() 111 .move() 112 .build(); 113 114 // NOTE: POINTER_DOWN and POINTER_UP are for secondary pointers, not main mouse pointer. 115 public static final MotionEvent POINTER_DOWN = 116 TestEvents.builder() 117 .mouse() 118 .down() 119 .build(); 120 121 public static final MotionEvent POINTER_UP = 122 TestEvents.builder() 123 .mouse() 124 .action(MotionEvent.ACTION_POINTER_UP) 125 .build(); 126 } 127 128 /** 129 * Common touch event types...for your convenience. 130 */ 131 public static final class Touch { 132 133 public static final MotionEvent DOWN = TestEvents.builder() 134 .down() 135 .touch() 136 .location(1, 1) 137 .build(); 138 139 public static final MotionEvent MOVE = TestEvents.builder() 140 .move() 141 .touch() 142 .location(1, 1) 143 .build(); 144 145 public static final MotionEvent UP = TestEvents.builder() 146 .up() 147 .touch() 148 .location(1, 1) 149 .build(); 150 151 public static final MotionEvent TAP = TestEvents.builder() 152 .touch() 153 .build(); 154 } 155 156 /** 157 * Common touch event types...for your convenience. 158 */ 159 public static final class Unknown { 160 public static final MotionEvent CANCEL = TestEvents.builder() 161 .action(MotionEvent.ACTION_CANCEL) 162 .build(); 163 } 164 165 static final int ACTION_UNSET = -1; 166 167 // Add other actions from MotionEvent.ACTION_ as needed. 168 @IntDef(flag = true, value = { 169 MotionEvent.ACTION_DOWN, 170 MotionEvent.ACTION_MOVE, 171 MotionEvent.ACTION_UP, 172 MotionEvent.ACTION_CANCEL, 173 }) 174 @Retention(RetentionPolicy.SOURCE) 175 public @interface Action { 176 } 177 178 // Add other types from MotionEvent.TOOL_TYPE_ as needed. 179 @IntDef(flag = true, value = { 180 MotionEvent.TOOL_TYPE_FINGER, 181 MotionEvent.TOOL_TYPE_MOUSE, 182 MotionEvent.TOOL_TYPE_STYLUS, 183 MotionEvent.TOOL_TYPE_UNKNOWN, 184 }) 185 @Retention(RetentionPolicy.SOURCE) 186 public @interface ToolType { 187 } 188 189 // Add other types from InputDevice.SOURCE_* as needed. 190 @IntDef(flag = true, value = { 191 InputDevice.SOURCE_MOUSE, 192 InputDevice.SOURCE_UNKNOWN, 193 }) 194 @Retention(RetentionPolicy.SOURCE) 195 public @interface Source { 196 } 197 198 @IntDef(flag = true, value = { 199 MotionEvent.BUTTON_PRIMARY, 200 MotionEvent.BUTTON_SECONDARY, 201 }) 202 @Retention(RetentionPolicy.SOURCE) 203 public @interface Button { 204 } 205 206 @IntDef(flag = true, value = { 207 KeyEvent.META_SHIFT_ON, 208 KeyEvent.META_CTRL_ON, 209 }) 210 @Retention(RetentionPolicy.SOURCE) 211 public @interface Key { 212 } 213 214 private static final class State { 215 private @Action int mAction = ACTION_UNSET; 216 private @ToolType int mToolType = MotionEvent.TOOL_TYPE_UNKNOWN; 217 private @Source int mSource = InputDevice.SOURCE_UNKNOWN; 218 private int mPointerCount = 1; 219 private Set<Integer> mButtons = new HashSet<>(); 220 private Set<Integer> mKeys = new HashSet<>(); 221 private Point mLocation = new Point(0, 0); 222 private Point mRawLocation = new Point(0, 0); 223 } 224 builder()225 public static Builder builder() { 226 return new Builder(); 227 } 228 229 /** 230 * Test event builder with convenience methods for common event attrs. 231 */ 232 public static final class Builder { 233 234 private State mState = new State(); 235 236 /** 237 * @param action Any action specified in {@link MotionEvent}. 238 */ action(int action)239 public Builder action(int action) { 240 mState.mAction = action; 241 return this; 242 } 243 type(@oolType int type)244 public Builder type(@ToolType int type) { 245 mState.mToolType = type; 246 return this; 247 } 248 249 /** 250 * @param source Any source type specified in {@link InputDevice}. When adding a new 251 * source, ensure it is also added to the IntDef for @Source. 252 */ source(@ource int source)253 public Builder source(@Source int source) { 254 mState.mSource = source; 255 return this; 256 } 257 location(int x, int y)258 public Builder location(int x, int y) { 259 mState.mLocation = new Point(x, y); 260 return this; 261 } 262 rawLocation(int x, int y)263 public Builder rawLocation(int x, int y) { 264 mState.mRawLocation = new Point(x, y); 265 return this; 266 } 267 pointerCount(int count)268 public Builder pointerCount(int count) { 269 mState.mPointerCount = count; 270 return this; 271 } 272 273 /** 274 * Adds one or more button press attributes. 275 */ pressButton(@utton int... buttons)276 public Builder pressButton(@Button int... buttons) { 277 for (int button : buttons) { 278 mState.mButtons.add(button); 279 } 280 return this; 281 } 282 283 /** 284 * Removes one or more button press attributes. 285 */ releaseButton(@utton int... buttons)286 public Builder releaseButton(@Button int... buttons) { 287 for (int button : buttons) { 288 mState.mButtons.remove(button); 289 } 290 return this; 291 } 292 293 /** 294 * Adds one or more key press attributes. 295 */ pressKey(@ey int... keys)296 public Builder pressKey(@Key int... keys) { 297 for (int key : keys) { 298 mState.mKeys.add(key); 299 } 300 return this; 301 } 302 303 /** 304 * Removes one or more key press attributes. 305 */ releaseKey(@utton int... keys)306 public Builder releaseKey(@Button int... keys) { 307 for (int key : keys) { 308 mState.mKeys.remove(key); 309 } 310 return this; 311 } 312 up()313 public Builder up() { 314 action(MotionEvent.ACTION_UP); 315 return this; 316 } 317 318 /** 319 * Sets action to MotionEvent#ACTION_DOWN which can be used 320 * with most tool types including mouse. 321 * 322 * <p>NOTE: ACTION_POINTER_DOWN is used for secondary pointers. 323 */ down()324 public Builder down() { 325 action(MotionEvent.ACTION_DOWN); 326 return this; 327 } 328 move()329 public Builder move() { 330 action(MotionEvent.ACTION_MOVE); 331 return this; 332 } 333 touch()334 public Builder touch() { 335 type(MotionEvent.TOOL_TYPE_FINGER); 336 return this; 337 } 338 mouse()339 public Builder mouse() { 340 type(MotionEvent.TOOL_TYPE_MOUSE); 341 return this; 342 } 343 unknown()344 public Builder unknown() { 345 type(MotionEvent.TOOL_TYPE_UNKNOWN); 346 return this; 347 } 348 shift()349 public Builder shift() { 350 pressKey(KeyEvent.META_SHIFT_ON); 351 return this; 352 } 353 unshift()354 public Builder unshift() { 355 releaseKey(KeyEvent.META_SHIFT_ON); 356 return this; 357 } 358 ctrl()359 public Builder ctrl() { 360 pressKey(KeyEvent.META_CTRL_ON); 361 return this; 362 } 363 alt()364 public Builder alt() { 365 pressKey(KeyEvent.META_ALT_ON); 366 return this; 367 } 368 primary()369 public Builder primary() { 370 pressButton(MotionEvent.BUTTON_PRIMARY); 371 releaseButton(MotionEvent.BUTTON_SECONDARY); 372 releaseButton(MotionEvent.BUTTON_TERTIARY); 373 return this; 374 } 375 secondary()376 public Builder secondary() { 377 pressButton(MotionEvent.BUTTON_SECONDARY); 378 releaseButton(MotionEvent.BUTTON_PRIMARY); 379 releaseButton(MotionEvent.BUTTON_TERTIARY); 380 return this; 381 } 382 tertiary()383 public Builder tertiary() { 384 pressButton(MotionEvent.BUTTON_TERTIARY); 385 releaseButton(MotionEvent.BUTTON_PRIMARY); 386 releaseButton(MotionEvent.BUTTON_SECONDARY); 387 return this; 388 } 389 build()390 public MotionEvent build() { 391 392 PointerProperties[] pointers = new PointerProperties[1]; 393 pointers[0] = new PointerProperties(); 394 pointers[0].id = 0; 395 pointers[0].toolType = mState.mToolType; 396 397 PointerCoords[] coords = new PointerCoords[1]; 398 coords[0] = new PointerCoords(); 399 coords[0].x = mState.mLocation.x; 400 coords[0].y = mState.mLocation.y; 401 402 int buttons = 0; 403 for (Integer button : mState.mButtons) { 404 buttons |= button; 405 } 406 407 int keys = 0; 408 for (Integer key : mState.mKeys) { 409 keys |= key; 410 } 411 412 return MotionEvent.obtain( 413 0, // down time 414 1, // event time 415 mState.mAction, 416 1, // pointerCount, 417 pointers, 418 coords, 419 keys, 420 buttons, 421 1.0f, // x precision 422 1.0f, // y precision 423 0, // device id 424 0, // edge flags 425 mState.mSource, // int source, 426 0 // int flags 427 ); 428 } 429 } 430 } 431