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         nodeRect.intersect(parentRect);
357         return nodeRect;
358     }
359 
360     /**
361      * Walks up the layout hierarchy to find a scrollable parent. A scrollable parent
362      * indicates that this node might be in a container where it is partially
363      * visible due to scrolling. In this case, its clickable center might not be visible and
364      * the click coordinates should be adjusted.
365      *
366      * @param node
367      * @return The accessibility node info.
368      */
getScrollableParent(AccessibilityNodeInfo node)369     private AccessibilityNodeInfo getScrollableParent(AccessibilityNodeInfo node) {
370         AccessibilityNodeInfo parent = node;
371         while(parent != null) {
372             parent = parent.getParent();
373             if (parent != null && parent.isScrollable()) {
374                 return parent;
375             }
376         }
377         return null;
378     }
379 
380     /**
381      * Performs a click at the center of the visible bounds of the UI element represented
382      * by this UiObject.
383      *
384      * @return true id successful else false
385      * @throws UiObjectNotFoundException
386      * @since API Level 16
387      */
click()388     public boolean click() throws UiObjectNotFoundException {
389         Tracer.trace();
390         AccessibilityNodeInfo node = findAccessibilityNodeInfo(mConfig.getWaitForSelectorTimeout());
391         if(node == null) {
392             throw new UiObjectNotFoundException(getSelector().toString());
393         }
394         Rect rect = getVisibleBounds(node);
395         return getInteractionController().clickAndSync(rect.centerX(), rect.centerY(),
396                 mConfig.getActionAcknowledgmentTimeout());
397     }
398 
399     /**
400      * Waits for window transitions that would typically take longer than the
401      * usual default timeouts.
402      * See {@link #clickAndWaitForNewWindow(long)}
403      *
404      * @return true if the event was triggered, else false
405      * @throws UiObjectNotFoundException
406      * @since API Level 16
407      */
clickAndWaitForNewWindow()408     public boolean clickAndWaitForNewWindow() throws UiObjectNotFoundException {
409         Tracer.trace();
410         return clickAndWaitForNewWindow(WAIT_FOR_WINDOW_TMEOUT);
411     }
412 
413     /**
414      * Performs a click at the center of the visible bounds of the UI element represented
415      * by this UiObject and waits for window transitions.
416      *
417      * This method differ from {@link UiObject#click()} only in that this method waits for a
418      * a new window transition as a result of the click. Some examples of a window transition:
419      * <li>launching a new activity</li>
420      * <li>bringing up a pop-up menu</li>
421      * <li>bringing up a dialog</li>
422      *
423      * @param timeout timeout before giving up on waiting for a new window
424      * @return true if the event was triggered, else false
425      * @throws UiObjectNotFoundException
426      * @since API Level 16
427      */
clickAndWaitForNewWindow(long timeout)428     public boolean clickAndWaitForNewWindow(long timeout) throws UiObjectNotFoundException {
429         Tracer.trace(timeout);
430         AccessibilityNodeInfo node = findAccessibilityNodeInfo(mConfig.getWaitForSelectorTimeout());
431         if(node == null) {
432             throw new UiObjectNotFoundException(getSelector().toString());
433         }
434         Rect rect = getVisibleBounds(node);
435         return getInteractionController().clickAndWaitForNewWindow(rect.centerX(), rect.centerY(),
436                 mConfig.getActionAcknowledgmentTimeout());
437     }
438 
439     /**
440      * Clicks the top and left corner of the UI element
441      *
442      * @return true on success
443      * @throws UiObjectNotFoundException
444      * @since API Level 16
445      */
clickTopLeft()446     public boolean clickTopLeft() throws UiObjectNotFoundException {
447         Tracer.trace();
448         AccessibilityNodeInfo node = findAccessibilityNodeInfo(mConfig.getWaitForSelectorTimeout());
449         if(node == null) {
450             throw new UiObjectNotFoundException(getSelector().toString());
451         }
452         Rect rect = getVisibleBounds(node);
453         return getInteractionController().clickNoSync(rect.left + 5, rect.top + 5);
454     }
455 
456     /**
457      * Long clicks bottom and right corner of the UI element
458      *
459      * @return true if operation was successful
460      * @throws UiObjectNotFoundException
461      * @since API Level 16
462      */
longClickBottomRight()463     public boolean longClickBottomRight() throws UiObjectNotFoundException  {
464         Tracer.trace();
465         AccessibilityNodeInfo node = findAccessibilityNodeInfo(mConfig.getWaitForSelectorTimeout());
466         if(node == null) {
467             throw new UiObjectNotFoundException(getSelector().toString());
468         }
469         Rect rect = getVisibleBounds(node);
470         return getInteractionController().longTapNoSync(rect.right - 5, rect.bottom - 5);
471     }
472 
473     /**
474      * Clicks the bottom and right corner of the UI element
475      *
476      * @return true on success
477      * @throws UiObjectNotFoundException
478      * @since API Level 16
479      */
clickBottomRight()480     public boolean clickBottomRight() throws UiObjectNotFoundException {
481         Tracer.trace();
482         AccessibilityNodeInfo node = findAccessibilityNodeInfo(mConfig.getWaitForSelectorTimeout());
483         if(node == null) {
484             throw new UiObjectNotFoundException(getSelector().toString());
485         }
486         Rect rect = getVisibleBounds(node);
487         return getInteractionController().clickNoSync(rect.right - 5, rect.bottom - 5);
488     }
489 
490     /**
491      * Long clicks the center of the visible bounds of the UI element
492      *
493      * @return true if operation was successful
494      * @throws UiObjectNotFoundException
495      * @since API Level 16
496      */
longClick()497     public boolean longClick() throws UiObjectNotFoundException  {
498         Tracer.trace();
499         AccessibilityNodeInfo node = findAccessibilityNodeInfo(mConfig.getWaitForSelectorTimeout());
500         if(node == null) {
501             throw new UiObjectNotFoundException(getSelector().toString());
502         }
503         Rect rect = getVisibleBounds(node);
504         return getInteractionController().longTapNoSync(rect.centerX(), rect.centerY());
505     }
506 
507     /**
508      * Long clicks on the top and left corner of the UI element
509      *
510      * @return true if operation was successful
511      * @throws UiObjectNotFoundException
512      * @since API Level 16
513      */
longClickTopLeft()514     public boolean longClickTopLeft() throws UiObjectNotFoundException {
515         Tracer.trace();
516         AccessibilityNodeInfo node = findAccessibilityNodeInfo(mConfig.getWaitForSelectorTimeout());
517         if(node == null) {
518             throw new UiObjectNotFoundException(getSelector().toString());
519         }
520         Rect rect = getVisibleBounds(node);
521         return getInteractionController().longTapNoSync(rect.left + 5, rect.top + 5);
522     }
523 
524     /**
525      * Reads the <code>text</code> property of the UI element
526      *
527      * @return text value of the current node represented by this UiObject
528      * @throws UiObjectNotFoundException if no match could be found
529      * @since API Level 16
530      */
getText()531     public String getText() throws UiObjectNotFoundException {
532         Tracer.trace();
533         AccessibilityNodeInfo node = findAccessibilityNodeInfo(mConfig.getWaitForSelectorTimeout());
534         if(node == null) {
535             throw new UiObjectNotFoundException(getSelector().toString());
536         }
537         String retVal = safeStringReturn(node.getText());
538         Log.d(LOG_TAG, String.format("getText() = %s", retVal));
539         return retVal;
540     }
541 
542     /**
543      * Retrieves the <code>className</code> property of the UI element.
544      *
545      * @return class name of the current node represented by this UiObject
546      * @throws UiObjectNotFoundException if no match was found
547      * @since API Level 18
548      */
getClassName()549     public String getClassName() throws UiObjectNotFoundException {
550         Tracer.trace();
551         AccessibilityNodeInfo node = findAccessibilityNodeInfo(mConfig.getWaitForSelectorTimeout());
552         if(node == null) {
553             throw new UiObjectNotFoundException(getSelector().toString());
554         }
555         String retVal = safeStringReturn(node.getClassName());
556         Log.d(LOG_TAG, String.format("getClassName() = %s", retVal));
557         return retVal;
558     }
559 
560     /**
561      * Reads the <code>content_desc</code> property of the UI element
562      *
563      * @return value of node attribute "content_desc"
564      * @throws UiObjectNotFoundException
565      * @since API Level 16
566      */
getContentDescription()567     public String getContentDescription() throws UiObjectNotFoundException {
568         Tracer.trace();
569         AccessibilityNodeInfo node = findAccessibilityNodeInfo(mConfig.getWaitForSelectorTimeout());
570         if(node == null) {
571             throw new UiObjectNotFoundException(getSelector().toString());
572         }
573         return safeStringReturn(node.getContentDescription());
574     }
575 
576     /**
577      * Sets the text in an editable field, after clearing the field's content.
578      *
579      * The {@link UiSelector} selector of this object must reference a UI element that is editable.
580      *
581      * When you call this method, the method first simulates a {@link #click()} on
582      * editable field to set focus. The method then clears the field's contents
583      * and injects your specified text into the field.
584      *
585      * If you want to capture the original contents of the field, call {@link #getText()} first.
586      * You can then modify the text and use this method to update the field.
587      *
588      * @param text string to set
589      * @return true if operation is successful
590      * @throws UiObjectNotFoundException
591      * @since API Level 16
592      */
setText(String text)593     public boolean setText(String text) throws UiObjectNotFoundException {
594         Tracer.trace(text);
595         clearTextField();
596         return getInteractionController().sendText(text);
597     }
598 
599     /**
600      * Clears the existing text contents in an editable field.
601      *
602      * The {@link UiSelector} of this object must reference a UI element that is editable.
603      *
604      * When you call this method, the method first sets focus at the start edge of the field.
605      * The method then simulates a long-press to select the existing text, and deletes the
606      * selected text.
607      *
608      * If a "Select-All" option is displayed, the method will automatically attempt to use it
609      * to ensure full text selection.
610      *
611      * Note that it is possible that not all the text in the field is selected; for example,
612      * if the text contains separators such as spaces, slashes, at symbol etc.
613      * Also, not all editable fields support the long-press functionality.
614      *
615      * @throws UiObjectNotFoundException
616      * @since API Level 16
617      */
clearTextField()618     public void clearTextField() throws UiObjectNotFoundException {
619         Tracer.trace();
620         // long click left + center
621         AccessibilityNodeInfo node = findAccessibilityNodeInfo(mConfig.getWaitForSelectorTimeout());
622         if(node == null) {
623             throw new UiObjectNotFoundException(getSelector().toString());
624         }
625         Rect rect = getVisibleBounds(node);
626         getInteractionController().longTapNoSync(rect.left + 20, rect.centerY());
627         // check if the edit menu is open
628         UiObject selectAll = new UiObject(new UiSelector().descriptionContains("Select all"));
629         if(selectAll.waitForExists(50))
630             selectAll.click();
631         // wait for the selection
632         SystemClock.sleep(250);
633         // delete it
634         getInteractionController().sendKey(KeyEvent.KEYCODE_DEL, 0);
635     }
636 
637     /**
638      * Check if the UI element's <code>checked</code> property is currently true
639      *
640      * @return true if it is else false
641      * @since API Level 16
642      */
isChecked()643     public boolean isChecked() throws UiObjectNotFoundException {
644         Tracer.trace();
645         AccessibilityNodeInfo node = findAccessibilityNodeInfo(mConfig.getWaitForSelectorTimeout());
646         if(node == null) {
647             throw new UiObjectNotFoundException(getSelector().toString());
648         }
649         return node.isChecked();
650     }
651 
652     /**
653      * Checks if the UI element's <code>selected</code> property is currently true.
654      *
655      * @return true if it is else false
656      * @throws UiObjectNotFoundException
657      * @since API Level 16
658      */
isSelected()659     public boolean isSelected() throws UiObjectNotFoundException {
660         Tracer.trace();
661         AccessibilityNodeInfo node = findAccessibilityNodeInfo(mConfig.getWaitForSelectorTimeout());
662         if(node == null) {
663             throw new UiObjectNotFoundException(getSelector().toString());
664         }
665         return node.isSelected();
666     }
667 
668     /**
669      * Checks if the UI element's <code>checkable</code> property is currently true.
670      *
671      * @return true if it is else false
672      * @throws UiObjectNotFoundException
673      * @since API Level 16
674      */
isCheckable()675     public boolean isCheckable() throws UiObjectNotFoundException {
676         Tracer.trace();
677         AccessibilityNodeInfo node = findAccessibilityNodeInfo(mConfig.getWaitForSelectorTimeout());
678         if(node == null) {
679             throw new UiObjectNotFoundException(getSelector().toString());
680         }
681         return node.isCheckable();
682     }
683 
684     /**
685      * Checks if the UI element's <code>enabled</code> property is currently true.
686      *
687      * @return true if it is else false
688      * @throws UiObjectNotFoundException
689      * @since API Level 16
690      */
isEnabled()691     public boolean isEnabled() throws UiObjectNotFoundException {
692         Tracer.trace();
693         AccessibilityNodeInfo node = findAccessibilityNodeInfo(mConfig.getWaitForSelectorTimeout());
694         if(node == null) {
695             throw new UiObjectNotFoundException(getSelector().toString());
696         }
697         return node.isEnabled();
698     }
699 
700     /**
701      * Checks if the UI element's <code>clickable</code> property is currently true.
702      *
703      * @return true if it is else false
704      * @throws UiObjectNotFoundException
705      * @since API Level 16
706      */
isClickable()707     public boolean isClickable() throws UiObjectNotFoundException {
708         Tracer.trace();
709         AccessibilityNodeInfo node = findAccessibilityNodeInfo(mConfig.getWaitForSelectorTimeout());
710         if(node == null) {
711             throw new UiObjectNotFoundException(getSelector().toString());
712         }
713         return node.isClickable();
714     }
715 
716     /**
717      * Check if the UI element's <code>focused</code> property is currently true
718      *
719      * @return true if it is else false
720      * @throws UiObjectNotFoundException
721      * @since API Level 16
722      */
isFocused()723     public boolean isFocused() throws UiObjectNotFoundException {
724         Tracer.trace();
725         AccessibilityNodeInfo node = findAccessibilityNodeInfo(mConfig.getWaitForSelectorTimeout());
726         if(node == null) {
727             throw new UiObjectNotFoundException(getSelector().toString());
728         }
729         return node.isFocused();
730     }
731 
732     /**
733      * Check if the UI element's <code>focusable</code> property is currently true.
734      *
735      * @return true if it is else false
736      * @throws UiObjectNotFoundException
737      * @since API Level 16
738      */
isFocusable()739     public boolean isFocusable() throws UiObjectNotFoundException {
740         Tracer.trace();
741         AccessibilityNodeInfo node = findAccessibilityNodeInfo(mConfig.getWaitForSelectorTimeout());
742         if(node == null) {
743             throw new UiObjectNotFoundException(getSelector().toString());
744         }
745         return node.isFocusable();
746     }
747 
748     /**
749      * Check if the view's <code>scrollable</code> property is currently true
750      *
751      * @return true if it is else false
752      * @throws UiObjectNotFoundException
753      * @since API Level 16
754      */
isScrollable()755     public boolean isScrollable() throws UiObjectNotFoundException {
756         Tracer.trace();
757         AccessibilityNodeInfo node = findAccessibilityNodeInfo(mConfig.getWaitForSelectorTimeout());
758         if(node == null) {
759             throw new UiObjectNotFoundException(getSelector().toString());
760         }
761         return node.isScrollable();
762     }
763 
764     /**
765      * Check if the view's <code>long-clickable</code> property is currently true
766      *
767      * @return true if it is else false
768      * @throws UiObjectNotFoundException
769      * @since API Level 16
770      */
isLongClickable()771     public boolean isLongClickable() throws UiObjectNotFoundException {
772         Tracer.trace();
773         AccessibilityNodeInfo node = findAccessibilityNodeInfo(mConfig.getWaitForSelectorTimeout());
774         if(node == null) {
775             throw new UiObjectNotFoundException(getSelector().toString());
776         }
777         return node.isLongClickable();
778     }
779 
780     /**
781      * Reads the view's <code>package</code> property
782      *
783      * @return true if it is else false
784      * @throws UiObjectNotFoundException
785      * @since API Level 16
786      */
getPackageName()787     public String getPackageName() throws UiObjectNotFoundException {
788         Tracer.trace();
789         AccessibilityNodeInfo node = findAccessibilityNodeInfo(mConfig.getWaitForSelectorTimeout());
790         if(node == null) {
791             throw new UiObjectNotFoundException(getSelector().toString());
792         }
793         return safeStringReturn(node.getPackageName());
794     }
795 
796     /**
797      * Returns the visible bounds of the view.
798      *
799      * If a portion of the view is visible, only the bounds of the visible portion are
800      * reported.
801      *
802      * @return Rect
803      * @throws UiObjectNotFoundException
804      * @see {@link #getBounds()}
805      * @since API Level 17
806      */
getVisibleBounds()807     public Rect getVisibleBounds() throws UiObjectNotFoundException {
808         Tracer.trace();
809         AccessibilityNodeInfo node = findAccessibilityNodeInfo(mConfig.getWaitForSelectorTimeout());
810         if(node == null) {
811             throw new UiObjectNotFoundException(getSelector().toString());
812         }
813         return getVisibleBounds(node);
814     }
815 
816     /**
817      * Returns the view's <code>bounds</code> property. See {@link #getVisibleBounds()}
818      *
819      * @return Rect
820      * @throws UiObjectNotFoundException
821      * @since API Level 16
822      */
getBounds()823     public Rect getBounds() throws UiObjectNotFoundException {
824         Tracer.trace();
825         AccessibilityNodeInfo node = findAccessibilityNodeInfo(mConfig.getWaitForSelectorTimeout());
826         if(node == null) {
827             throw new UiObjectNotFoundException(getSelector().toString());
828         }
829         Rect nodeRect = new Rect();
830         node.getBoundsInScreen(nodeRect);
831 
832         return nodeRect;
833     }
834 
835     /**
836      * Waits a specified length of time for a view to become visible.
837      *
838      * This method waits until the view becomes visible on the display, or
839      * until the timeout has elapsed. You can use this method in situations where
840      * the content that you want to select is not immediately displayed.
841      *
842      * @param timeout the amount of time to wait (in milliseconds)
843      * @return true if the view is displayed, else false if timeout elapsed while waiting
844      * @since API Level 16
845      */
waitForExists(long timeout)846     public boolean waitForExists(long timeout) {
847         Tracer.trace(timeout);
848         if(findAccessibilityNodeInfo(timeout) != null) {
849             return true;
850         }
851         return false;
852     }
853 
854     /**
855      * Waits a specified length of time for a view to become undetectable.
856      *
857      * This method waits until a view is no longer matchable, or until the
858      * timeout has elapsed.
859      *
860      * A view becomes undetectable when the {@link UiSelector} of the object is
861      * unable to find a match because the element has either changed its state or is no
862      * longer displayed.
863      *
864      * You can use this method when attempting to wait for some long operation
865      * to compete, such as downloading a large file or connecting to a remote server.
866      *
867      * @param timeout time to wait (in milliseconds)
868      * @return true if the element is gone before timeout elapsed, else false if timeout elapsed
869      * but a matching element is still found.
870      * @since API Level 16
871      */
waitUntilGone(long timeout)872     public boolean waitUntilGone(long timeout) {
873         Tracer.trace(timeout);
874         long startMills = SystemClock.uptimeMillis();
875         long currentMills = 0;
876         while (currentMills <= timeout) {
877             if(findAccessibilityNodeInfo(0) == null)
878                 return true;
879             currentMills = SystemClock.uptimeMillis() - startMills;
880             if(timeout > 0)
881                 SystemClock.sleep(WAIT_FOR_SELECTOR_POLL);
882         }
883         return false;
884     }
885 
886     /**
887      * Check if view exists.
888      *
889      * This methods performs a {@link #waitForExists(long)} with zero timeout. This
890      * basically returns immediately whether the view represented by this UiObject
891      * exists or not. If you need to wait longer for this view, then see
892      * {@link #waitForExists(long)}.
893      *
894      * @return true if the view represented by this UiObject does exist
895      * @since API Level 16
896      */
exists()897     public boolean exists() {
898         Tracer.trace();
899         return waitForExists(0);
900     }
901 
safeStringReturn(CharSequence cs)902     private String safeStringReturn(CharSequence cs) {
903         if(cs == null)
904             return "";
905         return cs.toString();
906     }
907 
908     /**
909      * Performs a two-pointer gesture, where each pointer moves diagonally
910      * opposite across the other, from the center out towards the edges of the
911      * this UiObject.
912      * @param percent percentage of the object's diagonal length for the pinch gesture
913      * @param steps the number of steps for the gesture. Steps are injected
914      * about 5 milliseconds apart, so 100 steps may take around 0.5 seconds to complete.
915      * @return <code>true</code> if all touch events for this gesture are injected successfully,
916      *         <code>false</code> otherwise
917      * @throws UiObjectNotFoundException
918      * @since API Level 18
919      */
pinchOut(int percent, int steps)920     public boolean pinchOut(int percent, int steps) throws UiObjectNotFoundException {
921         // make value between 1 and 100
922         percent = (percent < 0) ? 1 : (percent > 100) ? 100 : percent;
923         float percentage = percent / 100f;
924 
925         AccessibilityNodeInfo node = findAccessibilityNodeInfo(mConfig.getWaitForSelectorTimeout());
926         if (node == null) {
927             throw new UiObjectNotFoundException(getSelector().toString());
928         }
929 
930         Rect rect = getVisibleBounds(node);
931         if (rect.width() <= FINGER_TOUCH_HALF_WIDTH * 2)
932             throw new IllegalStateException("Object width is too small for operation");
933 
934         // start from the same point at the center of the control
935         Point startPoint1 = new Point(rect.centerX() - FINGER_TOUCH_HALF_WIDTH, rect.centerY());
936         Point startPoint2 = new Point(rect.centerX() + FINGER_TOUCH_HALF_WIDTH, rect.centerY());
937 
938         // End at the top-left and bottom-right corners of the control
939         Point endPoint1 = new Point(rect.centerX() - (int)((rect.width()/2) * percentage),
940                 rect.centerY());
941         Point endPoint2 = new Point(rect.centerX() + (int)((rect.width()/2) * percentage),
942                 rect.centerY());
943 
944         return performTwoPointerGesture(startPoint1, startPoint2, endPoint1, endPoint2, steps);
945     }
946 
947     /**
948      * Performs a two-pointer gesture, where each pointer moves diagonally
949      * toward the other, from the edges to the center of this UiObject .
950      * @param percent percentage of the object's diagonal length for the pinch gesture
951      * @param steps the number of steps for the gesture. Steps are injected
952      * about 5 milliseconds apart, so 100 steps may take around 0.5 seconds to complete.
953      * @return <code>true</code> if all touch events for this gesture are injected successfully,
954      *         <code>false</code> otherwise
955      * @throws UiObjectNotFoundException
956      * @since API Level 18
957      */
pinchIn(int percent, int steps)958     public boolean pinchIn(int percent, int steps) throws UiObjectNotFoundException {
959         // make value between 1 and 100
960         percent = (percent < 0) ? 0 : (percent > 100) ? 100 : percent;
961         float percentage = percent / 100f;
962 
963         AccessibilityNodeInfo node = findAccessibilityNodeInfo(mConfig.getWaitForSelectorTimeout());
964         if (node == null) {
965             throw new UiObjectNotFoundException(getSelector().toString());
966         }
967 
968         Rect rect = getVisibleBounds(node);
969         if (rect.width() <= FINGER_TOUCH_HALF_WIDTH * 2)
970             throw new IllegalStateException("Object width is too small for operation");
971 
972         Point startPoint1 = new Point(rect.centerX() - (int)((rect.width()/2) * percentage),
973                 rect.centerY());
974         Point startPoint2 = new Point(rect.centerX() + (int)((rect.width()/2) * percentage),
975                 rect.centerY());
976 
977         Point endPoint1 = new Point(rect.centerX() - FINGER_TOUCH_HALF_WIDTH, rect.centerY());
978         Point endPoint2 = new Point(rect.centerX() + FINGER_TOUCH_HALF_WIDTH, rect.centerY());
979 
980         return performTwoPointerGesture(startPoint1, startPoint2, endPoint1, endPoint2, steps);
981     }
982 
983     /**
984      * Generates a two-pointer gesture with arbitrary starting and ending points.
985      *
986      * @param startPoint1 start point of pointer 1
987      * @param startPoint2 start point of pointer 2
988      * @param endPoint1 end point of pointer 1
989      * @param endPoint2 end point of pointer 2
990      * @param steps the number of steps for the gesture. Steps are injected
991      * about 5 milliseconds apart, so 100 steps may take around 0.5 seconds to complete.
992      * @return <code>true</code> if all touch events for this gesture are injected successfully,
993      *         <code>false</code> otherwise
994      * @since API Level 18
995      */
performTwoPointerGesture(Point startPoint1, Point startPoint2, Point endPoint1, Point endPoint2, int steps)996     public boolean performTwoPointerGesture(Point startPoint1, Point startPoint2, Point endPoint1,
997             Point endPoint2, int steps) {
998 
999         // avoid a divide by zero
1000         if(steps == 0)
1001             steps = 1;
1002 
1003         final float stepX1 = (endPoint1.x - startPoint1.x) / steps;
1004         final float stepY1 = (endPoint1.y - startPoint1.y) / steps;
1005         final float stepX2 = (endPoint2.x - startPoint2.x) / steps;
1006         final float stepY2 = (endPoint2.y - startPoint2.y) / steps;
1007 
1008         int eventX1, eventY1, eventX2, eventY2;
1009         eventX1 = startPoint1.x;
1010         eventY1 = startPoint1.y;
1011         eventX2 = startPoint2.x;
1012         eventY2 = startPoint2.y;
1013 
1014         // allocate for steps plus first down and last up
1015         PointerCoords[] points1 = new PointerCoords[steps + 2];
1016         PointerCoords[] points2 = new PointerCoords[steps + 2];
1017 
1018         // Include the first and last touch downs in the arrays of steps
1019         for (int i = 0; i < steps + 1; i++) {
1020             PointerCoords p1 = new PointerCoords();
1021             p1.x = eventX1;
1022             p1.y = eventY1;
1023             p1.pressure = 1;
1024             p1.size = 1;
1025             points1[i] = p1;
1026 
1027             PointerCoords p2 = new PointerCoords();
1028             p2.x = eventX2;
1029             p2.y = eventY2;
1030             p2.pressure = 1;
1031             p2.size = 1;
1032             points2[i] = p2;
1033 
1034             eventX1 += stepX1;
1035             eventY1 += stepY1;
1036             eventX2 += stepX2;
1037             eventY2 += stepY2;
1038         }
1039 
1040         // ending pointers coordinates
1041         PointerCoords p1 = new PointerCoords();
1042         p1.x = endPoint1.x;
1043         p1.y = endPoint1.y;
1044         p1.pressure = 1;
1045         p1.size = 1;
1046         points1[steps + 1] = p1;
1047 
1048         PointerCoords p2 = new PointerCoords();
1049         p2.x = endPoint2.x;
1050         p2.y = endPoint2.y;
1051         p2.pressure = 1;
1052         p2.size = 1;
1053         points2[steps + 1] = p2;
1054 
1055         return performMultiPointerGesture(points1, points2);
1056     }
1057 
1058     /**
1059      * Performs a multi-touch gesture. You must specify touch coordinates for
1060      * at least 2 pointers. Each pointer must have all of its touch steps
1061      * defined in an array of {@link PointerCoords}. You can use this method to
1062      * specify complex gestures, like circles and irregular shapes, where each
1063      * pointer may take a different path.
1064      *
1065      * To create a single point on a pointer's touch path:
1066      * <code>
1067      *       PointerCoords p = new PointerCoords();
1068      *       p.x = stepX;
1069      *       p.y = stepY;
1070      *       p.pressure = 1;
1071      *       p.size = 1;
1072      * </code>
1073      * @param touches represents the pointers' paths. Each {@link PointerCoords}
1074      * array represents a different pointer. Each {@link PointerCoords} in an
1075      * array element represents a touch point on a pointer's path.
1076      * @return <code>true</code> if all touch events for this gesture are injected successfully,
1077      *         <code>false</code> otherwise
1078      * @since API Level 18
1079      */
performMultiPointerGesture(PointerCoords[] ....touches)1080     public boolean performMultiPointerGesture(PointerCoords[] ...touches) {
1081         return getInteractionController().performMultiPointerGesture(touches);
1082     }
1083 }
1084