1 /* 2 * Copyright (C) 2016 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 com.android.documentsui.testing; 18 19 import android.annotation.IntDef; 20 import android.graphics.Point; 21 import android.support.v7.widget.RecyclerView; 22 import android.text.TextUtils; 23 import android.view.KeyEvent; 24 import android.view.MotionEvent; 25 26 import com.android.documentsui.base.Events.InputEvent; 27 import com.android.documentsui.dirlist.DocumentDetails; 28 29 import java.lang.annotation.Retention; 30 import java.lang.annotation.RetentionPolicy; 31 import java.util.HashSet; 32 import java.util.Set; 33 34 /** 35 * Events and DocDetails are closely related. For the pursposes of this test 36 * we coalesce the two in a single, handy-dandy test class. 37 */ 38 public class TestEvent implements InputEvent { 39 private static final int ACTION_UNSET = -1; 40 41 // Add other actions from MotionEvent.ACTION_ as needed. 42 @IntDef(flag = true, value = { 43 MotionEvent.ACTION_DOWN, 44 MotionEvent.ACTION_MOVE, 45 MotionEvent.ACTION_UP 46 }) 47 @Retention(RetentionPolicy.SOURCE) 48 public @interface Action {} 49 50 // Add other types from MotionEvent.TOOL_TYPE_ as needed. 51 @IntDef(flag = true, value = { 52 MotionEvent.TOOL_TYPE_FINGER, 53 MotionEvent.TOOL_TYPE_MOUSE, 54 MotionEvent.TOOL_TYPE_STYLUS, 55 MotionEvent.TOOL_TYPE_UNKNOWN 56 }) 57 @Retention(RetentionPolicy.SOURCE) 58 public @interface ToolType {} 59 60 @IntDef(flag = true, value = { 61 MotionEvent.BUTTON_PRIMARY, 62 MotionEvent.BUTTON_SECONDARY 63 }) 64 @Retention(RetentionPolicy.SOURCE) 65 public @interface Button {} 66 67 @IntDef(flag = true, value = { 68 KeyEvent.META_SHIFT_ON, 69 KeyEvent.META_CTRL_ON 70 }) 71 @Retention(RetentionPolicy.SOURCE) 72 public @interface Key {} 73 74 private @Action int mAction; 75 private @ToolType int mToolType; 76 private int mPointerCount; 77 private Set<Integer> mButtons; 78 private Set<Integer> mKeys; 79 private Point mLocation; 80 private Point mRawLocation; 81 private Details mDetails; 82 TestEvent()83 private TestEvent() { 84 mAction = ACTION_UNSET; // somebody has to set this, else we'll barf later. 85 mToolType = MotionEvent.TOOL_TYPE_UNKNOWN; 86 mButtons = new HashSet<>(); 87 mKeys = new HashSet<>(); 88 mLocation = new Point(0, 0); 89 mRawLocation = new Point(0, 0); 90 mDetails = new Details(); 91 mPointerCount = 0; 92 } 93 TestEvent(TestEvent source)94 private TestEvent(TestEvent source) { 95 assert(source.mAction != ACTION_UNSET); 96 mAction = source.mAction; 97 mToolType = source.mToolType; 98 mButtons = source.mButtons; 99 mKeys = source.mKeys; 100 mLocation = source.mLocation; 101 mRawLocation = source.mRawLocation; 102 mDetails = new Details(source.mDetails); 103 mPointerCount = source.mPointerCount; 104 } 105 106 @Override getOrigin()107 public Point getOrigin() { 108 return mLocation; 109 } 110 111 @Override getX()112 public float getX() { 113 return mLocation.x; 114 } 115 116 @Override getY()117 public float getY() { 118 return mLocation.y; 119 } 120 121 @Override getRawX()122 public float getRawX() { 123 return mRawLocation.x; 124 } 125 126 @Override getRawY()127 public float getRawY() { 128 return mRawLocation.y; 129 } 130 131 @Override getPointerCount()132 public int getPointerCount() { 133 return mPointerCount; 134 } 135 136 @Override isMouseEvent()137 public boolean isMouseEvent() { 138 return mToolType == MotionEvent.TOOL_TYPE_MOUSE; 139 } 140 141 @Override isPrimaryButtonPressed()142 public boolean isPrimaryButtonPressed() { 143 return mButtons.contains(MotionEvent.BUTTON_PRIMARY); 144 } 145 146 @Override isSecondaryButtonPressed()147 public boolean isSecondaryButtonPressed() { 148 return mButtons.contains(MotionEvent.BUTTON_SECONDARY); 149 } 150 151 @Override isTertiaryButtonPressed()152 public boolean isTertiaryButtonPressed() { 153 return mButtons.contains(MotionEvent.BUTTON_TERTIARY); 154 } 155 156 @Override isShiftKeyDown()157 public boolean isShiftKeyDown() { 158 return mKeys.contains(KeyEvent.META_SHIFT_ON); 159 } 160 161 @Override isCtrlKeyDown()162 public boolean isCtrlKeyDown() { 163 return mKeys.contains(KeyEvent.META_CTRL_ON); 164 } 165 166 @Override isAltKeyDown()167 public boolean isAltKeyDown() { 168 return mKeys.contains(KeyEvent.META_ALT_ON); 169 } 170 171 @Override isActionDown()172 public boolean isActionDown() { 173 return mAction == MotionEvent.ACTION_DOWN; 174 } 175 176 @Override isActionUp()177 public boolean isActionUp() { 178 return mAction == MotionEvent.ACTION_UP; 179 } 180 181 @Override isMultiPointerActionDown()182 public boolean isMultiPointerActionDown() { 183 return mAction == MotionEvent.ACTION_POINTER_DOWN; 184 } 185 186 @Override isMultiPointerActionUp()187 public boolean isMultiPointerActionUp() { 188 return mAction == MotionEvent.ACTION_POINTER_UP; 189 } 190 191 @Override isActionMove()192 public boolean isActionMove() { 193 return mAction == MotionEvent.ACTION_MOVE; 194 } 195 196 @Override isActionCancel()197 public boolean isActionCancel() { 198 return mAction == MotionEvent.ACTION_CANCEL; 199 } 200 201 @Override isOverItem()202 public boolean isOverItem() { 203 return mDetails.isOverItem(); 204 } 205 206 @Override isOverDocIcon()207 public boolean isOverDocIcon() { 208 return mDetails.isOverDocIcon(this); 209 } 210 211 @Override isOverDragHotspot()212 public boolean isOverDragHotspot() { 213 return isOverItem() && mDetails.isInDragHotspot(this); 214 } 215 216 @Override isOverModelItem()217 public boolean isOverModelItem() { 218 if (isOverItem()) { 219 DocumentDetails doc = getDocumentDetails(); 220 return doc != null && doc.hasModelId(); 221 } 222 return false; 223 } 224 225 @Override isTouchpadScroll()226 public boolean isTouchpadScroll() { 227 return isMouseEvent() && mButtons.isEmpty() && isActionMove(); 228 } 229 230 @Override getItemPosition()231 public int getItemPosition() { 232 return mDetails.mPosition; 233 } 234 235 @Override getDocumentDetails()236 public DocumentDetails getDocumentDetails() { 237 return mDetails; 238 } 239 240 @Override close()241 public void close() {} 242 243 @Override hashCode()244 public int hashCode() { 245 return mDetails.hashCode(); 246 } 247 248 @Override equals(Object o)249 public boolean equals(Object o) { 250 if (this == o) { 251 return true; 252 } 253 254 if (!(o instanceof TestEvent)) { 255 return false; 256 } 257 258 TestEvent other = (TestEvent) o; 259 return mAction == other.mAction 260 && mToolType == other.mToolType 261 && mButtons.equals(other.mButtons) 262 && mKeys.equals(other.mKeys) 263 && mLocation.equals(other.mLocation) 264 && mRawLocation.equals(other.mRawLocation) 265 && mDetails.equals(other.mDetails); 266 } 267 268 private static final class Details implements DocumentDetails { 269 270 private int mPosition; 271 private String mModelId; 272 private boolean mInSelectionHotspot; 273 private boolean mInDragHotspot; 274 private boolean mOverDocIcon; 275 Details()276 public Details() { 277 mPosition = Integer.MIN_VALUE; 278 } 279 Details(Details source)280 public Details(Details source) { 281 mPosition = source.mPosition; 282 mModelId = source.mModelId; 283 mInSelectionHotspot = source.mInSelectionHotspot; 284 mInDragHotspot = source.mInDragHotspot; 285 mOverDocIcon = source.mOverDocIcon; 286 } 287 288 isOverItem()289 private boolean isOverItem() { 290 return mPosition != Integer.MIN_VALUE && mPosition != RecyclerView.NO_POSITION; 291 } 292 293 @Override hasModelId()294 public boolean hasModelId() { 295 return !TextUtils.isEmpty(mModelId); 296 } 297 298 @Override getModelId()299 public String getModelId() { 300 return mModelId; 301 } 302 303 @Override getAdapterPosition()304 public int getAdapterPosition() { 305 return mPosition; 306 } 307 308 @Override isInSelectionHotspot(InputEvent event)309 public boolean isInSelectionHotspot(InputEvent event) { 310 return mInSelectionHotspot; 311 } 312 313 @Override isInDragHotspot(InputEvent event)314 public boolean isInDragHotspot(InputEvent event) { 315 return mInDragHotspot; 316 } 317 318 @Override isOverDocIcon(InputEvent event)319 public boolean isOverDocIcon(InputEvent event) { 320 return mOverDocIcon; 321 } 322 323 @Override hashCode()324 public int hashCode() { 325 return mModelId != null ? mModelId.hashCode() : ACTION_UNSET; 326 } 327 328 @Override equals(Object o)329 public boolean equals(Object o) { 330 if (this == o) { 331 return true; 332 } 333 334 if (!(o instanceof Details)) { 335 return false; 336 } 337 338 Details other = (Details) o; 339 return mPosition == other.mPosition 340 && mModelId == other.mModelId; 341 } 342 } 343 builder()344 public static final Builder builder() { 345 return new Builder(); 346 } 347 348 /** 349 * Test event builder with convenience methods for common event attrs. 350 */ 351 public static final class Builder { 352 353 private TestEvent mState = new TestEvent(); 354 Builder()355 public Builder() { 356 } 357 Builder(TestEvent state)358 public Builder(TestEvent state) { 359 mState = new TestEvent(state); 360 } 361 362 /** 363 * @param action Any action specified in {@link MotionEvent}. 364 * @return 365 */ action(int action)366 public Builder action(int action) { 367 mState.mAction = action; 368 return this; 369 } 370 type(@oolType int type)371 public Builder type(@ToolType int type) { 372 mState.mToolType = type; 373 return this; 374 } 375 location(int x, int y)376 public Builder location(int x, int y) { 377 mState.mLocation = new Point(x, y); 378 return this; 379 } 380 rawLocation(int x, int y)381 public Builder rawLocation(int x, int y) { 382 mState.mRawLocation = new Point(x, y); 383 return this; 384 } 385 pointerCount(int count)386 public Builder pointerCount(int count) { 387 mState.mPointerCount = count; 388 return this; 389 } 390 391 /** 392 * Adds one or more button press attributes. 393 */ pressButton(@utton int... buttons)394 public Builder pressButton(@Button int... buttons) { 395 for (int button : buttons) { 396 mState.mButtons.add(button); 397 } 398 return this; 399 } 400 401 /** 402 * Removes one or more button press attributes. 403 */ releaseButton(@utton int... buttons)404 public Builder releaseButton(@Button int... buttons) { 405 for (int button : buttons) { 406 mState.mButtons.remove(button); 407 } 408 return this; 409 } 410 411 /** 412 * Adds one or more key press attributes. 413 */ pressKey(@ey int... keys)414 public Builder pressKey(@Key int... keys) { 415 for (int key : keys) { 416 mState.mKeys.add(key); 417 } 418 return this; 419 } 420 421 /** 422 * Removes one or more key press attributes. 423 */ releaseKey(@utton int... keys)424 public Builder releaseKey(@Button int... keys) { 425 for (int key : keys) { 426 mState.mKeys.remove(key); 427 } 428 return this; 429 } 430 at(int position)431 public Builder at(int position) { 432 mState.mDetails.mPosition = position; // this is both "adapter position" and "item position". 433 mState.mDetails.mModelId = String.valueOf(position); 434 return this; 435 } 436 inSelectionHotspot()437 public Builder inSelectionHotspot() { 438 mState.mDetails.mInSelectionHotspot = true; 439 return this; 440 } 441 inDragHotspot()442 public Builder inDragHotspot() { 443 mState.mDetails.mInDragHotspot = true; 444 return this; 445 } 446 notInDragHotspot()447 public Builder notInDragHotspot() { 448 mState.mDetails.mInDragHotspot = false; 449 return this; 450 } 451 overDocIcon()452 public Builder overDocIcon() { 453 mState.mDetails.mOverDocIcon = true; 454 return this; 455 } 456 notOverDocIcon()457 public Builder notOverDocIcon() { 458 mState.mDetails.mOverDocIcon = false; 459 return this; 460 } 461 touch()462 public Builder touch() { 463 type(MotionEvent.TOOL_TYPE_FINGER); 464 return this; 465 } 466 mouse()467 public Builder mouse() { 468 type(MotionEvent.TOOL_TYPE_MOUSE); 469 return this; 470 } 471 shift()472 public Builder shift() { 473 pressKey(KeyEvent.META_SHIFT_ON); 474 return this; 475 } 476 477 /** 478 * Use {@link #remove(@Attribute int...)} 479 */ 480 @Deprecated unshift()481 public Builder unshift() { 482 releaseKey(KeyEvent.META_SHIFT_ON); 483 return this; 484 } 485 ctrl()486 public Builder ctrl() { 487 pressKey(KeyEvent.META_CTRL_ON); 488 return this; 489 } 490 alt()491 public Builder alt() { 492 pressKey(KeyEvent.META_ALT_ON); 493 return this; 494 } 495 primary()496 public Builder primary() { 497 pressButton(MotionEvent.BUTTON_PRIMARY); 498 releaseButton(MotionEvent.BUTTON_SECONDARY); 499 releaseButton(MotionEvent.BUTTON_TERTIARY); 500 return this; 501 } 502 secondary()503 public Builder secondary() { 504 pressButton(MotionEvent.BUTTON_SECONDARY); 505 releaseButton(MotionEvent.BUTTON_PRIMARY); 506 releaseButton(MotionEvent.BUTTON_TERTIARY); 507 return this; 508 } 509 tertiary()510 public Builder tertiary() { 511 pressButton(MotionEvent.BUTTON_TERTIARY); 512 releaseButton(MotionEvent.BUTTON_PRIMARY); 513 releaseButton(MotionEvent.BUTTON_SECONDARY); 514 return this; 515 } 516 reset()517 public Builder reset() { 518 mState = new TestEvent(); 519 return this; 520 } 521 522 @Override clone()523 public Builder clone() { 524 return new Builder(build()); 525 } 526 build()527 public TestEvent build() { 528 // Return a copy, so nobody can mess w/ our internal state. 529 return new TestEvent(mState); 530 } 531 } 532 } 533