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