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 17package com.android.uiautomator.core; 18 19import android.graphics.Rect; 20import android.os.SystemClock; 21import android.util.Log; 22import android.view.KeyEvent; 23import android.view.accessibility.AccessibilityEvent; 24import android.view.accessibility.AccessibilityNodeInfo; 25 26/** 27 * A UiObject is a representation of a UI element. It is not in any way directly bound to a 28 * UI element as an object reference. A UiObject holds information to help it 29 * locate a matching UI element at runtime based on the {@link UiSelector} properties specified in 30 * its constructor. Since a UiObject is a representative for a UI element, it can 31 * be reused for different views with matching UI elements. 32 */ 33public class UiObject { 34 private static final String LOG_TAG = UiObject.class.getSimpleName(); 35 protected static final long WAIT_FOR_SELECTOR_TIMEOUT = 10 * 1000; 36 protected static final long WAIT_FOR_SELECTOR_POLL = 1000; 37 // set a default timeout to 5.5s, since ANR threshold is 5s 38 protected static final long WAIT_FOR_WINDOW_TMEOUT = 5500; 39 protected static final long WAIT_FOR_EVENT_TMEOUT = 3 * 1000; 40 protected static final int SWIPE_MARGIN_LIMIT = 5; 41 42 private final UiSelector mSelector; 43 private final UiAutomatorBridge mUiAutomationBridge; 44 45 /** 46 * Constructs a UiObject to represent a specific UI element matched by the specified 47 * {@link UiSelector} selector properties. 48 * 49 * @param selector 50 */ 51 public UiObject(UiSelector selector) { 52 mUiAutomationBridge = UiDevice.getInstance().getAutomatorBridge(); 53 mSelector = selector; 54 } 55 56 /** 57 * Debugging helper. A test can dump the properties of a selector as a string 58 * to its logs if needed. <code>getSelector().toString();</code> 59 * 60 * @return {@link UiSelector} 61 */ 62 public final UiSelector getSelector() { 63 return new UiSelector(mSelector); 64 } 65 66 /** 67 * Retrieves the {@link QueryController} to translate a {@link UiSelector} selector 68 * into an {@link AccessibilityNodeInfo}. 69 * 70 * @return {@link QueryController} 71 */ 72 QueryController getQueryController() { 73 return mUiAutomationBridge.getQueryController(); 74 } 75 76 /** 77 * Retrieves the {@link InteractionController} to perform finger actions such as tapping, 78 * swiping or entering text. 79 * 80 * @return {@link InteractionController} 81 */ 82 InteractionController getInteractionController() { 83 return mUiAutomationBridge.getInteractionController(); 84 } 85 86 /** 87 * Creates a new UiObject representing a child UI element of the element currently represented 88 * by this UiObject. 89 * 90 * @param selector for UI element to match 91 * @return a new UiObject representing the matched UI element 92 */ 93 public UiObject getChild(UiSelector selector) throws UiObjectNotFoundException { 94 return new UiObject(getSelector().childSelector(selector)); 95 } 96 97 /** 98 * Creates a new UiObject representing a child UI element from the parent element currently 99 * represented by this object. Essentially this is starting the search from the parent 100 * element and can also be used to find sibling UI elements to the one currently represented 101 * by this UiObject. 102 * 103 * @param selector for the UI element to match 104 * @return a new UiObject representing the matched UI element 105 * @throws UiObjectNotFoundException 106 */ 107 public UiObject getFromParent(UiSelector selector) throws UiObjectNotFoundException { 108 return new UiObject(getSelector().fromParent(selector)); 109 } 110 111 /** 112 * Counts the child UI elements immediately under the UI element currently represented by 113 * this UiObject. 114 * 115 * @return the count of child UI elements. 116 * @throws UiObjectNotFoundException 117 */ 118 public int getChildCount() throws UiObjectNotFoundException { 119 AccessibilityNodeInfo node = findAccessibilityNodeInfo(WAIT_FOR_SELECTOR_TIMEOUT); 120 if(node == null) { 121 throw new UiObjectNotFoundException(getSelector().toString()); 122 } 123 return node.getChildCount(); 124 } 125 126 /** 127 * Uses the member UiSelector properties to find a matching UI element reported in 128 * the accessibility hierarchy. 129 * 130 * @param selector {@link UiSelector} 131 * @param timeout in milliseconds 132 * @return AccessibilityNodeInfo if found else null 133 */ 134 protected AccessibilityNodeInfo findAccessibilityNodeInfo(long timeout) { 135 AccessibilityNodeInfo node = null; 136 if(UiDevice.getInstance().isInWatcherContext()) { 137 // we will NOT run watchers or do any sort of polling if the 138 // reason we're here is because of a watcher is executing. Watchers 139 // will not have other watchers run for them so they should not block 140 // while they poll for items to become present. We disable polling for them. 141 node = getQueryController().findAccessibilityNodeInfo(getSelector()); 142 } else { 143 long startMills = SystemClock.uptimeMillis(); 144 long currentMills = 0; 145 while (currentMills <= timeout) { 146 node = getQueryController().findAccessibilityNodeInfo(getSelector()); 147 if (node != null) { 148 break; 149 } else { 150 UiDevice.getInstance().runWatchers(); 151 } 152 currentMills = SystemClock.uptimeMillis() - startMills; 153 if(timeout > 0) { 154 SystemClock.sleep(WAIT_FOR_SELECTOR_POLL); 155 } 156 } 157 } 158 return node; 159 } 160 161 /** 162 * Perform the action on the UI element that is represented by this UiObject. Also see 163 * {@link #scrollToBeginning(int)}, {@link #scrollToEnd(int)}, {@link #scrollBackward()}, 164 * {@link #scrollForward()}. 165 * 166 * @param steps indicates the number of injected move steps into the system. Steps are 167 * injected about 5ms apart. So a 100 steps may take about 1/2 second to complete. 168 * @return true of successful 169 * @throws UiObjectNotFoundException 170 */ 171 public boolean swipeUp(int steps) throws UiObjectNotFoundException { 172 Rect rect = getVisibleBounds(); 173 if(rect.height() <= SWIPE_MARGIN_LIMIT * 2) 174 return false; // too small to swipe 175 return getInteractionController().swipe(rect.centerX(), 176 rect.bottom - SWIPE_MARGIN_LIMIT, rect.centerX(), rect.top + SWIPE_MARGIN_LIMIT, 177 steps); 178 } 179 180 /** 181 * Perform the action on the UI element that is represented by this object, Also see 182 * {@link #scrollToBeginning(int)}, {@link #scrollToEnd(int)}, {@link #scrollBackward()}, 183 * {@link #scrollForward()}. This method will perform the swipe gesture over any 184 * surface. The targeted UI element does not need to have the attribute 185 * <code>scrollable</code> set to <code>true</code> for this operation to be performed. 186 * 187 * @param steps indicates the number of injected move steps into the system. Steps are 188 * injected about 5ms apart. So a 100 steps may take about 1/2 second to complete. 189 * @return true if successful 190 * @throws UiObjectNotFoundException 191 */ 192 public boolean swipeDown(int steps) throws UiObjectNotFoundException { 193 Rect rect = getVisibleBounds(); 194 if(rect.height() <= SWIPE_MARGIN_LIMIT * 2) 195 return false; // too small to swipe 196 return getInteractionController().swipe(rect.centerX(), 197 rect.top + SWIPE_MARGIN_LIMIT, rect.centerX(), 198 rect.bottom - SWIPE_MARGIN_LIMIT, steps); 199 } 200 201 /** 202 * Perform the action on the UI element that is represented by this object. Also see 203 * {@link #scrollToBeginning(int)}, {@link #scrollToEnd(int)}, {@link #scrollBackward()}, 204 * {@link #scrollForward()}. This method will perform the swipe gesture over any 205 * surface. The targeted UI element does not need to have the attribute 206 * <code>scrollable</code> set to <code>true</code> for this operation to be performed. 207 * 208 * @param steps indicates the number of injected move steps into the system. Steps are 209 * injected about 5ms apart. So a 100 steps may take about 1/2 second to complete. 210 * @return true if successful 211 * @throws UiObjectNotFoundException 212 */ 213 public boolean swipeLeft(int steps) throws UiObjectNotFoundException { 214 Rect rect = getVisibleBounds(); 215 if(rect.width() <= SWIPE_MARGIN_LIMIT * 2) 216 return false; // too small to swipe 217 return getInteractionController().swipe(rect.right - SWIPE_MARGIN_LIMIT, 218 rect.centerY(), rect.left + SWIPE_MARGIN_LIMIT, rect.centerY(), steps); 219 } 220 221 /** 222 * Perform the action on the UI element that is represented by this object. Also see 223 * {@link #scrollToBeginning(int)}, {@link #scrollToEnd(int)}, {@link #scrollBackward()}, 224 * {@link #scrollForward()}. This method will perform the swipe gesture over any 225 * surface. The targeted UI element does not need to have the attribute 226 * <code>scrollable</code> set to <code>true</code> for this operation to be performed. 227 * 228 * @param steps indicates the number of injected move steps into the system. Steps are 229 * injected about 5ms apart. So a 100 steps may take about 1/2 second to complete. 230 * @return true if successful 231 * @throws UiObjectNotFoundException 232 */ 233 public boolean swipeRight(int steps) throws UiObjectNotFoundException { 234 Rect rect = getVisibleBounds(); 235 if(rect.width() <= SWIPE_MARGIN_LIMIT * 2) 236 return false; // too small to swipe 237 return getInteractionController().swipe(rect.left + SWIPE_MARGIN_LIMIT, 238 rect.centerY(), rect.right - SWIPE_MARGIN_LIMIT, rect.centerY(), steps); 239 } 240 241 /** 242 * Finds the visible bounds of a partially visible UI element 243 * 244 * @param node 245 * @return null if node is null, else a Rect containing visible bounds 246 */ 247 private Rect getVisibleBounds(AccessibilityNodeInfo node) { 248 if (node == null) { 249 return null; 250 } 251 252 // targeted node's bounds 253 Rect nodeRect = AccessibilityNodeInfoHelper.getVisibleBoundsInScreen(node); 254 255 // is the targeted node within a scrollable container? 256 AccessibilityNodeInfo scrollableParentNode = getScrollableParent(node); 257 if(scrollableParentNode == null) { 258 // nothing to adjust for so return the node's Rect as is 259 return nodeRect; 260 } 261 262 // Scrollable parent's visible bounds 263 Rect parentRect = AccessibilityNodeInfoHelper 264 .getVisibleBoundsInScreen(scrollableParentNode); 265 // adjust for partial clipping of targeted by parent node if required 266 nodeRect.intersect(parentRect); 267 return nodeRect; 268 } 269 270 /** 271 * Walk the hierarchy up to find a scrollable parent. A scrollable parent 272 * indicates that this node may be in a content where it is partially 273 * visible due to scrolling. its clickable center maybe invisible and 274 * adjustments should be made to the click coordinates. 275 * 276 * @param node 277 * @return 278 */ 279 private AccessibilityNodeInfo getScrollableParent(AccessibilityNodeInfo node) { 280 AccessibilityNodeInfo parent = node; 281 while(parent != null) { 282 parent = parent.getParent(); 283 if (parent != null && parent.isScrollable()) { 284 return parent; 285 } 286 } 287 return null; 288 } 289 290 /** 291 * Performs a click at the center of the visible bounds of the UI element represented 292 * by this UiObject. 293 * 294 * @return true id successful else false 295 * @throws UiObjectNotFoundException 296 */ 297 public boolean click() throws UiObjectNotFoundException { 298 AccessibilityNodeInfo node = findAccessibilityNodeInfo(WAIT_FOR_SELECTOR_TIMEOUT); 299 if(node == null) { 300 throw new UiObjectNotFoundException(getSelector().toString()); 301 } 302 Rect rect = getVisibleBounds(node); 303 return getInteractionController().clickAndWaitForEvents(rect.centerX(), rect.centerY(), 304 WAIT_FOR_EVENT_TMEOUT, false, AccessibilityEvent.TYPE_WINDOW_CONTENT_CHANGED + 305 AccessibilityEvent.TYPE_VIEW_SELECTED); 306 } 307 308 /** 309 * See {@link #clickAndWaitForNewWindow(long)} 310 * This method is intended to reliably wait for window transitions that would typically take 311 * longer than the usual default timeouts. 312 * 313 * @return true if the event was triggered, else false 314 * @throws UiObjectNotFoundException 315 */ 316 public boolean clickAndWaitForNewWindow() throws UiObjectNotFoundException { 317 return clickAndWaitForNewWindow(WAIT_FOR_WINDOW_TMEOUT); 318 } 319 320 /** 321 * Performs a click at the center of the visible bounds of the UI element represented 322 * by this UiObject and waits for window transitions. 323 * 324 * This method differ from {@link UiObject#click()} only in that this method waits for a 325 * a new window transition as a result of the click. Some examples of a window transition: 326 * <li>launching a new activity</li> 327 * <li>bringing up a pop-up menu</li> 328 * <li>bringing up a dialog</li> 329 * 330 * @param timeout timeout before giving up on waiting for a new window 331 * @return true if the event was triggered, else false 332 * @throws UiObjectNotFoundException 333 */ 334 public boolean clickAndWaitForNewWindow(long timeout) throws UiObjectNotFoundException { 335 AccessibilityNodeInfo node = findAccessibilityNodeInfo(WAIT_FOR_SELECTOR_TIMEOUT); 336 if(node == null) { 337 throw new UiObjectNotFoundException(getSelector().toString()); 338 } 339 Rect rect = getVisibleBounds(node); 340 return getInteractionController().clickAndWaitForNewWindow( 341 rect.centerX(), rect.centerY(), timeout); 342 } 343 344 /** 345 * Clicks the top and left corner of the UI element 346 * 347 * @return true on success 348 * @throws Exception 349 */ 350 public boolean clickTopLeft() throws UiObjectNotFoundException { 351 AccessibilityNodeInfo node = findAccessibilityNodeInfo(WAIT_FOR_SELECTOR_TIMEOUT); 352 if(node == null) { 353 throw new UiObjectNotFoundException(getSelector().toString()); 354 } 355 Rect rect = getVisibleBounds(node); 356 return getInteractionController().click(rect.left + 5, rect.top + 5); 357 } 358 359 /** 360 * Long clicks bottom and right corner of the UI element 361 * 362 * @return true if operation was successful 363 * @throws UiObjectNotFoundException 364 */ 365 public boolean longClickBottomRight() throws UiObjectNotFoundException { 366 AccessibilityNodeInfo node = findAccessibilityNodeInfo(WAIT_FOR_SELECTOR_TIMEOUT); 367 if(node == null) { 368 throw new UiObjectNotFoundException(getSelector().toString()); 369 } 370 Rect rect = getVisibleBounds(node); 371 return getInteractionController().longTap(rect.right - 5, rect.bottom - 5); 372 } 373 374 /** 375 * Clicks the bottom and right corner of the UI element 376 * 377 * @return true on success 378 * @throws Exception 379 */ 380 public boolean clickBottomRight() throws UiObjectNotFoundException { 381 AccessibilityNodeInfo node = findAccessibilityNodeInfo(WAIT_FOR_SELECTOR_TIMEOUT); 382 if(node == null) { 383 throw new UiObjectNotFoundException(getSelector().toString()); 384 } 385 Rect rect = getVisibleBounds(node); 386 return getInteractionController().click(rect.right - 5, rect.bottom - 5); 387 } 388 389 /** 390 * Long clicks the center of the visible bounds of the UI element 391 * 392 * @return true if operation was successful 393 * @throws UiObjectNotFoundException 394 */ 395 public boolean longClick() throws UiObjectNotFoundException { 396 AccessibilityNodeInfo node = findAccessibilityNodeInfo(WAIT_FOR_SELECTOR_TIMEOUT); 397 if(node == null) { 398 throw new UiObjectNotFoundException(getSelector().toString()); 399 } 400 Rect rect = getVisibleBounds(node); 401 return getInteractionController().longTap(rect.centerX(), rect.centerY()); 402 } 403 404 /** 405 * Long clicks on the top and left corner of the UI element 406 * 407 * @return true if operation was successful 408 * @throws UiObjectNotFoundException 409 */ 410 public boolean longClickTopLeft() throws UiObjectNotFoundException { 411 AccessibilityNodeInfo node = findAccessibilityNodeInfo(WAIT_FOR_SELECTOR_TIMEOUT); 412 if(node == null) { 413 throw new UiObjectNotFoundException(getSelector().toString()); 414 } 415 Rect rect = getVisibleBounds(node); 416 return getInteractionController().longTap(rect.left + 5, rect.top + 5); 417 } 418 419 /** 420 * Reads the <code>text</code> property of the UI element 421 * 422 * @return text value of the current node represented by this UiObject 423 * @throws UiObjectNotFoundException if no match could be found 424 */ 425 public String getText() throws UiObjectNotFoundException { 426 AccessibilityNodeInfo node = findAccessibilityNodeInfo(WAIT_FOR_SELECTOR_TIMEOUT); 427 if(node == null) { 428 throw new UiObjectNotFoundException(getSelector().toString()); 429 } 430 String retVal = safeStringReturn(node.getText()); 431 Log.d(LOG_TAG, String.format("getText() = %s", retVal)); 432 return retVal; 433 } 434 435 /** 436 * Reads the <code>content_desc</code> property of the UI element 437 * 438 * @return value of node attribute "content_desc" 439 * @throws UiObjectNotFoundException 440 */ 441 public String getContentDescription() throws UiObjectNotFoundException { 442 AccessibilityNodeInfo node = findAccessibilityNodeInfo(WAIT_FOR_SELECTOR_TIMEOUT); 443 if(node == null) { 444 throw new UiObjectNotFoundException(getSelector().toString()); 445 } 446 return safeStringReturn(node.getContentDescription()); 447 } 448 449 /** 450 * Sets the text in an editable field, after clearing the field's content. 451 * 452 * The {@link UiSelector} selector of this object must reference a UI element that is editable. 453 * 454 * When you call this method, the method first simulates a {@link #click()} on 455 * editable field to set focus. The method then clears the field's contents 456 * and injects your specified text into the field. 457 * 458 * If you want to capture the original contents of the field, call {@link #getText()} first. 459 * You can then modify the text and use this method to update the field. 460 * 461 * @param text string to set 462 * @return true if operation is successful 463 * @throws UiObjectNotFoundException 464 */ 465 public boolean setText(String text) throws UiObjectNotFoundException { 466 clearTextField(); 467 return getInteractionController().sendText(text); 468 } 469 470 /** 471 * Clears the existing text contents in an editable field. 472 * 473 * The {@link UiSelector} of this object must reference a UI element that is editable. 474 * 475 * When you call this method, the method first sets focus at the start edge of the field. 476 * The method then simulates a long-press to select the existing text, and deletes the 477 * selected text. 478 * 479 * If a "Select-All" option is displayed, the method will automatically attempt to use it 480 * to ensure full text selection. 481 * 482 * Note that it is possible that not all the text in the field is selected; for example, 483 * if the text contains separators such as spaces, slashes, at symbol etc. 484 * Also, not all editable fields support the long-press functionality. 485 * 486 * @throws UiObjectNotFoundException 487 */ 488 public void clearTextField() throws UiObjectNotFoundException { 489 // long click left + center 490 AccessibilityNodeInfo node = findAccessibilityNodeInfo(WAIT_FOR_SELECTOR_TIMEOUT); 491 if(node == null) { 492 throw new UiObjectNotFoundException(getSelector().toString()); 493 } 494 Rect rect = getVisibleBounds(node); 495 getInteractionController().longTap(rect.left + 20, rect.centerY()); 496 // check if the edit menu is open 497 UiObject selectAll = new UiObject(new UiSelector().descriptionContains("Select all")); 498 if(selectAll.waitForExists(50)) 499 selectAll.click(); 500 // wait for the selection 501 SystemClock.sleep(250); 502 // delete it 503 getInteractionController().sendKey(KeyEvent.KEYCODE_DEL, 0); 504 } 505 506 /** 507 * Check if the UI element's <code>checked</code> property is currently true 508 * 509 * @return true if it is else false 510 */ 511 public boolean isChecked() throws UiObjectNotFoundException { 512 AccessibilityNodeInfo node = findAccessibilityNodeInfo(WAIT_FOR_SELECTOR_TIMEOUT); 513 if(node == null) { 514 throw new UiObjectNotFoundException(getSelector().toString()); 515 } 516 return node.isChecked(); 517 } 518 519 /** 520 * Check if the UI element's <code>selected</code> property is currently true 521 * 522 * @return true if it is else false 523 * @throws UiObjectNotFoundException 524 */ 525 public boolean isSelected() throws UiObjectNotFoundException { 526 AccessibilityNodeInfo node = findAccessibilityNodeInfo(WAIT_FOR_SELECTOR_TIMEOUT); 527 if(node == null) { 528 throw new UiObjectNotFoundException(getSelector().toString()); 529 } 530 return node.isSelected(); 531 } 532 533 /** 534 * Check if the UI element's <code>checkable</code> property is currently true 535 * 536 * @return true if it is else false 537 * @throws UiObjectNotFoundException 538 */ 539 public boolean isCheckable() throws UiObjectNotFoundException { 540 AccessibilityNodeInfo node = findAccessibilityNodeInfo(WAIT_FOR_SELECTOR_TIMEOUT); 541 if(node == null) { 542 throw new UiObjectNotFoundException(getSelector().toString()); 543 } 544 return node.isCheckable(); 545 } 546 547 /** 548 * Check if the UI element's <code>enabled</code> property is currently true 549 * 550 * @return true if it is else false 551 * @throws UiObjectNotFoundException 552 */ 553 public boolean isEnabled() throws UiObjectNotFoundException { 554 AccessibilityNodeInfo node = findAccessibilityNodeInfo(WAIT_FOR_SELECTOR_TIMEOUT); 555 if(node == null) { 556 throw new UiObjectNotFoundException(getSelector().toString()); 557 } 558 return node.isEnabled(); 559 } 560 561 /** 562 * Check if the UI element's <code>clickable</code> property is currently true 563 * 564 * @return true if it is else false 565 * @throws UiObjectNotFoundException 566 */ 567 public boolean isClickable() throws UiObjectNotFoundException { 568 AccessibilityNodeInfo node = findAccessibilityNodeInfo(WAIT_FOR_SELECTOR_TIMEOUT); 569 if(node == null) { 570 throw new UiObjectNotFoundException(getSelector().toString()); 571 } 572 return node.isClickable(); 573 } 574 575 /** 576 * Check if the UI element's <code>focused</code> property is currently true 577 * 578 * @return true if it is else false 579 * @throws UiObjectNotFoundException 580 */ 581 public boolean isFocused() throws UiObjectNotFoundException { 582 AccessibilityNodeInfo node = findAccessibilityNodeInfo(WAIT_FOR_SELECTOR_TIMEOUT); 583 if(node == null) { 584 throw new UiObjectNotFoundException(getSelector().toString()); 585 } 586 return node.isFocused(); 587 } 588 589 /** 590 * Check if the UI element's <code>focusable</code> property is currently true 591 * 592 * @return true if it is else false 593 * @throws UiObjectNotFoundException 594 */ 595 public boolean isFocusable() throws UiObjectNotFoundException { 596 AccessibilityNodeInfo node = findAccessibilityNodeInfo(WAIT_FOR_SELECTOR_TIMEOUT); 597 if(node == null) { 598 throw new UiObjectNotFoundException(getSelector().toString()); 599 } 600 return node.isFocusable(); 601 } 602 603 /** 604 * Check if the UI element's <code>scrollable</code> property is currently true 605 * 606 * @return true if it is else false 607 * @throws UiObjectNotFoundException 608 */ 609 public boolean isScrollable() throws UiObjectNotFoundException { 610 AccessibilityNodeInfo node = findAccessibilityNodeInfo(WAIT_FOR_SELECTOR_TIMEOUT); 611 if(node == null) { 612 throw new UiObjectNotFoundException(getSelector().toString()); 613 } 614 return node.isScrollable(); 615 } 616 617 /** 618 * Check if the UI element's <code>long-clickable</code> property is currently true 619 * 620 * @return true if it is else false 621 * @throws UiObjectNotFoundException 622 */ 623 public boolean isLongClickable() throws UiObjectNotFoundException { 624 AccessibilityNodeInfo node = findAccessibilityNodeInfo(WAIT_FOR_SELECTOR_TIMEOUT); 625 if(node == null) { 626 throw new UiObjectNotFoundException(getSelector().toString()); 627 } 628 return node.isLongClickable(); 629 } 630 631 /** 632 * Reads the UI element's <code>package</code> property 633 * 634 * @return true if it is else false 635 * @throws UiObjectNotFoundException 636 */ 637 public String getPackageName() throws UiObjectNotFoundException { 638 AccessibilityNodeInfo node = findAccessibilityNodeInfo(WAIT_FOR_SELECTOR_TIMEOUT); 639 if(node == null) { 640 throw new UiObjectNotFoundException(getSelector().toString()); 641 } 642 return safeStringReturn(node.getPackageName()); 643 } 644 645 /** 646 * Returns the visible bounds of the UI element. 647 * 648 * If a portion of the UI element is visible, only the bounds of the visible portion are 649 * reported. 650 * 651 * @return Rect 652 * @throws UiObjectNotFoundException 653 * @see {@link #getBound()} 654 */ 655 public Rect getVisibleBounds() throws UiObjectNotFoundException { 656 AccessibilityNodeInfo node = findAccessibilityNodeInfo(WAIT_FOR_SELECTOR_TIMEOUT); 657 if(node == null) { 658 throw new UiObjectNotFoundException(getSelector().toString()); 659 } 660 return getVisibleBounds(node); 661 } 662 663 /** 664 * Returns the UI element's <code>bounds</code> property. See {@link #getVisibleBounds()} 665 * 666 * @return Rect 667 * @throws UiObjectNotFoundException 668 */ 669 public Rect getBounds() throws UiObjectNotFoundException { 670 AccessibilityNodeInfo node = findAccessibilityNodeInfo(WAIT_FOR_SELECTOR_TIMEOUT); 671 if(node == null) { 672 throw new UiObjectNotFoundException(getSelector().toString()); 673 } 674 Rect nodeRect = new Rect(); 675 node.getBoundsInScreen(nodeRect); 676 677 return nodeRect; 678 } 679 680 /** 681 * Waits a specified length of time for a UI element to become visible. 682 * 683 * This method waits until the UI element becomes visible on the display, or 684 * until the timeout has elapsed. You can use this method in situations where 685 * the content that you want to select is not immediately displayed. 686 * 687 * @param timeout the amount of time to wait (in milliseconds) 688 * @return true if the UI element is displayed, else false if timeout elapsed while waiting 689 */ 690 public boolean waitForExists(long timeout) { 691 if(findAccessibilityNodeInfo(timeout) != null) { 692 return true; 693 } 694 return false; 695 } 696 697 /** 698 * Waits a specified length of time for a UI element to become undetectable. 699 * 700 * This method waits until a UI element is no longer matchable, or until the 701 * timeout has elapsed. 702 * 703 * A UI element becomes undetectable when the {@link UiSelector} of the object is 704 * unable to find a match because the element has either changed its state or is no 705 * longer displayed. 706 * 707 * You can use this method when attempting to wait for some long operation 708 * to compete, such as downloading a large file or connecting to a remote server. 709 * 710 * @param timeout time to wait (in milliseconds) 711 * @return true if the element is gone before timeout elapsed, else false if timeout elapsed 712 * but a matching element is still found. 713 */ 714 public boolean waitUntilGone(long timeout) { 715 long startMills = SystemClock.uptimeMillis(); 716 long currentMills = 0; 717 while (currentMills <= timeout) { 718 if(findAccessibilityNodeInfo(0) == null) 719 return true; 720 currentMills = SystemClock.uptimeMillis() - startMills; 721 if(timeout > 0) 722 SystemClock.sleep(WAIT_FOR_SELECTOR_POLL); 723 } 724 return false; 725 } 726 727 /** 728 * Check if UI element exists. 729 * 730 * This methods performs a {@link #waitForExists(long)} with zero timeout. This 731 * basically returns immediately whether the UI element represented by this UiObject 732 * exists or not. If you need to wait longer for this UI element, then see 733 * {@link #waitForExists(long)}. 734 * 735 * @return true if the UI element represented by this UiObject does exist 736 */ 737 public boolean exists() { 738 return waitForExists(0); 739 } 740 741 private String safeStringReturn(CharSequence cs) { 742 if(cs == null) 743 return ""; 744 return cs.toString(); 745 } 746} 747