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