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