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 isOverDragHotspot()207 public boolean isOverDragHotspot() { 208 return isOverItem() && mDetails.isInDragHotspot(this); 209 } 210 211 @Override isOverModelItem()212 public boolean isOverModelItem() { 213 if (isOverItem()) { 214 DocumentDetails doc = getDocumentDetails(); 215 return doc != null && doc.hasModelId(); 216 } 217 return false; 218 } 219 220 @Override isTouchpadScroll()221 public boolean isTouchpadScroll() { 222 return isMouseEvent() && mButtons.isEmpty() && isActionMove(); 223 } 224 225 @Override getItemPosition()226 public int getItemPosition() { 227 return mDetails.mPosition; 228 } 229 230 @Override getDocumentDetails()231 public DocumentDetails getDocumentDetails() { 232 return mDetails; 233 } 234 235 @Override close()236 public void close() {} 237 238 @Override hashCode()239 public int hashCode() { 240 return mDetails.hashCode(); 241 } 242 243 @Override equals(Object o)244 public boolean equals(Object o) { 245 if (this == o) { 246 return true; 247 } 248 249 if (!(o instanceof TestEvent)) { 250 return false; 251 } 252 253 TestEvent other = (TestEvent) o; 254 return mAction == other.mAction 255 && mToolType == other.mToolType 256 && mButtons.equals(other.mButtons) 257 && mKeys.equals(other.mKeys) 258 && mLocation.equals(other.mLocation) 259 && mRawLocation.equals(other.mRawLocation) 260 && mDetails.equals(other.mDetails); 261 } 262 263 private static final class Details implements DocumentDetails { 264 265 private int mPosition; 266 private String mModelId; 267 private boolean mInSelectionHotspot; 268 private boolean mInDragHotspot; 269 Details()270 public Details() { 271 mPosition = Integer.MIN_VALUE; 272 } 273 Details(Details source)274 public Details(Details source) { 275 mPosition = source.mPosition; 276 mModelId = source.mModelId; 277 mInSelectionHotspot = source.mInSelectionHotspot; 278 mInDragHotspot = source.mInDragHotspot; 279 } 280 281 isOverItem()282 private boolean isOverItem() { 283 return mPosition != Integer.MIN_VALUE && mPosition != RecyclerView.NO_POSITION; 284 } 285 286 @Override hasModelId()287 public boolean hasModelId() { 288 return !TextUtils.isEmpty(mModelId); 289 } 290 291 @Override getModelId()292 public String getModelId() { 293 return mModelId; 294 } 295 296 @Override getAdapterPosition()297 public int getAdapterPosition() { 298 return mPosition; 299 } 300 301 @Override isInSelectionHotspot(InputEvent event)302 public boolean isInSelectionHotspot(InputEvent event) { 303 return mInSelectionHotspot; 304 } 305 306 @Override isInDragHotspot(InputEvent event)307 public boolean isInDragHotspot(InputEvent event) { 308 return mInDragHotspot; 309 } 310 311 @Override hashCode()312 public int hashCode() { 313 return mModelId != null ? mModelId.hashCode() : ACTION_UNSET; 314 } 315 316 @Override equals(Object o)317 public boolean equals(Object o) { 318 if (this == o) { 319 return true; 320 } 321 322 if (!(o instanceof Details)) { 323 return false; 324 } 325 326 Details other = (Details) o; 327 return mPosition == other.mPosition 328 && mModelId == other.mModelId; 329 } 330 } 331 builder()332 public static final Builder builder() { 333 return new Builder(); 334 } 335 336 /** 337 * Test event builder with convenience methods for common event attrs. 338 */ 339 public static final class Builder { 340 341 private TestEvent mState = new TestEvent(); 342 Builder()343 public Builder() { 344 } 345 Builder(TestEvent state)346 public Builder(TestEvent state) { 347 mState = new TestEvent(state); 348 } 349 350 /** 351 * @param action Any action specified in {@link MotionEvent}. 352 * @return 353 */ action(int action)354 public Builder action(int action) { 355 mState.mAction = action; 356 return this; 357 } 358 type(@oolType int type)359 public Builder type(@ToolType int type) { 360 mState.mToolType = type; 361 return this; 362 } 363 location(int x, int y)364 public Builder location(int x, int y) { 365 mState.mLocation = new Point(x, y); 366 return this; 367 } 368 rawLocation(int x, int y)369 public Builder rawLocation(int x, int y) { 370 mState.mRawLocation = new Point(x, y); 371 return this; 372 } 373 pointerCount(int count)374 public Builder pointerCount(int count) { 375 mState.mPointerCount = count; 376 return this; 377 } 378 379 /** 380 * Adds one or more button press attributes. 381 */ pressButton(@utton int... buttons)382 public Builder pressButton(@Button int... buttons) { 383 for (int button : buttons) { 384 mState.mButtons.add(button); 385 } 386 return this; 387 } 388 389 /** 390 * Removes one or more button press attributes. 391 */ releaseButton(@utton int... buttons)392 public Builder releaseButton(@Button int... buttons) { 393 for (int button : buttons) { 394 mState.mButtons.remove(button); 395 } 396 return this; 397 } 398 399 /** 400 * Adds one or more key press attributes. 401 */ pressKey(@ey int... keys)402 public Builder pressKey(@Key int... keys) { 403 for (int key : keys) { 404 mState.mKeys.add(key); 405 } 406 return this; 407 } 408 409 /** 410 * Removes one or more key press attributes. 411 */ releaseKey(@utton int... keys)412 public Builder releaseKey(@Button int... keys) { 413 for (int key : keys) { 414 mState.mKeys.remove(key); 415 } 416 return this; 417 } 418 at(int position)419 public Builder at(int position) { 420 mState.mDetails.mPosition = position; // this is both "adapter position" and "item position". 421 mState.mDetails.mModelId = String.valueOf(position); 422 return this; 423 } 424 inSelectionHotspot()425 public Builder inSelectionHotspot() { 426 mState.mDetails.mInSelectionHotspot = true; 427 return this; 428 } 429 inDragHotspot()430 public Builder inDragHotspot() { 431 mState.mDetails.mInDragHotspot = true; 432 return this; 433 } 434 notInDragHotspot()435 public Builder notInDragHotspot() { 436 mState.mDetails.mInDragHotspot = false; 437 return this; 438 } 439 touch()440 public Builder touch() { 441 type(MotionEvent.TOOL_TYPE_FINGER); 442 return this; 443 } 444 mouse()445 public Builder mouse() { 446 type(MotionEvent.TOOL_TYPE_MOUSE); 447 return this; 448 } 449 shift()450 public Builder shift() { 451 pressKey(KeyEvent.META_SHIFT_ON); 452 return this; 453 } 454 455 /** 456 * Use {@link #remove(@Attribute int...)} 457 */ 458 @Deprecated unshift()459 public Builder unshift() { 460 releaseKey(KeyEvent.META_SHIFT_ON); 461 return this; 462 } 463 ctrl()464 public Builder ctrl() { 465 pressKey(KeyEvent.META_CTRL_ON); 466 return this; 467 } 468 alt()469 public Builder alt() { 470 pressKey(KeyEvent.META_ALT_ON); 471 return this; 472 } 473 primary()474 public Builder primary() { 475 pressButton(MotionEvent.BUTTON_PRIMARY); 476 releaseButton(MotionEvent.BUTTON_SECONDARY); 477 releaseButton(MotionEvent.BUTTON_TERTIARY); 478 return this; 479 } 480 secondary()481 public Builder secondary() { 482 pressButton(MotionEvent.BUTTON_SECONDARY); 483 releaseButton(MotionEvent.BUTTON_PRIMARY); 484 releaseButton(MotionEvent.BUTTON_TERTIARY); 485 return this; 486 } 487 tertiary()488 public Builder tertiary() { 489 pressButton(MotionEvent.BUTTON_TERTIARY); 490 releaseButton(MotionEvent.BUTTON_PRIMARY); 491 releaseButton(MotionEvent.BUTTON_SECONDARY); 492 return this; 493 } 494 reset()495 public Builder reset() { 496 mState = new TestEvent(); 497 return this; 498 } 499 500 @Override clone()501 public Builder clone() { 502 return new Builder(build()); 503 } 504 build()505 public TestEvent build() { 506 // Return a copy, so nobody can mess w/ our internal state. 507 return new TestEvent(mState); 508 } 509 } 510 } 511