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 nodeRect.intersect(parentRect); 357 return nodeRect; 358 } 359 360 /** 361 * Walks up the layout hierarchy to find a scrollable parent. A scrollable parent 362 * indicates that this node might be in a container where it is partially 363 * visible due to scrolling. In this case, its clickable center might not be visible and 364 * the click coordinates should be adjusted. 365 * 366 * @param node 367 * @return The accessibility node info. 368 */ getScrollableParent(AccessibilityNodeInfo node)369 private AccessibilityNodeInfo getScrollableParent(AccessibilityNodeInfo node) { 370 AccessibilityNodeInfo parent = node; 371 while(parent != null) { 372 parent = parent.getParent(); 373 if (parent != null && parent.isScrollable()) { 374 return parent; 375 } 376 } 377 return null; 378 } 379 380 /** 381 * Performs a click at the center of the visible bounds of the UI element represented 382 * by this UiObject. 383 * 384 * @return true id successful else false 385 * @throws UiObjectNotFoundException 386 * @since API Level 16 387 */ click()388 public boolean click() throws UiObjectNotFoundException { 389 Tracer.trace(); 390 AccessibilityNodeInfo node = findAccessibilityNodeInfo(mConfig.getWaitForSelectorTimeout()); 391 if(node == null) { 392 throw new UiObjectNotFoundException(getSelector().toString()); 393 } 394 Rect rect = getVisibleBounds(node); 395 return getInteractionController().clickAndSync(rect.centerX(), rect.centerY(), 396 mConfig.getActionAcknowledgmentTimeout()); 397 } 398 399 /** 400 * Waits for window transitions that would typically take longer than the 401 * usual default timeouts. 402 * See {@link #clickAndWaitForNewWindow(long)} 403 * 404 * @return true if the event was triggered, else false 405 * @throws UiObjectNotFoundException 406 * @since API Level 16 407 */ clickAndWaitForNewWindow()408 public boolean clickAndWaitForNewWindow() throws UiObjectNotFoundException { 409 Tracer.trace(); 410 return clickAndWaitForNewWindow(WAIT_FOR_WINDOW_TMEOUT); 411 } 412 413 /** 414 * Performs a click at the center of the visible bounds of the UI element represented 415 * by this UiObject and waits for window transitions. 416 * 417 * This method differ from {@link UiObject#click()} only in that this method waits for a 418 * a new window transition as a result of the click. Some examples of a window transition: 419 * <li>launching a new activity</li> 420 * <li>bringing up a pop-up menu</li> 421 * <li>bringing up a dialog</li> 422 * 423 * @param timeout timeout before giving up on waiting for a new window 424 * @return true if the event was triggered, else false 425 * @throws UiObjectNotFoundException 426 * @since API Level 16 427 */ clickAndWaitForNewWindow(long timeout)428 public boolean clickAndWaitForNewWindow(long timeout) throws UiObjectNotFoundException { 429 Tracer.trace(timeout); 430 AccessibilityNodeInfo node = findAccessibilityNodeInfo(mConfig.getWaitForSelectorTimeout()); 431 if(node == null) { 432 throw new UiObjectNotFoundException(getSelector().toString()); 433 } 434 Rect rect = getVisibleBounds(node); 435 return getInteractionController().clickAndWaitForNewWindow(rect.centerX(), rect.centerY(), 436 mConfig.getActionAcknowledgmentTimeout()); 437 } 438 439 /** 440 * Clicks the top and left corner of the UI element 441 * 442 * @return true on success 443 * @throws UiObjectNotFoundException 444 * @since API Level 16 445 */ clickTopLeft()446 public boolean clickTopLeft() throws UiObjectNotFoundException { 447 Tracer.trace(); 448 AccessibilityNodeInfo node = findAccessibilityNodeInfo(mConfig.getWaitForSelectorTimeout()); 449 if(node == null) { 450 throw new UiObjectNotFoundException(getSelector().toString()); 451 } 452 Rect rect = getVisibleBounds(node); 453 return getInteractionController().clickNoSync(rect.left + 5, rect.top + 5); 454 } 455 456 /** 457 * Long clicks bottom and right corner of the UI element 458 * 459 * @return true if operation was successful 460 * @throws UiObjectNotFoundException 461 * @since API Level 16 462 */ longClickBottomRight()463 public boolean longClickBottomRight() throws UiObjectNotFoundException { 464 Tracer.trace(); 465 AccessibilityNodeInfo node = findAccessibilityNodeInfo(mConfig.getWaitForSelectorTimeout()); 466 if(node == null) { 467 throw new UiObjectNotFoundException(getSelector().toString()); 468 } 469 Rect rect = getVisibleBounds(node); 470 return getInteractionController().longTapNoSync(rect.right - 5, rect.bottom - 5); 471 } 472 473 /** 474 * Clicks the bottom and right corner of the UI element 475 * 476 * @return true on success 477 * @throws UiObjectNotFoundException 478 * @since API Level 16 479 */ clickBottomRight()480 public boolean clickBottomRight() throws UiObjectNotFoundException { 481 Tracer.trace(); 482 AccessibilityNodeInfo node = findAccessibilityNodeInfo(mConfig.getWaitForSelectorTimeout()); 483 if(node == null) { 484 throw new UiObjectNotFoundException(getSelector().toString()); 485 } 486 Rect rect = getVisibleBounds(node); 487 return getInteractionController().clickNoSync(rect.right - 5, rect.bottom - 5); 488 } 489 490 /** 491 * Long clicks the center of the visible bounds of the UI element 492 * 493 * @return true if operation was successful 494 * @throws UiObjectNotFoundException 495 * @since API Level 16 496 */ longClick()497 public boolean longClick() throws UiObjectNotFoundException { 498 Tracer.trace(); 499 AccessibilityNodeInfo node = findAccessibilityNodeInfo(mConfig.getWaitForSelectorTimeout()); 500 if(node == null) { 501 throw new UiObjectNotFoundException(getSelector().toString()); 502 } 503 Rect rect = getVisibleBounds(node); 504 return getInteractionController().longTapNoSync(rect.centerX(), rect.centerY()); 505 } 506 507 /** 508 * Long clicks on the top and left corner of the UI element 509 * 510 * @return true if operation was successful 511 * @throws UiObjectNotFoundException 512 * @since API Level 16 513 */ longClickTopLeft()514 public boolean longClickTopLeft() throws UiObjectNotFoundException { 515 Tracer.trace(); 516 AccessibilityNodeInfo node = findAccessibilityNodeInfo(mConfig.getWaitForSelectorTimeout()); 517 if(node == null) { 518 throw new UiObjectNotFoundException(getSelector().toString()); 519 } 520 Rect rect = getVisibleBounds(node); 521 return getInteractionController().longTapNoSync(rect.left + 5, rect.top + 5); 522 } 523 524 /** 525 * Reads the <code>text</code> property of the UI element 526 * 527 * @return text value of the current node represented by this UiObject 528 * @throws UiObjectNotFoundException if no match could be found 529 * @since API Level 16 530 */ getText()531 public String getText() throws UiObjectNotFoundException { 532 Tracer.trace(); 533 AccessibilityNodeInfo node = findAccessibilityNodeInfo(mConfig.getWaitForSelectorTimeout()); 534 if(node == null) { 535 throw new UiObjectNotFoundException(getSelector().toString()); 536 } 537 String retVal = safeStringReturn(node.getText()); 538 Log.d(LOG_TAG, String.format("getText() = %s", retVal)); 539 return retVal; 540 } 541 542 /** 543 * Retrieves the <code>className</code> property of the UI element. 544 * 545 * @return class name of the current node represented by this UiObject 546 * @throws UiObjectNotFoundException if no match was found 547 * @since API Level 18 548 */ getClassName()549 public String getClassName() throws UiObjectNotFoundException { 550 Tracer.trace(); 551 AccessibilityNodeInfo node = findAccessibilityNodeInfo(mConfig.getWaitForSelectorTimeout()); 552 if(node == null) { 553 throw new UiObjectNotFoundException(getSelector().toString()); 554 } 555 String retVal = safeStringReturn(node.getClassName()); 556 Log.d(LOG_TAG, String.format("getClassName() = %s", retVal)); 557 return retVal; 558 } 559 560 /** 561 * Reads the <code>content_desc</code> property of the UI element 562 * 563 * @return value of node attribute "content_desc" 564 * @throws UiObjectNotFoundException 565 * @since API Level 16 566 */ getContentDescription()567 public String getContentDescription() throws UiObjectNotFoundException { 568 Tracer.trace(); 569 AccessibilityNodeInfo node = findAccessibilityNodeInfo(mConfig.getWaitForSelectorTimeout()); 570 if(node == null) { 571 throw new UiObjectNotFoundException(getSelector().toString()); 572 } 573 return safeStringReturn(node.getContentDescription()); 574 } 575 576 /** 577 * Sets the text in an editable field, after clearing the field's content. 578 * 579 * The {@link UiSelector} selector of this object must reference a UI element that is editable. 580 * 581 * When you call this method, the method first simulates a {@link #click()} on 582 * editable field to set focus. The method then clears the field's contents 583 * and injects your specified text into the field. 584 * 585 * If you want to capture the original contents of the field, call {@link #getText()} first. 586 * You can then modify the text and use this method to update the field. 587 * 588 * @param text string to set 589 * @return true if operation is successful 590 * @throws UiObjectNotFoundException 591 * @since API Level 16 592 */ setText(String text)593 public boolean setText(String text) throws UiObjectNotFoundException { 594 Tracer.trace(text); 595 clearTextField(); 596 return getInteractionController().sendText(text); 597 } 598 599 /** 600 * Clears the existing text contents in an editable field. 601 * 602 * The {@link UiSelector} of this object must reference a UI element that is editable. 603 * 604 * When you call this method, the method first sets focus at the start edge of the field. 605 * The method then simulates a long-press to select the existing text, and deletes the 606 * selected text. 607 * 608 * If a "Select-All" option is displayed, the method will automatically attempt to use it 609 * to ensure full text selection. 610 * 611 * Note that it is possible that not all the text in the field is selected; for example, 612 * if the text contains separators such as spaces, slashes, at symbol etc. 613 * Also, not all editable fields support the long-press functionality. 614 * 615 * @throws UiObjectNotFoundException 616 * @since API Level 16 617 */ clearTextField()618 public void clearTextField() throws UiObjectNotFoundException { 619 Tracer.trace(); 620 // long click left + center 621 AccessibilityNodeInfo node = findAccessibilityNodeInfo(mConfig.getWaitForSelectorTimeout()); 622 if(node == null) { 623 throw new UiObjectNotFoundException(getSelector().toString()); 624 } 625 Rect rect = getVisibleBounds(node); 626 getInteractionController().longTapNoSync(rect.left + 20, rect.centerY()); 627 // check if the edit menu is open 628 UiObject selectAll = new UiObject(new UiSelector().descriptionContains("Select all")); 629 if(selectAll.waitForExists(50)) 630 selectAll.click(); 631 // wait for the selection 632 SystemClock.sleep(250); 633 // delete it 634 getInteractionController().sendKey(KeyEvent.KEYCODE_DEL, 0); 635 } 636 637 /** 638 * Check if the UI element's <code>checked</code> property is currently true 639 * 640 * @return true if it is else false 641 * @since API Level 16 642 */ isChecked()643 public boolean isChecked() throws UiObjectNotFoundException { 644 Tracer.trace(); 645 AccessibilityNodeInfo node = findAccessibilityNodeInfo(mConfig.getWaitForSelectorTimeout()); 646 if(node == null) { 647 throw new UiObjectNotFoundException(getSelector().toString()); 648 } 649 return node.isChecked(); 650 } 651 652 /** 653 * Checks if the UI element's <code>selected</code> property is currently true. 654 * 655 * @return true if it is else false 656 * @throws UiObjectNotFoundException 657 * @since API Level 16 658 */ isSelected()659 public boolean isSelected() throws UiObjectNotFoundException { 660 Tracer.trace(); 661 AccessibilityNodeInfo node = findAccessibilityNodeInfo(mConfig.getWaitForSelectorTimeout()); 662 if(node == null) { 663 throw new UiObjectNotFoundException(getSelector().toString()); 664 } 665 return node.isSelected(); 666 } 667 668 /** 669 * Checks if the UI element's <code>checkable</code> property is currently true. 670 * 671 * @return true if it is else false 672 * @throws UiObjectNotFoundException 673 * @since API Level 16 674 */ isCheckable()675 public boolean isCheckable() throws UiObjectNotFoundException { 676 Tracer.trace(); 677 AccessibilityNodeInfo node = findAccessibilityNodeInfo(mConfig.getWaitForSelectorTimeout()); 678 if(node == null) { 679 throw new UiObjectNotFoundException(getSelector().toString()); 680 } 681 return node.isCheckable(); 682 } 683 684 /** 685 * Checks if the UI element's <code>enabled</code> property is currently true. 686 * 687 * @return true if it is else false 688 * @throws UiObjectNotFoundException 689 * @since API Level 16 690 */ isEnabled()691 public boolean isEnabled() throws UiObjectNotFoundException { 692 Tracer.trace(); 693 AccessibilityNodeInfo node = findAccessibilityNodeInfo(mConfig.getWaitForSelectorTimeout()); 694 if(node == null) { 695 throw new UiObjectNotFoundException(getSelector().toString()); 696 } 697 return node.isEnabled(); 698 } 699 700 /** 701 * Checks if the UI element's <code>clickable</code> property is currently true. 702 * 703 * @return true if it is else false 704 * @throws UiObjectNotFoundException 705 * @since API Level 16 706 */ isClickable()707 public boolean isClickable() throws UiObjectNotFoundException { 708 Tracer.trace(); 709 AccessibilityNodeInfo node = findAccessibilityNodeInfo(mConfig.getWaitForSelectorTimeout()); 710 if(node == null) { 711 throw new UiObjectNotFoundException(getSelector().toString()); 712 } 713 return node.isClickable(); 714 } 715 716 /** 717 * Check if the UI element's <code>focused</code> property is currently true 718 * 719 * @return true if it is else false 720 * @throws UiObjectNotFoundException 721 * @since API Level 16 722 */ isFocused()723 public boolean isFocused() throws UiObjectNotFoundException { 724 Tracer.trace(); 725 AccessibilityNodeInfo node = findAccessibilityNodeInfo(mConfig.getWaitForSelectorTimeout()); 726 if(node == null) { 727 throw new UiObjectNotFoundException(getSelector().toString()); 728 } 729 return node.isFocused(); 730 } 731 732 /** 733 * Check if the UI element's <code>focusable</code> property is currently true. 734 * 735 * @return true if it is else false 736 * @throws UiObjectNotFoundException 737 * @since API Level 16 738 */ isFocusable()739 public boolean isFocusable() throws UiObjectNotFoundException { 740 Tracer.trace(); 741 AccessibilityNodeInfo node = findAccessibilityNodeInfo(mConfig.getWaitForSelectorTimeout()); 742 if(node == null) { 743 throw new UiObjectNotFoundException(getSelector().toString()); 744 } 745 return node.isFocusable(); 746 } 747 748 /** 749 * Check if the view's <code>scrollable</code> property is currently true 750 * 751 * @return true if it is else false 752 * @throws UiObjectNotFoundException 753 * @since API Level 16 754 */ isScrollable()755 public boolean isScrollable() throws UiObjectNotFoundException { 756 Tracer.trace(); 757 AccessibilityNodeInfo node = findAccessibilityNodeInfo(mConfig.getWaitForSelectorTimeout()); 758 if(node == null) { 759 throw new UiObjectNotFoundException(getSelector().toString()); 760 } 761 return node.isScrollable(); 762 } 763 764 /** 765 * Check if the view's <code>long-clickable</code> property is currently true 766 * 767 * @return true if it is else false 768 * @throws UiObjectNotFoundException 769 * @since API Level 16 770 */ isLongClickable()771 public boolean isLongClickable() throws UiObjectNotFoundException { 772 Tracer.trace(); 773 AccessibilityNodeInfo node = findAccessibilityNodeInfo(mConfig.getWaitForSelectorTimeout()); 774 if(node == null) { 775 throw new UiObjectNotFoundException(getSelector().toString()); 776 } 777 return node.isLongClickable(); 778 } 779 780 /** 781 * Reads the view's <code>package</code> property 782 * 783 * @return true if it is else false 784 * @throws UiObjectNotFoundException 785 * @since API Level 16 786 */ getPackageName()787 public String getPackageName() throws UiObjectNotFoundException { 788 Tracer.trace(); 789 AccessibilityNodeInfo node = findAccessibilityNodeInfo(mConfig.getWaitForSelectorTimeout()); 790 if(node == null) { 791 throw new UiObjectNotFoundException(getSelector().toString()); 792 } 793 return safeStringReturn(node.getPackageName()); 794 } 795 796 /** 797 * Returns the visible bounds of the view. 798 * 799 * If a portion of the view is visible, only the bounds of the visible portion are 800 * reported. 801 * 802 * @return Rect 803 * @throws UiObjectNotFoundException 804 * @see {@link #getBounds()} 805 * @since API Level 17 806 */ getVisibleBounds()807 public Rect getVisibleBounds() throws UiObjectNotFoundException { 808 Tracer.trace(); 809 AccessibilityNodeInfo node = findAccessibilityNodeInfo(mConfig.getWaitForSelectorTimeout()); 810 if(node == null) { 811 throw new UiObjectNotFoundException(getSelector().toString()); 812 } 813 return getVisibleBounds(node); 814 } 815 816 /** 817 * Returns the view's <code>bounds</code> property. See {@link #getVisibleBounds()} 818 * 819 * @return Rect 820 * @throws UiObjectNotFoundException 821 * @since API Level 16 822 */ getBounds()823 public Rect getBounds() throws UiObjectNotFoundException { 824 Tracer.trace(); 825 AccessibilityNodeInfo node = findAccessibilityNodeInfo(mConfig.getWaitForSelectorTimeout()); 826 if(node == null) { 827 throw new UiObjectNotFoundException(getSelector().toString()); 828 } 829 Rect nodeRect = new Rect(); 830 node.getBoundsInScreen(nodeRect); 831 832 return nodeRect; 833 } 834 835 /** 836 * Waits a specified length of time for a view to become visible. 837 * 838 * This method waits until the view becomes visible on the display, or 839 * until the timeout has elapsed. You can use this method in situations where 840 * the content that you want to select is not immediately displayed. 841 * 842 * @param timeout the amount of time to wait (in milliseconds) 843 * @return true if the view is displayed, else false if timeout elapsed while waiting 844 * @since API Level 16 845 */ waitForExists(long timeout)846 public boolean waitForExists(long timeout) { 847 Tracer.trace(timeout); 848 if(findAccessibilityNodeInfo(timeout) != null) { 849 return true; 850 } 851 return false; 852 } 853 854 /** 855 * Waits a specified length of time for a view to become undetectable. 856 * 857 * This method waits until a view is no longer matchable, or until the 858 * timeout has elapsed. 859 * 860 * A view becomes undetectable when the {@link UiSelector} of the object is 861 * unable to find a match because the element has either changed its state or is no 862 * longer displayed. 863 * 864 * You can use this method when attempting to wait for some long operation 865 * to compete, such as downloading a large file or connecting to a remote server. 866 * 867 * @param timeout time to wait (in milliseconds) 868 * @return true if the element is gone before timeout elapsed, else false if timeout elapsed 869 * but a matching element is still found. 870 * @since API Level 16 871 */ waitUntilGone(long timeout)872 public boolean waitUntilGone(long timeout) { 873 Tracer.trace(timeout); 874 long startMills = SystemClock.uptimeMillis(); 875 long currentMills = 0; 876 while (currentMills <= timeout) { 877 if(findAccessibilityNodeInfo(0) == null) 878 return true; 879 currentMills = SystemClock.uptimeMillis() - startMills; 880 if(timeout > 0) 881 SystemClock.sleep(WAIT_FOR_SELECTOR_POLL); 882 } 883 return false; 884 } 885 886 /** 887 * Check if view exists. 888 * 889 * This methods performs a {@link #waitForExists(long)} with zero timeout. This 890 * basically returns immediately whether the view represented by this UiObject 891 * exists or not. If you need to wait longer for this view, then see 892 * {@link #waitForExists(long)}. 893 * 894 * @return true if the view represented by this UiObject does exist 895 * @since API Level 16 896 */ exists()897 public boolean exists() { 898 Tracer.trace(); 899 return waitForExists(0); 900 } 901 safeStringReturn(CharSequence cs)902 private String safeStringReturn(CharSequence cs) { 903 if(cs == null) 904 return ""; 905 return cs.toString(); 906 } 907 908 /** 909 * Performs a two-pointer gesture, where each pointer moves diagonally 910 * opposite across the other, from the center out towards the edges of the 911 * this UiObject. 912 * @param percent percentage of the object's diagonal length for the pinch gesture 913 * @param steps the number of steps for the gesture. Steps are injected 914 * about 5 milliseconds apart, so 100 steps may take around 0.5 seconds to complete. 915 * @return <code>true</code> if all touch events for this gesture are injected successfully, 916 * <code>false</code> otherwise 917 * @throws UiObjectNotFoundException 918 * @since API Level 18 919 */ pinchOut(int percent, int steps)920 public boolean pinchOut(int percent, int steps) throws UiObjectNotFoundException { 921 // make value between 1 and 100 922 percent = (percent < 0) ? 1 : (percent > 100) ? 100 : percent; 923 float percentage = percent / 100f; 924 925 AccessibilityNodeInfo node = findAccessibilityNodeInfo(mConfig.getWaitForSelectorTimeout()); 926 if (node == null) { 927 throw new UiObjectNotFoundException(getSelector().toString()); 928 } 929 930 Rect rect = getVisibleBounds(node); 931 if (rect.width() <= FINGER_TOUCH_HALF_WIDTH * 2) 932 throw new IllegalStateException("Object width is too small for operation"); 933 934 // start from the same point at the center of the control 935 Point startPoint1 = new Point(rect.centerX() - FINGER_TOUCH_HALF_WIDTH, rect.centerY()); 936 Point startPoint2 = new Point(rect.centerX() + FINGER_TOUCH_HALF_WIDTH, rect.centerY()); 937 938 // End at the top-left and bottom-right corners of the control 939 Point endPoint1 = new Point(rect.centerX() - (int)((rect.width()/2) * percentage), 940 rect.centerY()); 941 Point endPoint2 = new Point(rect.centerX() + (int)((rect.width()/2) * percentage), 942 rect.centerY()); 943 944 return performTwoPointerGesture(startPoint1, startPoint2, endPoint1, endPoint2, steps); 945 } 946 947 /** 948 * Performs a two-pointer gesture, where each pointer moves diagonally 949 * toward the other, from the edges to the center of this UiObject . 950 * @param percent percentage of the object's diagonal length for the pinch gesture 951 * @param steps the number of steps for the gesture. Steps are injected 952 * about 5 milliseconds apart, so 100 steps may take around 0.5 seconds to complete. 953 * @return <code>true</code> if all touch events for this gesture are injected successfully, 954 * <code>false</code> otherwise 955 * @throws UiObjectNotFoundException 956 * @since API Level 18 957 */ pinchIn(int percent, int steps)958 public boolean pinchIn(int percent, int steps) throws UiObjectNotFoundException { 959 // make value between 1 and 100 960 percent = (percent < 0) ? 0 : (percent > 100) ? 100 : percent; 961 float percentage = percent / 100f; 962 963 AccessibilityNodeInfo node = findAccessibilityNodeInfo(mConfig.getWaitForSelectorTimeout()); 964 if (node == null) { 965 throw new UiObjectNotFoundException(getSelector().toString()); 966 } 967 968 Rect rect = getVisibleBounds(node); 969 if (rect.width() <= FINGER_TOUCH_HALF_WIDTH * 2) 970 throw new IllegalStateException("Object width is too small for operation"); 971 972 Point startPoint1 = new Point(rect.centerX() - (int)((rect.width()/2) * percentage), 973 rect.centerY()); 974 Point startPoint2 = new Point(rect.centerX() + (int)((rect.width()/2) * percentage), 975 rect.centerY()); 976 977 Point endPoint1 = new Point(rect.centerX() - FINGER_TOUCH_HALF_WIDTH, rect.centerY()); 978 Point endPoint2 = new Point(rect.centerX() + FINGER_TOUCH_HALF_WIDTH, rect.centerY()); 979 980 return performTwoPointerGesture(startPoint1, startPoint2, endPoint1, endPoint2, steps); 981 } 982 983 /** 984 * Generates a two-pointer gesture with arbitrary starting and ending points. 985 * 986 * @param startPoint1 start point of pointer 1 987 * @param startPoint2 start point of pointer 2 988 * @param endPoint1 end point of pointer 1 989 * @param endPoint2 end point of pointer 2 990 * @param steps the number of steps for the gesture. Steps are injected 991 * about 5 milliseconds apart, so 100 steps may take around 0.5 seconds to complete. 992 * @return <code>true</code> if all touch events for this gesture are injected successfully, 993 * <code>false</code> otherwise 994 * @since API Level 18 995 */ performTwoPointerGesture(Point startPoint1, Point startPoint2, Point endPoint1, Point endPoint2, int steps)996 public boolean performTwoPointerGesture(Point startPoint1, Point startPoint2, Point endPoint1, 997 Point endPoint2, int steps) { 998 999 // avoid a divide by zero 1000 if(steps == 0) 1001 steps = 1; 1002 1003 final float stepX1 = (endPoint1.x - startPoint1.x) / steps; 1004 final float stepY1 = (endPoint1.y - startPoint1.y) / steps; 1005 final float stepX2 = (endPoint2.x - startPoint2.x) / steps; 1006 final float stepY2 = (endPoint2.y - startPoint2.y) / steps; 1007 1008 int eventX1, eventY1, eventX2, eventY2; 1009 eventX1 = startPoint1.x; 1010 eventY1 = startPoint1.y; 1011 eventX2 = startPoint2.x; 1012 eventY2 = startPoint2.y; 1013 1014 // allocate for steps plus first down and last up 1015 PointerCoords[] points1 = new PointerCoords[steps + 2]; 1016 PointerCoords[] points2 = new PointerCoords[steps + 2]; 1017 1018 // Include the first and last touch downs in the arrays of steps 1019 for (int i = 0; i < steps + 1; i++) { 1020 PointerCoords p1 = new PointerCoords(); 1021 p1.x = eventX1; 1022 p1.y = eventY1; 1023 p1.pressure = 1; 1024 p1.size = 1; 1025 points1[i] = p1; 1026 1027 PointerCoords p2 = new PointerCoords(); 1028 p2.x = eventX2; 1029 p2.y = eventY2; 1030 p2.pressure = 1; 1031 p2.size = 1; 1032 points2[i] = p2; 1033 1034 eventX1 += stepX1; 1035 eventY1 += stepY1; 1036 eventX2 += stepX2; 1037 eventY2 += stepY2; 1038 } 1039 1040 // ending pointers coordinates 1041 PointerCoords p1 = new PointerCoords(); 1042 p1.x = endPoint1.x; 1043 p1.y = endPoint1.y; 1044 p1.pressure = 1; 1045 p1.size = 1; 1046 points1[steps + 1] = p1; 1047 1048 PointerCoords p2 = new PointerCoords(); 1049 p2.x = endPoint2.x; 1050 p2.y = endPoint2.y; 1051 p2.pressure = 1; 1052 p2.size = 1; 1053 points2[steps + 1] = p2; 1054 1055 return performMultiPointerGesture(points1, points2); 1056 } 1057 1058 /** 1059 * Performs a multi-touch gesture. You must specify touch coordinates for 1060 * at least 2 pointers. Each pointer must have all of its touch steps 1061 * defined in an array of {@link PointerCoords}. You can use this method to 1062 * specify complex gestures, like circles and irregular shapes, where each 1063 * pointer may take a different path. 1064 * 1065 * To create a single point on a pointer's touch path: 1066 * <code> 1067 * PointerCoords p = new PointerCoords(); 1068 * p.x = stepX; 1069 * p.y = stepY; 1070 * p.pressure = 1; 1071 * p.size = 1; 1072 * </code> 1073 * @param touches represents the pointers' paths. Each {@link PointerCoords} 1074 * array represents a different pointer. Each {@link PointerCoords} in an 1075 * array element represents a touch point on a pointer's path. 1076 * @return <code>true</code> if all touch events for this gesture are injected successfully, 1077 * <code>false</code> otherwise 1078 * @since API Level 18 1079 */ performMultiPointerGesture(PointerCoords[] ....touches)1080 public boolean performMultiPointerGesture(PointerCoords[] ...touches) { 1081 return getInteractionController().performMultiPointerGesture(touches); 1082 } 1083 } 1084