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