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