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