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