1 /* 2 * Copyright (C) 2012 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.uiautomator.core; 18 19 import android.graphics.Point; 20 import android.graphics.Rect; 21 import android.os.SystemClock; 22 import android.util.Log; 23 import android.view.KeyEvent; 24 import android.view.MotionEvent.PointerCoords; 25 import android.view.accessibility.AccessibilityNodeInfo; 26 27 /** 28 * A UiObject is a representation of a view. It is not in any way directly bound to a 29 * view as an object reference. A UiObject contains information to help it 30 * locate a matching view at runtime based on the {@link UiSelector} properties specified in 31 * its constructor. Once you create an instance of a UiObject, it can 32 * be reused for different views that match the selector criteria. 33 * @since API Level 16 34 */ 35 public class UiObject { 36 private static final String LOG_TAG = UiObject.class.getSimpleName(); 37 /** 38 * @since API Level 16 39 * @deprecated use {@link Configurator#setWaitForSelectorTimeout(long)} 40 **/ 41 @Deprecated 42 protected static final long WAIT_FOR_SELECTOR_TIMEOUT = 10 * 1000; 43 /** 44 * @since API Level 16 45 **/ 46 protected static final long WAIT_FOR_SELECTOR_POLL = 1000; 47 // set a default timeout to 5.5s, since ANR threshold is 5s 48 /** 49 * @since API Level 16 50 **/ 51 protected static final long WAIT_FOR_WINDOW_TMEOUT = 5500; 52 /** 53 * @since API Level 16 54 **/ 55 protected static final int SWIPE_MARGIN_LIMIT = 5; 56 /** 57 * @since API Level 17 58 * @deprecated use {@link Configurator#setScrollAcknowledgmentTimeout(long)} 59 **/ 60 @Deprecated 61 protected static final long WAIT_FOR_EVENT_TMEOUT = 3 * 1000; 62 /** 63 * @since API Level 18 64 **/ 65 protected static final int FINGER_TOUCH_HALF_WIDTH = 20; 66 67 private final UiSelector mSelector; 68 69 private final Configurator mConfig = Configurator.getInstance(); 70 71 /** 72 * Constructs a UiObject to represent a view that matches the specified 73 * selector criteria. 74 * @param selector 75 * @since API Level 16 76 */ UiObject(UiSelector selector)77 public UiObject(UiSelector selector) { 78 mSelector = selector; 79 } 80 81 /** 82 * Debugging helper. A test can dump the properties of a selector as a string 83 * to its logs if needed. <code>getSelector().toString();</code> 84 * 85 * @return {@link UiSelector} 86 * @since API Level 16 87 */ getSelector()88 public final UiSelector getSelector() { 89 Tracer.trace(); 90 return new UiSelector(mSelector); 91 } 92 93 /** 94 * Retrieves the {@link QueryController} to translate a {@link UiSelector} selector 95 * into an {@link AccessibilityNodeInfo}. 96 * 97 * @return {@link QueryController} 98 */ getQueryController()99 QueryController getQueryController() { 100 return UiDevice.getInstance().getAutomatorBridge().getQueryController(); 101 } 102 103 /** 104 * Retrieves the {@link InteractionController} to perform finger actions such as tapping, 105 * swiping, or entering text. 106 * 107 * @return {@link InteractionController} 108 */ getInteractionController()109 InteractionController getInteractionController() { 110 return UiDevice.getInstance().getAutomatorBridge().getInteractionController(); 111 } 112 113 /** 114 * Creates a new UiObject for a child view that is under the present UiObject. 115 * 116 * @param selector for child view to match 117 * @return a new UiObject representing the child view 118 * @since API Level 16 119 */ getChild(UiSelector selector)120 public UiObject getChild(UiSelector selector) throws UiObjectNotFoundException { 121 Tracer.trace(selector); 122 return new UiObject(getSelector().childSelector(selector)); 123 } 124 125 /** 126 * Creates a new UiObject for a sibling view or a child of the sibling view, 127 * relative to the present UiObject. 128 * 129 * @param selector for a sibling view or children of the sibling view 130 * @return a new UiObject representing the matched view 131 * @throws UiObjectNotFoundException 132 * @since API Level 16 133 */ getFromParent(UiSelector selector)134 public UiObject getFromParent(UiSelector selector) throws UiObjectNotFoundException { 135 Tracer.trace(selector); 136 return new UiObject(getSelector().fromParent(selector)); 137 } 138 139 /** 140 * Counts the child views immediately under the present UiObject. 141 * 142 * @return the count of child views. 143 * @throws UiObjectNotFoundException 144 * @since API Level 16 145 */ getChildCount()146 public int getChildCount() throws UiObjectNotFoundException { 147 Tracer.trace(); 148 AccessibilityNodeInfo node = findAccessibilityNodeInfo(mConfig.getWaitForSelectorTimeout()); 149 if(node == null) { 150 throw new UiObjectNotFoundException(getSelector().toString()); 151 } 152 return node.getChildCount(); 153 } 154 155 /** 156 * Finds a matching UI element in the accessibility hierarchy, by 157 * using the selector for this UiObject. 158 * 159 * @param timeout in milliseconds 160 * @return AccessibilityNodeInfo if found else null 161 * @since API Level 16 162 */ findAccessibilityNodeInfo(long timeout)163 protected AccessibilityNodeInfo findAccessibilityNodeInfo(long timeout) { 164 AccessibilityNodeInfo node = null; 165 long startMills = SystemClock.uptimeMillis(); 166 long currentMills = 0; 167 while (currentMills <= timeout) { 168 node = getQueryController().findAccessibilityNodeInfo(getSelector()); 169 if (node != null) { 170 break; 171 } else { 172 // does nothing if we're reentering another runWatchers() 173 UiDevice.getInstance().runWatchers(); 174 } 175 currentMills = SystemClock.uptimeMillis() - startMills; 176 if(timeout > 0) { 177 SystemClock.sleep(WAIT_FOR_SELECTOR_POLL); 178 } 179 } 180 return node; 181 } 182 183 /** 184 * Drags this object to a destination UiObject. 185 * The number of steps specified in your input parameter can influence the 186 * drag speed, and varying speeds may impact the results. Consider 187 * evaluating different speeds when using this method in your tests. 188 * 189 * @param destObj the destination UiObject. 190 * @param steps usually 40 steps. You can increase or decrease the steps to change the speed. 191 * @return true if successful 192 * @throws UiObjectNotFoundException 193 * @since API Level 18 194 */ dragTo(UiObject destObj, int steps)195 public boolean dragTo(UiObject destObj, int steps) throws UiObjectNotFoundException { 196 Rect srcRect = getVisibleBounds(); 197 Rect dstRect = destObj.getVisibleBounds(); 198 return getInteractionController().swipe(srcRect.centerX(), srcRect.centerY(), 199 dstRect.centerX(), dstRect.centerY(), steps, true); 200 } 201 202 /** 203 * Drags this object to arbitrary coordinates. 204 * The number of steps specified in your input parameter can influence the 205 * drag speed, and varying speeds may impact the results. Consider 206 * evaluating different speeds when using this method in your tests. 207 * 208 * @param destX the X-axis coordinate. 209 * @param destY the Y-axis coordinate. 210 * @param steps usually 40 steps. You can increase or decrease the steps to change the speed. 211 * @return true if successful 212 * @throws UiObjectNotFoundException 213 * @since API Level 18 214 */ dragTo(int destX, int destY, int steps)215 public boolean dragTo(int destX, int destY, int steps) throws UiObjectNotFoundException { 216 Rect srcRect = getVisibleBounds(); 217 return getInteractionController().swipe(srcRect.centerX(), srcRect.centerY(), destX, destY, 218 steps, true); 219 } 220 221 /** 222 * Performs the swipe up action on the UiObject. 223 * See also: 224 * <ul> 225 * <li>{@link UiScrollable#scrollToBeginning(int)}</li> 226 * <li>{@link UiScrollable#scrollToEnd(int)}</li> 227 * <li>{@link UiScrollable#scrollBackward()}</li> 228 * <li>{@link UiScrollable#scrollForward()}</li> 229 * </ul> 230 * 231 * @param steps indicates the number of injected move steps into the system. Steps are 232 * injected about 5ms apart. So a 100 steps may take about 1/2 second to complete. 233 * @return true of successful 234 * @throws UiObjectNotFoundException 235 * @since API Level 16 236 */ swipeUp(int steps)237 public boolean swipeUp(int steps) throws UiObjectNotFoundException { 238 Tracer.trace(steps); 239 Rect rect = getVisibleBounds(); 240 if(rect.height() <= SWIPE_MARGIN_LIMIT * 2) 241 return false; // too small to swipe 242 return getInteractionController().swipe(rect.centerX(), 243 rect.bottom - SWIPE_MARGIN_LIMIT, rect.centerX(), rect.top + SWIPE_MARGIN_LIMIT, 244 steps); 245 } 246 247 /** 248 * Performs the swipe down action on the UiObject. 249 * The swipe gesture can be performed over any surface. The targeted 250 * UI element does not need to be scrollable. 251 * See also: 252 * <ul> 253 * <li>{@link UiScrollable#scrollToBeginning(int)}</li> 254 * <li>{@link UiScrollable#scrollToEnd(int)}</li> 255 * <li>{@link UiScrollable#scrollBackward()}</li> 256 * <li>{@link UiScrollable#scrollForward()}</li> 257 * </ul> 258 * 259 * @param steps indicates the number of injected move steps into the system. Steps are 260 * injected about 5ms apart. So a 100 steps may take about 1/2 second to complete. 261 * @return true if successful 262 * @throws UiObjectNotFoundException 263 * @since API Level 16 264 */ swipeDown(int steps)265 public boolean swipeDown(int steps) throws UiObjectNotFoundException { 266 Tracer.trace(steps); 267 Rect rect = getVisibleBounds(); 268 if(rect.height() <= SWIPE_MARGIN_LIMIT * 2) 269 return false; // too small to swipe 270 return getInteractionController().swipe(rect.centerX(), 271 rect.top + SWIPE_MARGIN_LIMIT, rect.centerX(), 272 rect.bottom - SWIPE_MARGIN_LIMIT, steps); 273 } 274 275 /** 276 * Performs the swipe left action on the UiObject. 277 * The swipe gesture can be performed over any surface. The targeted 278 * UI element does not need to be scrollable. 279 * See also: 280 * <ul> 281 * <li>{@link UiScrollable#scrollToBeginning(int)}</li> 282 * <li>{@link UiScrollable#scrollToEnd(int)}</li> 283 * <li>{@link UiScrollable#scrollBackward()}</li> 284 * <li>{@link UiScrollable#scrollForward()}</li> 285 * </ul> 286 * 287 * @param steps indicates the number of injected move steps into the system. Steps are 288 * injected about 5ms apart. So a 100 steps may take about 1/2 second to complete. 289 * @return true if successful 290 * @throws UiObjectNotFoundException 291 * @since API Level 16 292 */ swipeLeft(int steps)293 public boolean swipeLeft(int steps) throws UiObjectNotFoundException { 294 Tracer.trace(steps); 295 Rect rect = getVisibleBounds(); 296 if(rect.width() <= SWIPE_MARGIN_LIMIT * 2) 297 return false; // too small to swipe 298 return getInteractionController().swipe(rect.right - SWIPE_MARGIN_LIMIT, 299 rect.centerY(), rect.left + SWIPE_MARGIN_LIMIT, rect.centerY(), steps); 300 } 301 302 /** 303 * Performs the swipe right action on the UiObject. 304 * The swipe gesture can be performed over any surface. The targeted 305 * UI element does not need to be scrollable. 306 * See also: 307 * <ul> 308 * <li>{@link UiScrollable#scrollToBeginning(int)}</li> 309 * <li>{@link UiScrollable#scrollToEnd(int)}</li> 310 * <li>{@link UiScrollable#scrollBackward()}</li> 311 * <li>{@link UiScrollable#scrollForward()}</li> 312 * </ul> 313 * 314 * @param steps indicates the number of injected move steps into the system. Steps are 315 * injected about 5ms apart. So a 100 steps may take about 1/2 second to complete. 316 * @return true if successful 317 * @throws UiObjectNotFoundException 318 * @since API Level 16 319 */ swipeRight(int steps)320 public boolean swipeRight(int steps) throws UiObjectNotFoundException { 321 Tracer.trace(steps); 322 Rect rect = getVisibleBounds(); 323 if(rect.width() <= SWIPE_MARGIN_LIMIT * 2) 324 return false; // too small to swipe 325 return getInteractionController().swipe(rect.left + SWIPE_MARGIN_LIMIT, 326 rect.centerY(), rect.right - SWIPE_MARGIN_LIMIT, rect.centerY(), steps); 327 } 328 329 /** 330 * Finds the visible bounds of a partially visible UI element 331 * 332 * @param node 333 * @return null if node is null, else a Rect containing visible bounds 334 */ getVisibleBounds(AccessibilityNodeInfo node)335 private Rect getVisibleBounds(AccessibilityNodeInfo node) { 336 if (node == null) { 337 return null; 338 } 339 340 // targeted node's bounds 341 int w = UiDevice.getInstance().getDisplayWidth(); 342 int h = UiDevice.getInstance().getDisplayHeight(); 343 Rect nodeRect = AccessibilityNodeInfoHelper.getVisibleBoundsInScreen(node, w, h); 344 345 // is the targeted node within a scrollable container? 346 AccessibilityNodeInfo scrollableParentNode = getScrollableParent(node); 347 if(scrollableParentNode == null) { 348 // nothing to adjust for so return the node's Rect as is 349 return nodeRect; 350 } 351 352 // Scrollable parent's visible bounds 353 Rect parentRect = AccessibilityNodeInfoHelper 354 .getVisibleBoundsInScreen(scrollableParentNode, w, h); 355 // adjust for partial clipping of targeted by parent node if required 356 if (nodeRect.intersect(parentRect)) { 357 return nodeRect; 358 } else { 359 // Node rect has no intersection with parent Rect 360 return new Rect(); 361 } 362 } 363 364 /** 365 * Walks up the layout hierarchy to find a scrollable parent. A scrollable parent 366 * indicates that this node might be in a container where it is partially 367 * visible due to scrolling. In this case, its clickable center might not be visible and 368 * the click coordinates should be adjusted. 369 * 370 * @param node 371 * @return The accessibility node info. 372 */ getScrollableParent(AccessibilityNodeInfo node)373 private AccessibilityNodeInfo getScrollableParent(AccessibilityNodeInfo node) { 374 AccessibilityNodeInfo parent = node; 375 while(parent != null) { 376 parent = parent.getParent(); 377 if (parent != null && parent.isScrollable()) { 378 return parent; 379 } 380 } 381 return null; 382 } 383 384 /** 385 * Performs a click at the center of the visible bounds of the UI element represented 386 * by this UiObject. 387 * 388 * @return true id successful else false 389 * @throws UiObjectNotFoundException 390 * @since API Level 16 391 */ click()392 public boolean click() throws UiObjectNotFoundException { 393 Tracer.trace(); 394 AccessibilityNodeInfo node = findAccessibilityNodeInfo(mConfig.getWaitForSelectorTimeout()); 395 if(node == null) { 396 throw new UiObjectNotFoundException(getSelector().toString()); 397 } 398 Rect rect = getVisibleBounds(node); 399 return getInteractionController().clickAndSync(rect.centerX(), rect.centerY(), 400 mConfig.getActionAcknowledgmentTimeout()); 401 } 402 403 /** 404 * Waits for window transitions that would typically take longer than the 405 * usual default timeouts. 406 * See {@link #clickAndWaitForNewWindow(long)} 407 * 408 * @return true if the event was triggered, else false 409 * @throws UiObjectNotFoundException 410 * @since API Level 16 411 */ clickAndWaitForNewWindow()412 public boolean clickAndWaitForNewWindow() throws UiObjectNotFoundException { 413 Tracer.trace(); 414 return clickAndWaitForNewWindow(WAIT_FOR_WINDOW_TMEOUT); 415 } 416 417 /** 418 * Performs a click at the center of the visible bounds of the UI element represented 419 * by this UiObject and waits for window transitions. 420 * 421 * This method differ from {@link UiObject#click()} only in that this method waits for a 422 * a new window transition as a result of the click. Some examples of a window transition: 423 * <li>launching a new activity</li> 424 * <li>bringing up a pop-up menu</li> 425 * <li>bringing up a dialog</li> 426 * 427 * @param timeout timeout before giving up on waiting for a new window 428 * @return true if the event was triggered, else false 429 * @throws UiObjectNotFoundException 430 * @since API Level 16 431 */ clickAndWaitForNewWindow(long timeout)432 public boolean clickAndWaitForNewWindow(long timeout) throws UiObjectNotFoundException { 433 Tracer.trace(timeout); 434 AccessibilityNodeInfo node = findAccessibilityNodeInfo(mConfig.getWaitForSelectorTimeout()); 435 if(node == null) { 436 throw new UiObjectNotFoundException(getSelector().toString()); 437 } 438 Rect rect = getVisibleBounds(node); 439 return getInteractionController().clickAndWaitForNewWindow(rect.centerX(), rect.centerY(), 440 mConfig.getActionAcknowledgmentTimeout()); 441 } 442 443 /** 444 * Clicks the top and left corner of the UI element 445 * 446 * @return true on success 447 * @throws UiObjectNotFoundException 448 * @since API Level 16 449 */ clickTopLeft()450 public boolean clickTopLeft() throws UiObjectNotFoundException { 451 Tracer.trace(); 452 AccessibilityNodeInfo node = findAccessibilityNodeInfo(mConfig.getWaitForSelectorTimeout()); 453 if(node == null) { 454 throw new UiObjectNotFoundException(getSelector().toString()); 455 } 456 Rect rect = getVisibleBounds(node); 457 return getInteractionController().clickNoSync(rect.left + 5, rect.top + 5); 458 } 459 460 /** 461 * Long clicks bottom and right corner of the UI element 462 * 463 * @return true if operation was successful 464 * @throws UiObjectNotFoundException 465 * @since API Level 16 466 */ longClickBottomRight()467 public boolean longClickBottomRight() throws UiObjectNotFoundException { 468 Tracer.trace(); 469 AccessibilityNodeInfo node = findAccessibilityNodeInfo(mConfig.getWaitForSelectorTimeout()); 470 if(node == null) { 471 throw new UiObjectNotFoundException(getSelector().toString()); 472 } 473 Rect rect = getVisibleBounds(node); 474 return getInteractionController().longTapNoSync(rect.right - 5, rect.bottom - 5); 475 } 476 477 /** 478 * Clicks the bottom and right corner of the UI element 479 * 480 * @return true on success 481 * @throws UiObjectNotFoundException 482 * @since API Level 16 483 */ clickBottomRight()484 public boolean clickBottomRight() throws UiObjectNotFoundException { 485 Tracer.trace(); 486 AccessibilityNodeInfo node = findAccessibilityNodeInfo(mConfig.getWaitForSelectorTimeout()); 487 if(node == null) { 488 throw new UiObjectNotFoundException(getSelector().toString()); 489 } 490 Rect rect = getVisibleBounds(node); 491 return getInteractionController().clickNoSync(rect.right - 5, rect.bottom - 5); 492 } 493 494 /** 495 * Long clicks the center of the visible bounds of the UI element 496 * 497 * @return true if operation was successful 498 * @throws UiObjectNotFoundException 499 * @since API Level 16 500 */ longClick()501 public boolean longClick() throws UiObjectNotFoundException { 502 Tracer.trace(); 503 AccessibilityNodeInfo node = findAccessibilityNodeInfo(mConfig.getWaitForSelectorTimeout()); 504 if(node == null) { 505 throw new UiObjectNotFoundException(getSelector().toString()); 506 } 507 Rect rect = getVisibleBounds(node); 508 return getInteractionController().longTapNoSync(rect.centerX(), rect.centerY()); 509 } 510 511 /** 512 * Long clicks on the top and left corner of the UI element 513 * 514 * @return true if operation was successful 515 * @throws UiObjectNotFoundException 516 * @since API Level 16 517 */ longClickTopLeft()518 public boolean longClickTopLeft() throws UiObjectNotFoundException { 519 Tracer.trace(); 520 AccessibilityNodeInfo node = findAccessibilityNodeInfo(mConfig.getWaitForSelectorTimeout()); 521 if(node == null) { 522 throw new UiObjectNotFoundException(getSelector().toString()); 523 } 524 Rect rect = getVisibleBounds(node); 525 return getInteractionController().longTapNoSync(rect.left + 5, rect.top + 5); 526 } 527 528 /** 529 * Reads the <code>text</code> property of the UI element 530 * 531 * @return text value of the current node represented by this UiObject 532 * @throws UiObjectNotFoundException if no match could be found 533 * @since API Level 16 534 */ getText()535 public String getText() throws UiObjectNotFoundException { 536 Tracer.trace(); 537 AccessibilityNodeInfo node = findAccessibilityNodeInfo(mConfig.getWaitForSelectorTimeout()); 538 if(node == null) { 539 throw new UiObjectNotFoundException(getSelector().toString()); 540 } 541 String retVal = safeStringReturn(node.getText()); 542 Log.d(LOG_TAG, String.format("getText() = %s", retVal)); 543 return retVal; 544 } 545 546 /** 547 * Retrieves the <code>className</code> property of the UI element. 548 * 549 * @return class name of the current node represented by this UiObject 550 * @throws UiObjectNotFoundException if no match was found 551 * @since API Level 18 552 */ getClassName()553 public String getClassName() throws UiObjectNotFoundException { 554 Tracer.trace(); 555 AccessibilityNodeInfo node = findAccessibilityNodeInfo(mConfig.getWaitForSelectorTimeout()); 556 if(node == null) { 557 throw new UiObjectNotFoundException(getSelector().toString()); 558 } 559 String retVal = safeStringReturn(node.getClassName()); 560 Log.d(LOG_TAG, String.format("getClassName() = %s", retVal)); 561 return retVal; 562 } 563 564 /** 565 * Reads the <code>content_desc</code> property of the UI element 566 * 567 * @return value of node attribute "content_desc" 568 * @throws UiObjectNotFoundException 569 * @since API Level 16 570 */ getContentDescription()571 public String getContentDescription() throws UiObjectNotFoundException { 572 Tracer.trace(); 573 AccessibilityNodeInfo node = findAccessibilityNodeInfo(mConfig.getWaitForSelectorTimeout()); 574 if(node == null) { 575 throw new UiObjectNotFoundException(getSelector().toString()); 576 } 577 return safeStringReturn(node.getContentDescription()); 578 } 579 580 /** 581 * Sets the text in an editable field, after clearing the field's content. 582 * 583 * The {@link UiSelector} selector of this object must reference a UI element that is editable. 584 * 585 * When you call this method, the method first simulates a {@link #click()} on 586 * editable field to set focus. The method then clears the field's contents 587 * and injects your specified text into the field. 588 * 589 * If you want to capture the original contents of the field, call {@link #getText()} first. 590 * You can then modify the text and use this method to update the field. 591 * 592 * @param text string to set 593 * @return true if operation is successful 594 * @throws UiObjectNotFoundException 595 * @since API Level 16 596 */ setText(String text)597 public boolean setText(String text) throws UiObjectNotFoundException { 598 Tracer.trace(text); 599 clearTextField(); 600 return getInteractionController().sendText(text); 601 } 602 603 /** 604 * Clears the existing text contents in an editable field. 605 * 606 * The {@link UiSelector} of this object must reference a UI element that is editable. 607 * 608 * When you call this method, the method first sets focus at the start edge of the field. 609 * The method then simulates a long-press to select the existing text, and deletes the 610 * selected text. 611 * 612 * If a "Select-All" option is displayed, the method will automatically attempt to use it 613 * to ensure full text selection. 614 * 615 * Note that it is possible that not all the text in the field is selected; for example, 616 * if the text contains separators such as spaces, slashes, at symbol etc. 617 * Also, not all editable fields support the long-press functionality. 618 * 619 * @throws UiObjectNotFoundException 620 * @since API Level 16 621 */ clearTextField()622 public void clearTextField() throws UiObjectNotFoundException { 623 Tracer.trace(); 624 // long click left + center 625 AccessibilityNodeInfo node = findAccessibilityNodeInfo(mConfig.getWaitForSelectorTimeout()); 626 if(node == null) { 627 throw new UiObjectNotFoundException(getSelector().toString()); 628 } 629 Rect rect = getVisibleBounds(node); 630 getInteractionController().longTapNoSync(rect.left + 20, rect.centerY()); 631 // check if the edit menu is open 632 UiObject selectAll = new UiObject(new UiSelector().descriptionContains("Select all")); 633 if(selectAll.waitForExists(50)) 634 selectAll.click(); 635 // wait for the selection 636 SystemClock.sleep(250); 637 // delete it 638 getInteractionController().sendKey(KeyEvent.KEYCODE_DEL, 0); 639 } 640 641 /** 642 * Check if the UI element's <code>checked</code> property is currently true 643 * 644 * @return true if it is else false 645 * @since API Level 16 646 */ isChecked()647 public boolean isChecked() throws UiObjectNotFoundException { 648 Tracer.trace(); 649 AccessibilityNodeInfo node = findAccessibilityNodeInfo(mConfig.getWaitForSelectorTimeout()); 650 if(node == null) { 651 throw new UiObjectNotFoundException(getSelector().toString()); 652 } 653 return node.isChecked(); 654 } 655 656 /** 657 * Checks if the UI element's <code>selected</code> property is currently true. 658 * 659 * @return true if it is else false 660 * @throws UiObjectNotFoundException 661 * @since API Level 16 662 */ isSelected()663 public boolean isSelected() throws UiObjectNotFoundException { 664 Tracer.trace(); 665 AccessibilityNodeInfo node = findAccessibilityNodeInfo(mConfig.getWaitForSelectorTimeout()); 666 if(node == null) { 667 throw new UiObjectNotFoundException(getSelector().toString()); 668 } 669 return node.isSelected(); 670 } 671 672 /** 673 * Checks if the UI element's <code>checkable</code> property is currently true. 674 * 675 * @return true if it is else false 676 * @throws UiObjectNotFoundException 677 * @since API Level 16 678 */ isCheckable()679 public boolean isCheckable() throws UiObjectNotFoundException { 680 Tracer.trace(); 681 AccessibilityNodeInfo node = findAccessibilityNodeInfo(mConfig.getWaitForSelectorTimeout()); 682 if(node == null) { 683 throw new UiObjectNotFoundException(getSelector().toString()); 684 } 685 return node.isCheckable(); 686 } 687 688 /** 689 * Checks if the UI element's <code>enabled</code> property is currently true. 690 * 691 * @return true if it is else false 692 * @throws UiObjectNotFoundException 693 * @since API Level 16 694 */ isEnabled()695 public boolean isEnabled() throws UiObjectNotFoundException { 696 Tracer.trace(); 697 AccessibilityNodeInfo node = findAccessibilityNodeInfo(mConfig.getWaitForSelectorTimeout()); 698 if(node == null) { 699 throw new UiObjectNotFoundException(getSelector().toString()); 700 } 701 return node.isEnabled(); 702 } 703 704 /** 705 * Checks if the UI element's <code>clickable</code> property is currently true. 706 * 707 * @return true if it is else false 708 * @throws UiObjectNotFoundException 709 * @since API Level 16 710 */ isClickable()711 public boolean isClickable() throws UiObjectNotFoundException { 712 Tracer.trace(); 713 AccessibilityNodeInfo node = findAccessibilityNodeInfo(mConfig.getWaitForSelectorTimeout()); 714 if(node == null) { 715 throw new UiObjectNotFoundException(getSelector().toString()); 716 } 717 return node.isClickable(); 718 } 719 720 /** 721 * Check if the UI element's <code>focused</code> property is currently true 722 * 723 * @return true if it is else false 724 * @throws UiObjectNotFoundException 725 * @since API Level 16 726 */ isFocused()727 public boolean isFocused() throws UiObjectNotFoundException { 728 Tracer.trace(); 729 AccessibilityNodeInfo node = findAccessibilityNodeInfo(mConfig.getWaitForSelectorTimeout()); 730 if(node == null) { 731 throw new UiObjectNotFoundException(getSelector().toString()); 732 } 733 return node.isFocused(); 734 } 735 736 /** 737 * Check if the UI element's <code>focusable</code> property is currently true. 738 * 739 * @return true if it is else false 740 * @throws UiObjectNotFoundException 741 * @since API Level 16 742 */ isFocusable()743 public boolean isFocusable() throws UiObjectNotFoundException { 744 Tracer.trace(); 745 AccessibilityNodeInfo node = findAccessibilityNodeInfo(mConfig.getWaitForSelectorTimeout()); 746 if(node == null) { 747 throw new UiObjectNotFoundException(getSelector().toString()); 748 } 749 return node.isFocusable(); 750 } 751 752 /** 753 * Check if the view's <code>scrollable</code> property is currently true 754 * 755 * @return true if it is else false 756 * @throws UiObjectNotFoundException 757 * @since API Level 16 758 */ isScrollable()759 public boolean isScrollable() throws UiObjectNotFoundException { 760 Tracer.trace(); 761 AccessibilityNodeInfo node = findAccessibilityNodeInfo(mConfig.getWaitForSelectorTimeout()); 762 if(node == null) { 763 throw new UiObjectNotFoundException(getSelector().toString()); 764 } 765 return node.isScrollable(); 766 } 767 768 /** 769 * Check if the view's <code>long-clickable</code> property is currently true 770 * 771 * @return true if it is else false 772 * @throws UiObjectNotFoundException 773 * @since API Level 16 774 */ isLongClickable()775 public boolean isLongClickable() throws UiObjectNotFoundException { 776 Tracer.trace(); 777 AccessibilityNodeInfo node = findAccessibilityNodeInfo(mConfig.getWaitForSelectorTimeout()); 778 if(node == null) { 779 throw new UiObjectNotFoundException(getSelector().toString()); 780 } 781 return node.isLongClickable(); 782 } 783 784 /** 785 * Reads the view's <code>package</code> property 786 * 787 * @return true if it is else false 788 * @throws UiObjectNotFoundException 789 * @since API Level 16 790 */ getPackageName()791 public String getPackageName() throws UiObjectNotFoundException { 792 Tracer.trace(); 793 AccessibilityNodeInfo node = findAccessibilityNodeInfo(mConfig.getWaitForSelectorTimeout()); 794 if(node == null) { 795 throw new UiObjectNotFoundException(getSelector().toString()); 796 } 797 return safeStringReturn(node.getPackageName()); 798 } 799 800 /** 801 * Returns the visible bounds of the view. 802 * 803 * If a portion of the view is visible, only the bounds of the visible portion are 804 * reported. 805 * 806 * @return Rect 807 * @throws UiObjectNotFoundException 808 * @see {@link #getBounds()} 809 * @since API Level 17 810 */ getVisibleBounds()811 public Rect getVisibleBounds() throws UiObjectNotFoundException { 812 Tracer.trace(); 813 AccessibilityNodeInfo node = findAccessibilityNodeInfo(mConfig.getWaitForSelectorTimeout()); 814 if(node == null) { 815 throw new UiObjectNotFoundException(getSelector().toString()); 816 } 817 return getVisibleBounds(node); 818 } 819 820 /** 821 * Returns the view's <code>bounds</code> property. See {@link #getVisibleBounds()} 822 * 823 * @return Rect 824 * @throws UiObjectNotFoundException 825 * @since API Level 16 826 */ getBounds()827 public Rect getBounds() throws UiObjectNotFoundException { 828 Tracer.trace(); 829 AccessibilityNodeInfo node = findAccessibilityNodeInfo(mConfig.getWaitForSelectorTimeout()); 830 if(node == null) { 831 throw new UiObjectNotFoundException(getSelector().toString()); 832 } 833 Rect nodeRect = new Rect(); 834 node.getBoundsInScreen(nodeRect); 835 836 return nodeRect; 837 } 838 839 /** 840 * Waits a specified length of time for a view to become visible. 841 * 842 * This method waits until the view becomes visible on the display, or 843 * until the timeout has elapsed. You can use this method in situations where 844 * the content that you want to select is not immediately displayed. 845 * 846 * @param timeout the amount of time to wait (in milliseconds) 847 * @return true if the view is displayed, else false if timeout elapsed while waiting 848 * @since API Level 16 849 */ waitForExists(long timeout)850 public boolean waitForExists(long timeout) { 851 Tracer.trace(timeout); 852 if(findAccessibilityNodeInfo(timeout) != null) { 853 return true; 854 } 855 return false; 856 } 857 858 /** 859 * Waits a specified length of time for a view to become undetectable. 860 * 861 * This method waits until a view is no longer matchable, or until the 862 * timeout has elapsed. 863 * 864 * A view becomes undetectable when the {@link UiSelector} of the object is 865 * unable to find a match because the element has either changed its state or is no 866 * longer displayed. 867 * 868 * You can use this method when attempting to wait for some long operation 869 * to compete, such as downloading a large file or connecting to a remote server. 870 * 871 * @param timeout time to wait (in milliseconds) 872 * @return true if the element is gone before timeout elapsed, else false if timeout elapsed 873 * but a matching element is still found. 874 * @since API Level 16 875 */ waitUntilGone(long timeout)876 public boolean waitUntilGone(long timeout) { 877 Tracer.trace(timeout); 878 long startMills = SystemClock.uptimeMillis(); 879 long currentMills = 0; 880 while (currentMills <= timeout) { 881 if(findAccessibilityNodeInfo(0) == null) 882 return true; 883 currentMills = SystemClock.uptimeMillis() - startMills; 884 if(timeout > 0) 885 SystemClock.sleep(WAIT_FOR_SELECTOR_POLL); 886 } 887 return false; 888 } 889 890 /** 891 * Check if view exists. 892 * 893 * This methods performs a {@link #waitForExists(long)} with zero timeout. This 894 * basically returns immediately whether the view represented by this UiObject 895 * exists or not. If you need to wait longer for this view, then see 896 * {@link #waitForExists(long)}. 897 * 898 * @return true if the view represented by this UiObject does exist 899 * @since API Level 16 900 */ exists()901 public boolean exists() { 902 Tracer.trace(); 903 return waitForExists(0); 904 } 905 safeStringReturn(CharSequence cs)906 private String safeStringReturn(CharSequence cs) { 907 if(cs == null) 908 return ""; 909 return cs.toString(); 910 } 911 912 /** 913 * Performs a two-pointer gesture, where each pointer moves diagonally 914 * opposite across the other, from the center out towards the edges of the 915 * this UiObject. 916 * @param percent percentage of the object's diagonal length for the pinch gesture 917 * @param steps the number of steps for the gesture. Steps are injected 918 * about 5 milliseconds apart, so 100 steps may take around 0.5 seconds to complete. 919 * @return <code>true</code> if all touch events for this gesture are injected successfully, 920 * <code>false</code> otherwise 921 * @throws UiObjectNotFoundException 922 * @since API Level 18 923 */ pinchOut(int percent, int steps)924 public boolean pinchOut(int percent, int steps) throws UiObjectNotFoundException { 925 // make value between 1 and 100 926 percent = (percent < 0) ? 1 : (percent > 100) ? 100 : percent; 927 float percentage = percent / 100f; 928 929 AccessibilityNodeInfo node = findAccessibilityNodeInfo(mConfig.getWaitForSelectorTimeout()); 930 if (node == null) { 931 throw new UiObjectNotFoundException(getSelector().toString()); 932 } 933 934 Rect rect = getVisibleBounds(node); 935 if (rect.width() <= FINGER_TOUCH_HALF_WIDTH * 2) 936 throw new IllegalStateException("Object width is too small for operation"); 937 938 // start from the same point at the center of the control 939 Point startPoint1 = new Point(rect.centerX() - FINGER_TOUCH_HALF_WIDTH, rect.centerY()); 940 Point startPoint2 = new Point(rect.centerX() + FINGER_TOUCH_HALF_WIDTH, rect.centerY()); 941 942 // End at the top-left and bottom-right corners of the control 943 Point endPoint1 = new Point(rect.centerX() - (int)((rect.width()/2) * percentage), 944 rect.centerY()); 945 Point endPoint2 = new Point(rect.centerX() + (int)((rect.width()/2) * percentage), 946 rect.centerY()); 947 948 return performTwoPointerGesture(startPoint1, startPoint2, endPoint1, endPoint2, steps); 949 } 950 951 /** 952 * Performs a two-pointer gesture, where each pointer moves diagonally 953 * toward the other, from the edges to the center of this UiObject . 954 * @param percent percentage of the object's diagonal length for the pinch gesture 955 * @param steps the number of steps for the gesture. Steps are injected 956 * about 5 milliseconds apart, so 100 steps may take around 0.5 seconds to complete. 957 * @return <code>true</code> if all touch events for this gesture are injected successfully, 958 * <code>false</code> otherwise 959 * @throws UiObjectNotFoundException 960 * @since API Level 18 961 */ pinchIn(int percent, int steps)962 public boolean pinchIn(int percent, int steps) throws UiObjectNotFoundException { 963 // make value between 1 and 100 964 percent = (percent < 0) ? 0 : (percent > 100) ? 100 : percent; 965 float percentage = percent / 100f; 966 967 AccessibilityNodeInfo node = findAccessibilityNodeInfo(mConfig.getWaitForSelectorTimeout()); 968 if (node == null) { 969 throw new UiObjectNotFoundException(getSelector().toString()); 970 } 971 972 Rect rect = getVisibleBounds(node); 973 if (rect.width() <= FINGER_TOUCH_HALF_WIDTH * 2) 974 throw new IllegalStateException("Object width is too small for operation"); 975 976 Point startPoint1 = new Point(rect.centerX() - (int)((rect.width()/2) * percentage), 977 rect.centerY()); 978 Point startPoint2 = new Point(rect.centerX() + (int)((rect.width()/2) * percentage), 979 rect.centerY()); 980 981 Point endPoint1 = new Point(rect.centerX() - FINGER_TOUCH_HALF_WIDTH, rect.centerY()); 982 Point endPoint2 = new Point(rect.centerX() + FINGER_TOUCH_HALF_WIDTH, rect.centerY()); 983 984 return performTwoPointerGesture(startPoint1, startPoint2, endPoint1, endPoint2, steps); 985 } 986 987 /** 988 * Generates a two-pointer gesture with arbitrary starting and ending points. 989 * 990 * @param startPoint1 start point of pointer 1 991 * @param startPoint2 start point of pointer 2 992 * @param endPoint1 end point of pointer 1 993 * @param endPoint2 end point of pointer 2 994 * @param steps the number of steps for the gesture. Steps are injected 995 * about 5 milliseconds apart, so 100 steps may take around 0.5 seconds to complete. 996 * @return <code>true</code> if all touch events for this gesture are injected successfully, 997 * <code>false</code> otherwise 998 * @since API Level 18 999 */ performTwoPointerGesture(Point startPoint1, Point startPoint2, Point endPoint1, Point endPoint2, int steps)1000 public boolean performTwoPointerGesture(Point startPoint1, Point startPoint2, Point endPoint1, 1001 Point endPoint2, int steps) { 1002 1003 // avoid a divide by zero 1004 if(steps == 0) 1005 steps = 1; 1006 1007 final float stepX1 = (endPoint1.x - startPoint1.x) / steps; 1008 final float stepY1 = (endPoint1.y - startPoint1.y) / steps; 1009 final float stepX2 = (endPoint2.x - startPoint2.x) / steps; 1010 final float stepY2 = (endPoint2.y - startPoint2.y) / steps; 1011 1012 int eventX1, eventY1, eventX2, eventY2; 1013 eventX1 = startPoint1.x; 1014 eventY1 = startPoint1.y; 1015 eventX2 = startPoint2.x; 1016 eventY2 = startPoint2.y; 1017 1018 // allocate for steps plus first down and last up 1019 PointerCoords[] points1 = new PointerCoords[steps + 2]; 1020 PointerCoords[] points2 = new PointerCoords[steps + 2]; 1021 1022 // Include the first and last touch downs in the arrays of steps 1023 for (int i = 0; i < steps + 1; i++) { 1024 PointerCoords p1 = new PointerCoords(); 1025 p1.x = eventX1; 1026 p1.y = eventY1; 1027 p1.pressure = 1; 1028 p1.size = 1; 1029 points1[i] = p1; 1030 1031 PointerCoords p2 = new PointerCoords(); 1032 p2.x = eventX2; 1033 p2.y = eventY2; 1034 p2.pressure = 1; 1035 p2.size = 1; 1036 points2[i] = p2; 1037 1038 eventX1 += stepX1; 1039 eventY1 += stepY1; 1040 eventX2 += stepX2; 1041 eventY2 += stepY2; 1042 } 1043 1044 // ending pointers coordinates 1045 PointerCoords p1 = new PointerCoords(); 1046 p1.x = endPoint1.x; 1047 p1.y = endPoint1.y; 1048 p1.pressure = 1; 1049 p1.size = 1; 1050 points1[steps + 1] = p1; 1051 1052 PointerCoords p2 = new PointerCoords(); 1053 p2.x = endPoint2.x; 1054 p2.y = endPoint2.y; 1055 p2.pressure = 1; 1056 p2.size = 1; 1057 points2[steps + 1] = p2; 1058 1059 return performMultiPointerGesture(points1, points2); 1060 } 1061 1062 /** 1063 * Performs a multi-touch gesture. You must specify touch coordinates for 1064 * at least 2 pointers. Each pointer must have all of its touch steps 1065 * defined in an array of {@link PointerCoords}. You can use this method to 1066 * specify complex gestures, like circles and irregular shapes, where each 1067 * pointer may take a different path. 1068 * 1069 * To create a single point on a pointer's touch path: 1070 * <code> 1071 * PointerCoords p = new PointerCoords(); 1072 * p.x = stepX; 1073 * p.y = stepY; 1074 * p.pressure = 1; 1075 * p.size = 1; 1076 * </code> 1077 * @param touches represents the pointers' paths. Each {@link PointerCoords} 1078 * array represents a different pointer. Each {@link PointerCoords} in an 1079 * array element represents a touch point on a pointer's path. 1080 * @return <code>true</code> if all touch events for this gesture are injected successfully, 1081 * <code>false</code> otherwise 1082 * @since API Level 18 1083 */ performMultiPointerGesture(PointerCoords[] ....touches)1084 public boolean performMultiPointerGesture(PointerCoords[] ...touches) { 1085 return getInteractionController().performMultiPointerGesture(touches); 1086 } 1087 } 1088