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.compatibility.common.util; 18 19 import android.app.Instrumentation; 20 import android.app.UiAutomation; 21 import android.graphics.Point; 22 import android.os.SystemClock; 23 import android.util.SparseArray; 24 import android.view.InputDevice; 25 import android.view.MotionEvent; 26 import android.view.View; 27 import android.view.ViewConfiguration; 28 import android.view.ViewGroup; 29 30 /** 31 * Test utilities for touch emulation. 32 */ 33 public final class CtsTouchUtils { 34 /** 35 * Interface definition for a callback to be invoked when an event has been injected. 36 */ 37 public interface EventInjectionListener { 38 /** 39 * Callback method to be invoked when a {MotionEvent#ACTION_DOWN} has been injected. 40 * @param xOnScreen X coordinate of the injected event. 41 * @param yOnScreen Y coordinate of the injected event. 42 */ onDownInjected(int xOnScreen, int yOnScreen)43 public void onDownInjected(int xOnScreen, int yOnScreen); 44 45 /** 46 * Callback method to be invoked when a {MotionEvent#ACTION_MOVE} has been injected. 47 * @param xOnScreen X coordinates of the injected event. 48 * @param yOnScreen Y coordinates of the injected event. 49 */ onMoveInjected(int[] xOnScreen, int[] yOnScreen)50 public void onMoveInjected(int[] xOnScreen, int[] yOnScreen); 51 52 /** 53 * Callback method to be invoked when a {MotionEvent#ACTION_UP} has been injected. 54 * @param xOnScreen X coordinate of the injected event. 55 * @param yOnScreen Y coordinate of the injected event. 56 */ onUpInjected(int xOnScreen, int yOnScreen)57 public void onUpInjected(int xOnScreen, int yOnScreen); 58 } 59 CtsTouchUtils()60 private CtsTouchUtils() {} 61 62 /** 63 * Emulates a tap in the center of the passed {@link View}. 64 * 65 * @param instrumentation the instrumentation used to run the test 66 * @param view the view to "tap" 67 */ emulateTapOnViewCenter(Instrumentation instrumentation, View view)68 public static void emulateTapOnViewCenter(Instrumentation instrumentation, View view) { 69 emulateTapOnView(instrumentation, view, view.getWidth() / 2, view.getHeight() / 2); 70 } 71 72 /** 73 * Emulates a tap on a point relative to the top-left corner of the passed {@link View}. Offset 74 * parameters are used to compute the final screen coordinates of the tap point. 75 * 76 * @param instrumentation the instrumentation used to run the test 77 * @param anchorView the anchor view to determine the tap location on the screen 78 * @param offsetX extra X offset for the tap 79 * @param offsetY extra Y offset for the tap 80 */ emulateTapOnView(Instrumentation instrumentation, View anchorView, int offsetX, int offsetY)81 public static void emulateTapOnView(Instrumentation instrumentation, View anchorView, 82 int offsetX, int offsetY) { 83 final int touchSlop = ViewConfiguration.get(anchorView.getContext()).getScaledTouchSlop(); 84 // Get anchor coordinates on the screen 85 final int[] viewOnScreenXY = new int[2]; 86 anchorView.getLocationOnScreen(viewOnScreenXY); 87 int xOnScreen = viewOnScreenXY[0] + offsetX; 88 int yOnScreen = viewOnScreenXY[1] + offsetY; 89 final UiAutomation uiAutomation = instrumentation.getUiAutomation(); 90 final long downTime = SystemClock.uptimeMillis(); 91 92 injectDownEvent(uiAutomation, downTime, xOnScreen, yOnScreen, null); 93 injectMoveEventForTap(uiAutomation, downTime, touchSlop, xOnScreen, yOnScreen); 94 injectUpEvent(uiAutomation, downTime, false, xOnScreen, yOnScreen, null); 95 96 // Wait for the system to process all events in the queue 97 instrumentation.waitForIdleSync(); 98 } 99 100 /** 101 * Emulates a double tap in the center of the passed {@link View}. 102 * 103 * @param instrumentation the instrumentation used to run the test 104 * @param view the view to "double tap" 105 */ emulateDoubleTapOnViewCenter(Instrumentation instrumentation, View view)106 public static void emulateDoubleTapOnViewCenter(Instrumentation instrumentation, View view) { 107 emulateDoubleTapOnView(instrumentation, view, view.getWidth() / 2, view.getHeight() / 2); 108 } 109 110 /** 111 * Emulates a double tap on a point relative to the top-left corner of the passed {@link View}. 112 * Offset parameters are used to compute the final screen coordinates of the tap points. 113 * 114 * @param instrumentation the instrumentation used to run the test 115 * @param anchorView the anchor view to determine the tap location on the screen 116 * @param offsetX extra X offset for the taps 117 * @param offsetY extra Y offset for the taps 118 */ emulateDoubleTapOnView(Instrumentation instrumentation, View anchorView, int offsetX, int offsetY)119 public static void emulateDoubleTapOnView(Instrumentation instrumentation, View anchorView, 120 int offsetX, int offsetY) { 121 final int touchSlop = ViewConfiguration.get(anchorView.getContext()).getScaledTouchSlop(); 122 // Get anchor coordinates on the screen 123 final int[] viewOnScreenXY = new int[2]; 124 anchorView.getLocationOnScreen(viewOnScreenXY); 125 int xOnScreen = viewOnScreenXY[0] + offsetX; 126 int yOnScreen = viewOnScreenXY[1] + offsetY; 127 final UiAutomation uiAutomation = instrumentation.getUiAutomation(); 128 final long downTime = SystemClock.uptimeMillis(); 129 130 injectDownEvent(uiAutomation, downTime, xOnScreen, yOnScreen, null); 131 injectMoveEventForTap(uiAutomation, downTime, touchSlop, xOnScreen, yOnScreen); 132 injectUpEvent(uiAutomation, downTime, false, xOnScreen, yOnScreen, null); 133 injectDownEvent(uiAutomation, downTime, xOnScreen, yOnScreen, null); 134 injectMoveEventForTap(uiAutomation, downTime, touchSlop, xOnScreen, yOnScreen); 135 injectUpEvent(uiAutomation, downTime, false, xOnScreen, yOnScreen, null); 136 137 // Wait for the system to process all events in the queue 138 instrumentation.waitForIdleSync(); 139 } 140 141 /** 142 * Emulates a linear drag gesture between 2 points across the screen. 143 * 144 * @param instrumentation the instrumentation used to run the test 145 * @param dragStartX Start X of the emulated drag gesture 146 * @param dragStartY Start Y of the emulated drag gesture 147 * @param dragAmountX X amount of the emulated drag gesture 148 * @param dragAmountY Y amount of the emulated drag gesture 149 */ emulateDragGesture(Instrumentation instrumentation, int dragStartX, int dragStartY, int dragAmountX, int dragAmountY)150 public static void emulateDragGesture(Instrumentation instrumentation, 151 int dragStartX, int dragStartY, int dragAmountX, int dragAmountY) { 152 emulateDragGesture(instrumentation, dragStartX, dragStartY, dragAmountX, dragAmountY, 153 2000, 20, null); 154 } 155 emulateDragGesture(Instrumentation instrumentation, int dragStartX, int dragStartY, int dragAmountX, int dragAmountY, int dragDurationMs, int moveEventCount)156 private static void emulateDragGesture(Instrumentation instrumentation, 157 int dragStartX, int dragStartY, int dragAmountX, int dragAmountY, 158 int dragDurationMs, int moveEventCount) { 159 emulateDragGesture(instrumentation, dragStartX, dragStartY, dragAmountX, dragAmountY, 160 dragDurationMs, moveEventCount, null); 161 } 162 emulateDragGesture(Instrumentation instrumentation, int dragStartX, int dragStartY, int dragAmountX, int dragAmountY, int dragDurationMs, int moveEventCount, EventInjectionListener eventInjectionListener)163 private static void emulateDragGesture(Instrumentation instrumentation, 164 int dragStartX, int dragStartY, int dragAmountX, int dragAmountY, 165 int dragDurationMs, int moveEventCount, 166 EventInjectionListener eventInjectionListener) { 167 // We are using the UiAutomation object to inject events so that drag works 168 // across view / window boundaries (such as for the emulated drag and drop 169 // sequences) 170 final UiAutomation uiAutomation = instrumentation.getUiAutomation(); 171 final long downTime = SystemClock.uptimeMillis(); 172 173 injectDownEvent(uiAutomation, downTime, dragStartX, dragStartY, eventInjectionListener); 174 175 // Inject a sequence of MOVE events that emulate the "move" part of the gesture 176 injectMoveEventsForDrag(uiAutomation, downTime, true, dragStartX, dragStartY, 177 dragStartX + dragAmountX, dragStartY + dragAmountY, moveEventCount, dragDurationMs, 178 eventInjectionListener); 179 180 injectUpEvent(uiAutomation, downTime, true, dragStartX + dragAmountX, 181 dragStartY + dragAmountY, eventInjectionListener); 182 183 // Wait for the system to process all events in the queue 184 instrumentation.waitForIdleSync(); 185 } 186 187 /** 188 * Emulates a series of linear drag gestures across the screen between multiple points without 189 * lifting the finger. Note that this function does not support curve movements between the 190 * points. 191 * 192 * @param instrumentation the instrumentation used to run the test 193 * @param coordinates the ordered list of points for the drag gesture 194 */ emulateDragGesture(Instrumentation instrumentation, SparseArray<Point> coordinates)195 public static void emulateDragGesture(Instrumentation instrumentation, 196 SparseArray<Point> coordinates) { 197 emulateDragGesture(instrumentation, coordinates, 2000, 20); 198 } 199 emulateDragGesture(Instrumentation instrumentation, SparseArray<Point> coordinates, int dragDurationMs, int moveEventCount)200 private static void emulateDragGesture(Instrumentation instrumentation, 201 SparseArray<Point> coordinates, int dragDurationMs, int moveEventCount) { 202 final int coordinatesSize = coordinates.size(); 203 if (coordinatesSize < 2) { 204 throw new IllegalArgumentException("Need at least 2 points for emulating drag"); 205 } 206 // We are using the UiAutomation object to inject events so that drag works 207 // across view / window boundaries (such as for the emulated drag and drop 208 // sequences) 209 final UiAutomation uiAutomation = instrumentation.getUiAutomation(); 210 final long downTime = SystemClock.uptimeMillis(); 211 212 injectDownEvent(uiAutomation, downTime, coordinates.get(0).x, coordinates.get(0).y, null); 213 214 // Move to each coordinate. 215 for (int i = 0; i < coordinatesSize - 1; i++) { 216 // Inject a sequence of MOVE events that emulate the "move" part of the gesture. 217 injectMoveEventsForDrag(uiAutomation, 218 downTime, 219 true, 220 coordinates.get(i).x, 221 coordinates.get(i).y, 222 coordinates.get(i + 1).x, 223 coordinates.get(i + 1).y, 224 moveEventCount, 225 dragDurationMs, 226 null); 227 } 228 229 injectUpEvent(uiAutomation, 230 downTime, 231 true, 232 coordinates.get(coordinatesSize - 1).x, 233 coordinates.get(coordinatesSize - 1).y, 234 null); 235 236 // Wait for the system to process all events in the queue 237 instrumentation.waitForIdleSync(); 238 } 239 injectDownEvent(UiAutomation uiAutomation, long downTime, int xOnScreen, int yOnScreen, EventInjectionListener eventInjectionListener)240 private static long injectDownEvent(UiAutomation uiAutomation, long downTime, int xOnScreen, 241 int yOnScreen, EventInjectionListener eventInjectionListener) { 242 MotionEvent eventDown = MotionEvent.obtain( 243 downTime, downTime, MotionEvent.ACTION_DOWN, xOnScreen, yOnScreen, 1); 244 eventDown.setSource(InputDevice.SOURCE_TOUCHSCREEN); 245 uiAutomation.injectInputEvent(eventDown, true); 246 if (eventInjectionListener != null) { 247 eventInjectionListener.onDownInjected(xOnScreen, yOnScreen); 248 } 249 eventDown.recycle(); 250 return downTime; 251 } 252 injectMoveEventForTap(UiAutomation uiAutomation, long downTime, int touchSlop, int xOnScreen, int yOnScreen)253 private static void injectMoveEventForTap(UiAutomation uiAutomation, long downTime, 254 int touchSlop, int xOnScreen, int yOnScreen) { 255 MotionEvent eventMove = MotionEvent.obtain(downTime, downTime, MotionEvent.ACTION_MOVE, 256 xOnScreen + (touchSlop / 2.0f), yOnScreen + (touchSlop / 2.0f), 1); 257 eventMove.setSource(InputDevice.SOURCE_TOUCHSCREEN); 258 uiAutomation.injectInputEvent(eventMove, true); 259 eventMove.recycle(); 260 } 261 injectMoveEventsForDrag(UiAutomation uiAutomation, long downTime, boolean useCurrentEventTime, int dragStartX, int dragStartY, int dragEndX, int dragEndY, int moveEventCount, int dragDurationMs, EventInjectionListener eventInjectionListener)262 private static void injectMoveEventsForDrag(UiAutomation uiAutomation, long downTime, 263 boolean useCurrentEventTime, int dragStartX, int dragStartY, int dragEndX, int dragEndY, 264 int moveEventCount, int dragDurationMs, EventInjectionListener eventInjectionListener) { 265 final int dragAmountX = dragEndX - dragStartX; 266 final int dragAmountY = dragEndY - dragStartY; 267 final int sleepTime = dragDurationMs / moveEventCount; 268 269 // sleep for a bit to emulate the overall drag gesture. 270 long prevEventTime = downTime; 271 SystemClock.sleep(sleepTime); 272 for (int i = 0; i < moveEventCount; i++) { 273 // Note that the first MOVE event is generated "away" from the coordinates 274 // of the start / DOWN event, and the last MOVE event is generated 275 // at the same coordinates as the subsequent UP event. 276 final int moveX = dragStartX + dragAmountX * (i + 1) / moveEventCount; 277 final int moveY = dragStartY + dragAmountY * (i + 1) / moveEventCount; 278 long eventTime = useCurrentEventTime ? SystemClock.uptimeMillis() : downTime; 279 280 // If necessary, generate history for our next MOVE event. The history is generated 281 // to be spaced at 10 millisecond intervals, interpolating the coordinates from the 282 // last generated MOVE event to our current one. 283 int historyEventCount = (int) ((eventTime - prevEventTime) / 10); 284 int[] xCoordsForListener = (eventInjectionListener == null) ? null : 285 new int[Math.max(1, historyEventCount)]; 286 int[] yCoordsForListener = (eventInjectionListener == null) ? null : 287 new int[Math.max(1, historyEventCount)]; 288 MotionEvent eventMove = null; 289 if (historyEventCount == 0) { 290 eventMove = MotionEvent.obtain( 291 downTime, eventTime, MotionEvent.ACTION_MOVE, moveX, moveY, 1); 292 if (eventInjectionListener != null) { 293 xCoordsForListener[0] = moveX; 294 yCoordsForListener[0] = moveY; 295 } 296 } else { 297 final int prevMoveX = dragStartX + dragAmountX * i / moveEventCount; 298 final int prevMoveY = dragStartY + dragAmountY * i / moveEventCount; 299 final int deltaMoveX = moveX - prevMoveX; 300 final int deltaMoveY = moveY - prevMoveY; 301 final long deltaTime = (eventTime - prevEventTime); 302 for (int historyIndex = 0; historyIndex < historyEventCount; historyIndex++) { 303 int stepMoveX = prevMoveX + deltaMoveX * (historyIndex + 1) / historyEventCount; 304 int stepMoveY = prevMoveY + deltaMoveY * (historyIndex + 1) / historyEventCount; 305 long stepEventTime = useCurrentEventTime 306 ? prevEventTime + deltaTime * (historyIndex + 1) / historyEventCount 307 : downTime; 308 if (historyIndex == 0) { 309 // Generate the first event in our sequence 310 eventMove = MotionEvent.obtain(downTime, stepEventTime, 311 MotionEvent.ACTION_MOVE, stepMoveX, stepMoveY, 1); 312 } else { 313 // and then add to it 314 eventMove.addBatch(stepEventTime, stepMoveX, stepMoveY, 1.0f, 1.0f, 1); 315 } 316 if (eventInjectionListener != null) { 317 xCoordsForListener[historyIndex] = stepMoveX; 318 yCoordsForListener[historyIndex] = stepMoveY; 319 } 320 } 321 } 322 323 eventMove.setSource(InputDevice.SOURCE_TOUCHSCREEN); 324 uiAutomation.injectInputEvent(eventMove, true); 325 if (eventInjectionListener != null) { 326 eventInjectionListener.onMoveInjected(xCoordsForListener, yCoordsForListener); 327 } 328 eventMove.recycle(); 329 prevEventTime = eventTime; 330 331 // sleep for a bit to emulate the overall drag gesture. 332 SystemClock.sleep(sleepTime); 333 } 334 } 335 injectUpEvent(UiAutomation uiAutomation, long downTime, boolean useCurrentEventTime, int xOnScreen, int yOnScreen, EventInjectionListener eventInjectionListener)336 private static void injectUpEvent(UiAutomation uiAutomation, long downTime, 337 boolean useCurrentEventTime, int xOnScreen, int yOnScreen, 338 EventInjectionListener eventInjectionListener) { 339 long eventTime = useCurrentEventTime ? SystemClock.uptimeMillis() : downTime; 340 MotionEvent eventUp = MotionEvent.obtain( 341 downTime, eventTime, MotionEvent.ACTION_UP, xOnScreen, yOnScreen, 1); 342 eventUp.setSource(InputDevice.SOURCE_TOUCHSCREEN); 343 uiAutomation.injectInputEvent(eventUp, true); 344 if (eventInjectionListener != null) { 345 eventInjectionListener.onUpInjected(xOnScreen, yOnScreen); 346 } 347 eventUp.recycle(); 348 } 349 350 /** 351 * Emulates a fling gesture across the horizontal center of the passed view. 352 * 353 * @param instrumentation the instrumentation used to run the test 354 * @param view the view to fling 355 * @param isDownwardsFlingGesture if <code>true</code>, the emulated fling will 356 * be a downwards gesture 357 * @return The vertical amount of emulated fling in pixels 358 */ emulateFlingGesture(Instrumentation instrumentation, View view, boolean isDownwardsFlingGesture)359 public static int emulateFlingGesture(Instrumentation instrumentation, 360 View view, boolean isDownwardsFlingGesture) { 361 return emulateFlingGesture(instrumentation, view, isDownwardsFlingGesture, null); 362 } 363 364 /** 365 * Emulates a fling gesture across the horizontal center of the passed view. 366 * 367 * @param instrumentation the instrumentation used to run the test 368 * @param view the view to fling 369 * @param isDownwardsFlingGesture if <code>true</code>, the emulated fling will 370 * be a downwards gesture 371 * @param eventInjectionListener optional listener to notify about the injected events 372 * @return The vertical amount of emulated fling in pixels 373 */ emulateFlingGesture(Instrumentation instrumentation, View view, boolean isDownwardsFlingGesture, EventInjectionListener eventInjectionListener)374 public static int emulateFlingGesture(Instrumentation instrumentation, 375 View view, boolean isDownwardsFlingGesture, 376 EventInjectionListener eventInjectionListener) { 377 final ViewConfiguration configuration = ViewConfiguration.get(view.getContext()); 378 final int flingVelocity = (configuration.getScaledMinimumFlingVelocity() + 379 configuration.getScaledMaximumFlingVelocity()) / 2; 380 // Get view coordinates on the screen 381 final int[] viewOnScreenXY = new int[2]; 382 view.getLocationOnScreen(viewOnScreenXY); 383 384 // Our fling gesture will be from 25% height of the view to 75% height of the view 385 // for downwards fling gesture, and the other way around for upwards fling gesture 386 final int viewHeight = view.getHeight(); 387 final int x = viewOnScreenXY[0] + view.getWidth() / 2; 388 final int startY = isDownwardsFlingGesture ? viewOnScreenXY[1] + viewHeight / 4 389 : viewOnScreenXY[1] + 3 * viewHeight / 4; 390 final int amountY = isDownwardsFlingGesture ? viewHeight / 2 : -viewHeight / 2; 391 392 // Compute fling gesture duration based on the distance (50% height of the view) and 393 // fling velocity 394 final int durationMs = (1000 * viewHeight) / (2 * flingVelocity); 395 396 // And do the same event injection sequence as our generic drag gesture 397 emulateDragGesture(instrumentation, x, startY, 0, amountY, durationMs, durationMs / 16, 398 eventInjectionListener); 399 400 return amountY; 401 } 402 403 private static class ViewStateSnapshot { 404 final View mFirst; 405 final View mLast; 406 final int mFirstTop; 407 final int mLastBottom; 408 final int mChildCount; ViewStateSnapshot(ViewGroup viewGroup)409 private ViewStateSnapshot(ViewGroup viewGroup) { 410 mChildCount = viewGroup.getChildCount(); 411 if (mChildCount == 0) { 412 mFirst = mLast = null; 413 mFirstTop = mLastBottom = Integer.MIN_VALUE; 414 } else { 415 mFirst = viewGroup.getChildAt(0); 416 mLast = viewGroup.getChildAt(mChildCount - 1); 417 mFirstTop = mFirst.getTop(); 418 mLastBottom = mLast.getBottom(); 419 } 420 } 421 422 @Override equals(Object o)423 public boolean equals(Object o) { 424 if (this == o) { 425 return true; 426 } 427 if (o == null || getClass() != o.getClass()) { 428 return false; 429 } 430 431 final ViewStateSnapshot that = (ViewStateSnapshot) o; 432 return mFirstTop == that.mFirstTop && 433 mLastBottom == that.mLastBottom && 434 mFirst == that.mFirst && 435 mLast == that.mLast && 436 mChildCount == that.mChildCount; 437 } 438 439 @Override hashCode()440 public int hashCode() { 441 int result = mFirst != null ? mFirst.hashCode() : 0; 442 result = 31 * result + (mLast != null ? mLast.hashCode() : 0); 443 result = 31 * result + mFirstTop; 444 result = 31 * result + mLastBottom; 445 result = 31 * result + mChildCount; 446 return result; 447 } 448 } 449 450 /** 451 * Emulates a scroll to the bottom of the specified {@link ViewGroup}. 452 * 453 * @param instrumentation the instrumentation used to run the test 454 * @param viewGroup View group 455 */ emulateScrollToBottom(Instrumentation instrumentation, ViewGroup viewGroup)456 public static void emulateScrollToBottom(Instrumentation instrumentation, ViewGroup viewGroup) { 457 final int[] viewGroupOnScreenXY = new int[2]; 458 viewGroup.getLocationOnScreen(viewGroupOnScreenXY); 459 460 final int emulatedX = viewGroupOnScreenXY[0] + viewGroup.getWidth() / 2; 461 final int emulatedStartY = viewGroupOnScreenXY[1] + 3 * viewGroup.getHeight() / 4; 462 final int swipeAmount = viewGroup.getHeight() / 2; 463 464 ViewStateSnapshot prev; 465 ViewStateSnapshot next = new ViewStateSnapshot(viewGroup); 466 do { 467 prev = next; 468 emulateDragGesture(instrumentation, emulatedX, emulatedStartY, 0, -swipeAmount, 469 300, 10); 470 next = new ViewStateSnapshot(viewGroup); 471 } while (!prev.equals(next)); 472 } 473 474 /** 475 * Emulates a long press in the center of the passed {@link View}. 476 * 477 * @param instrumentation the instrumentation used to run the test 478 * @param view the view to "long press" 479 */ emulateLongPressOnViewCenter(Instrumentation instrumentation, View view)480 public static void emulateLongPressOnViewCenter(Instrumentation instrumentation, View view) { 481 emulateLongPressOnViewCenter(instrumentation, view, 0); 482 } 483 484 /** 485 * Emulates a long press in the center of the passed {@link View}. 486 * 487 * @param instrumentation the instrumentation used to run the test 488 * @param view the view to "long press" 489 * @param extraWaitMs the duration of emulated "long press" in milliseconds starting 490 * after system-level long press timeout. 491 */ emulateLongPressOnViewCenter(Instrumentation instrumentation, View view, long extraWaitMs)492 public static void emulateLongPressOnViewCenter(Instrumentation instrumentation, View view, 493 long extraWaitMs) { 494 final int touchSlop = ViewConfiguration.get(view.getContext()).getScaledTouchSlop(); 495 // Use instrumentation to emulate a tap on the spinner to bring down its popup 496 final int[] viewOnScreenXY = new int[2]; 497 view.getLocationOnScreen(viewOnScreenXY); 498 int xOnScreen = viewOnScreenXY[0] + view.getWidth() / 2; 499 int yOnScreen = viewOnScreenXY[1] + view.getHeight() / 2; 500 501 emulateLongPressOnScreen( 502 instrumentation, xOnScreen, yOnScreen, touchSlop, extraWaitMs, true); 503 } 504 505 /** 506 * Emulates a long press confirmed on a point relative to the top-left corner of the passed 507 * {@link View}. Offset parameters are used to compute the final screen coordinates of the 508 * press point. 509 * 510 * @param instrumentation the instrumentation used to run the test 511 * @param view the view to "long press" 512 * @param offsetX extra X offset for the tap 513 * @param offsetY extra Y offset for the tap 514 */ emulateLongPressOnView(Instrumentation instrumentation, View view, int offsetX, int offsetY)515 public static void emulateLongPressOnView(Instrumentation instrumentation, View view, 516 int offsetX, int offsetY) { 517 final int touchSlop = ViewConfiguration.get(view.getContext()).getScaledTouchSlop(); 518 final int[] viewOnScreenXY = new int[2]; 519 view.getLocationOnScreen(viewOnScreenXY); 520 int xOnScreen = viewOnScreenXY[0] + offsetX; 521 int yOnScreen = viewOnScreenXY[1] + offsetY; 522 523 emulateLongPressOnScreen(instrumentation, xOnScreen, yOnScreen, touchSlop, 0, true); 524 } 525 526 /** 527 * Emulates a long press then a linear drag gesture between 2 points across the screen. 528 * This is used for drag selection. 529 * 530 * @param instrumentation the instrumentation used to run the test 531 * @param dragStartX Start X of the emulated drag gesture 532 * @param dragStartY Start Y of the emulated drag gesture 533 * @param dragAmountX X amount of the emulated drag gesture 534 * @param dragAmountY Y amount of the emulated drag gesture 535 */ emulateLongPressAndDragGesture(Instrumentation instrumentation, int dragStartX, int dragStartY, int dragAmountX, int dragAmountY)536 public static void emulateLongPressAndDragGesture(Instrumentation instrumentation, 537 int dragStartX, int dragStartY, int dragAmountX, int dragAmountY) { 538 emulateLongPressOnScreen(instrumentation, dragStartX, dragStartY, 539 0 /* touchSlop */, 0 /* extraWaitMs */, false /* upGesture */); 540 emulateDragGesture(instrumentation, dragStartX, dragStartY, dragAmountX, dragAmountY); 541 } 542 543 /** 544 * Emulates a long press on the screen. 545 * 546 * @param instrumentation the instrumentation used to run the test 547 * @param xOnScreen X position on screen for the "long press" 548 * @param yOnScreen Y position on screen for the "long press" 549 * @param extraWaitMs extra duration of emulated long press in milliseconds added 550 * after the system-level "long press" timeout. 551 * @param upGesture whether to include an up event. 552 */ emulateLongPressOnScreen(Instrumentation instrumentation, int xOnScreen, int yOnScreen, int touchSlop, long extraWaitMs, boolean upGesture)553 private static void emulateLongPressOnScreen(Instrumentation instrumentation, 554 int xOnScreen, int yOnScreen, int touchSlop, long extraWaitMs, boolean upGesture) { 555 final UiAutomation uiAutomation = instrumentation.getUiAutomation(); 556 final long downTime = SystemClock.uptimeMillis(); 557 558 injectDownEvent(uiAutomation, downTime, xOnScreen, yOnScreen, null); 559 injectMoveEventForTap(uiAutomation, downTime, touchSlop, xOnScreen, yOnScreen); 560 SystemClock.sleep((long) (ViewConfiguration.getLongPressTimeout() * 1.5f) + extraWaitMs); 561 if (upGesture) { 562 injectUpEvent(uiAutomation, downTime, false, xOnScreen, yOnScreen, null); 563 } 564 565 // Wait for the system to process all events in the queue 566 instrumentation.waitForIdleSync(); 567 } 568 } 569