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