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