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