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.app.UiAutomation;
20import android.app.UiAutomation.AccessibilityEventFilter;
21import android.graphics.Point;
22import android.os.Build;
23import android.os.Environment;
24import android.os.RemoteException;
25import android.os.SystemClock;
26import android.util.DisplayMetrics;
27import android.util.Log;
28import android.view.Display;
29import android.view.KeyEvent;
30import android.view.Surface;
31import android.view.accessibility.AccessibilityEvent;
32import android.view.accessibility.AccessibilityNodeInfo;
33
34import java.io.File;
35import java.util.ArrayList;
36import java.util.HashMap;
37import java.util.List;
38import java.util.concurrent.TimeoutException;
39
40/**
41 * UiDevice provides access to state information about the device.
42 * You can also use this class to simulate user actions on the device,
43 * such as pressing the d-pad or pressing the Home and Menu buttons.
44 * @since API Level 16
45 * @deprecated New tests should be written using UI Automator 2.0 which is available as part of the
46 * Android Testing Support Library.
47 */
48@Deprecated
49public class UiDevice {
50    private static final String LOG_TAG = UiDevice.class.getSimpleName();
51
52    // Sometimes HOME and BACK key presses will generate no events if already on
53    // home page or there is nothing to go back to, Set low timeouts.
54    private static final long KEY_PRESS_EVENT_TIMEOUT = 1 * 1000;
55
56    // store for registered UiWatchers
57    private final HashMap<String, UiWatcher> mWatchers = new HashMap<String, UiWatcher>();
58    private final List<String> mWatchersTriggers = new ArrayList<String>();
59
60    // remember if we're executing in the context of a UiWatcher
61    private boolean mInWatcherContext = false;
62
63    // provides access the {@link QueryController} and {@link InteractionController}
64    private UiAutomatorBridge mUiAutomationBridge;
65
66    // reference to self
67    private static UiDevice sDevice;
68
69    private UiDevice() {
70        /* hide constructor */
71    }
72
73    /**
74     * @hide
75     */
76    public void initialize(UiAutomatorBridge uiAutomatorBridge) {
77        mUiAutomationBridge = uiAutomatorBridge;
78    }
79
80    boolean isInWatcherContext() {
81        return mInWatcherContext;
82    }
83
84    /**
85     * Provides access the {@link QueryController} and {@link InteractionController}
86     * @return {@link ShellUiAutomatorBridge}
87     */
88    UiAutomatorBridge getAutomatorBridge() {
89        if (mUiAutomationBridge == null) {
90            throw new RuntimeException("UiDevice not initialized");
91        }
92        return mUiAutomationBridge;
93    }
94
95    /**
96     * Enables or disables layout hierarchy compression.
97     *
98     * If compression is enabled, the layout hierarchy derived from the Acessibility
99     * framework will only contain nodes that are important for uiautomator
100     * testing. Any unnecessary surrounding layout nodes that make viewing
101     * and searching the hierarchy inefficient are removed.
102     *
103     * @param compressed true to enable compression; else, false to disable
104     * @since API Level 18
105     */
106    public void setCompressedLayoutHeirarchy(boolean compressed) {
107        getAutomatorBridge().setCompressedLayoutHierarchy(compressed);
108    }
109
110    /**
111     * Retrieves a singleton instance of UiDevice
112     *
113     * @return UiDevice instance
114     * @since API Level 16
115     */
116    public static UiDevice getInstance() {
117        if (sDevice == null) {
118            sDevice = new UiDevice();
119        }
120        return sDevice;
121    }
122
123    /**
124     * Returns the display size in dp (device-independent pixel)
125     *
126     * The returned display size is adjusted per screen rotation. Also this will return the actual
127     * size of the screen, rather than adjusted per system decorations (like status bar).
128     *
129     * @return a Point containing the display size in dp
130     */
131    public Point getDisplaySizeDp() {
132        Tracer.trace();
133        Display display = getAutomatorBridge().getDefaultDisplay();
134        Point p = new Point();
135        display.getRealSize(p);
136        DisplayMetrics metrics = new DisplayMetrics();
137        display.getRealMetrics(metrics);
138        float dpx = p.x / metrics.density;
139        float dpy = p.y / metrics.density;
140        p.x = Math.round(dpx);
141        p.y = Math.round(dpy);
142        return p;
143    }
144
145    /**
146     * Retrieves the product name of the device.
147     *
148     * This method provides information on what type of device the test is running on. This value is
149     * the same as returned by invoking #adb shell getprop ro.product.name.
150     *
151     * @return product name of the device
152     * @since API Level 17
153     */
154    public String getProductName() {
155        Tracer.trace();
156        return Build.PRODUCT;
157    }
158
159    /**
160     * Retrieves the text from the last UI traversal event received.
161     *
162     * You can use this method to read the contents in a WebView container
163     * because the accessibility framework fires events
164     * as each text is highlighted. You can write a test to perform
165     * directional arrow presses to focus on different elements inside a WebView,
166     * and call this method to get the text from each traversed element.
167     * If you are testing a view container that can return a reference to a
168     * Document Object Model (DOM) object, your test should use the view's
169     * DOM instead.
170     *
171     * @return text of the last traversal event, else return an empty string
172     * @since API Level 16
173     */
174    public String getLastTraversedText() {
175        Tracer.trace();
176        return getAutomatorBridge().getQueryController().getLastTraversedText();
177    }
178
179    /**
180     * Clears the text from the last UI traversal event.
181     * See {@link #getLastTraversedText()}.
182     * @since API Level 16
183     */
184    public void clearLastTraversedText() {
185        Tracer.trace();
186        getAutomatorBridge().getQueryController().clearLastTraversedText();
187    }
188
189    /**
190     * Simulates a short press on the MENU button.
191     * @return true if successful, else return false
192     * @since API Level 16
193     */
194    public boolean pressMenu() {
195        Tracer.trace();
196        waitForIdle();
197        return getAutomatorBridge().getInteractionController().sendKeyAndWaitForEvent(
198                KeyEvent.KEYCODE_MENU, 0, AccessibilityEvent.TYPE_WINDOW_CONTENT_CHANGED,
199                KEY_PRESS_EVENT_TIMEOUT);
200    }
201
202    /**
203     * Simulates a short press on the BACK button.
204     * @return true if successful, else return false
205     * @since API Level 16
206     */
207    public boolean pressBack() {
208        Tracer.trace();
209        waitForIdle();
210        return getAutomatorBridge().getInteractionController().sendKeyAndWaitForEvent(
211                KeyEvent.KEYCODE_BACK, 0, AccessibilityEvent.TYPE_WINDOW_CONTENT_CHANGED,
212                KEY_PRESS_EVENT_TIMEOUT);
213    }
214
215    /**
216     * Simulates a short press on the HOME button.
217     * @return true if successful, else return false
218     * @since API Level 16
219     */
220    public boolean pressHome() {
221        Tracer.trace();
222        waitForIdle();
223        return getAutomatorBridge().getInteractionController().sendKeyAndWaitForEvent(
224                KeyEvent.KEYCODE_HOME, 0, AccessibilityEvent.TYPE_WINDOW_CONTENT_CHANGED,
225                KEY_PRESS_EVENT_TIMEOUT);
226    }
227
228    /**
229     * Simulates a short press on the SEARCH button.
230     * @return true if successful, else return false
231     * @since API Level 16
232     */
233    public boolean pressSearch() {
234        Tracer.trace();
235        return pressKeyCode(KeyEvent.KEYCODE_SEARCH);
236    }
237
238    /**
239     * Simulates a short press on the CENTER button.
240     * @return true if successful, else return false
241     * @since API Level 16
242     */
243    public boolean pressDPadCenter() {
244        Tracer.trace();
245        return pressKeyCode(KeyEvent.KEYCODE_DPAD_CENTER);
246    }
247
248    /**
249     * Simulates a short press on the DOWN button.
250     * @return true if successful, else return false
251     * @since API Level 16
252     */
253    public boolean pressDPadDown() {
254        Tracer.trace();
255        return pressKeyCode(KeyEvent.KEYCODE_DPAD_DOWN);
256    }
257
258    /**
259     * Simulates a short press on the UP button.
260     * @return true if successful, else return false
261     * @since API Level 16
262     */
263    public boolean pressDPadUp() {
264        Tracer.trace();
265        return pressKeyCode(KeyEvent.KEYCODE_DPAD_UP);
266    }
267
268    /**
269     * Simulates a short press on the LEFT button.
270     * @return true if successful, else return false
271     * @since API Level 16
272     */
273    public boolean pressDPadLeft() {
274        Tracer.trace();
275        return pressKeyCode(KeyEvent.KEYCODE_DPAD_LEFT);
276    }
277
278    /**
279     * Simulates a short press on the RIGHT button.
280     * @return true if successful, else return false
281     * @since API Level 16
282     */
283    public boolean pressDPadRight() {
284        Tracer.trace();
285        return pressKeyCode(KeyEvent.KEYCODE_DPAD_RIGHT);
286    }
287
288    /**
289     * Simulates a short press on the DELETE key.
290     * @return true if successful, else return false
291     * @since API Level 16
292     */
293    public boolean pressDelete() {
294        Tracer.trace();
295        return pressKeyCode(KeyEvent.KEYCODE_DEL);
296    }
297
298    /**
299     * Simulates a short press on the ENTER key.
300     * @return true if successful, else return false
301     * @since API Level 16
302     */
303    public boolean pressEnter() {
304        Tracer.trace();
305        return pressKeyCode(KeyEvent.KEYCODE_ENTER);
306    }
307
308    /**
309     * Simulates a short press using a key code.
310     *
311     * See {@link KeyEvent}
312     * @return true if successful, else return false
313     * @since API Level 16
314     */
315    public boolean pressKeyCode(int keyCode) {
316        Tracer.trace(keyCode);
317        waitForIdle();
318        return getAutomatorBridge().getInteractionController().sendKey(keyCode, 0);
319    }
320
321    /**
322     * Simulates a short press using a key code.
323     *
324     * See {@link KeyEvent}.
325     * @param keyCode the key code of the event.
326     * @param metaState an integer in which each bit set to 1 represents a pressed meta key
327     * @return true if successful, else return false
328     * @since API Level 16
329     */
330    public boolean pressKeyCode(int keyCode, int metaState) {
331        Tracer.trace(keyCode, metaState);
332        waitForIdle();
333        return getAutomatorBridge().getInteractionController().sendKey(keyCode, metaState);
334    }
335
336    /**
337     * Simulates a short press on the Recent Apps button.
338     *
339     * @return true if successful, else return false
340     * @throws RemoteException
341     * @since API Level 16
342     */
343    public boolean pressRecentApps() throws RemoteException {
344        Tracer.trace();
345        waitForIdle();
346        return getAutomatorBridge().getInteractionController().toggleRecentApps();
347    }
348
349    /**
350     * Opens the notification shade.
351     *
352     * @return true if successful, else return false
353     * @since API Level 18
354     */
355    public boolean openNotification() {
356        Tracer.trace();
357        waitForIdle();
358        return  getAutomatorBridge().getInteractionController().openNotification();
359    }
360
361    /**
362     * Opens the Quick Settings shade.
363     *
364     * @return true if successful, else return false
365     * @since API Level 18
366     */
367    public boolean openQuickSettings() {
368        Tracer.trace();
369        waitForIdle();
370        return getAutomatorBridge().getInteractionController().openQuickSettings();
371    }
372
373    /**
374     * Gets the width of the display, in pixels. The width and height details
375     * are reported based on the current orientation of the display.
376     * @return width in pixels or zero on failure
377     * @since API Level 16
378     */
379    public int getDisplayWidth() {
380        Tracer.trace();
381        Display display = getAutomatorBridge().getDefaultDisplay();
382        Point p = new Point();
383        display.getSize(p);
384        return p.x;
385    }
386
387    /**
388     * Gets the height of the display, in pixels. The size is adjusted based
389     * on the current orientation of the display.
390     * @return height in pixels or zero on failure
391     * @since API Level 16
392     */
393    public int getDisplayHeight() {
394        Tracer.trace();
395        Display display = getAutomatorBridge().getDefaultDisplay();
396        Point p = new Point();
397        display.getSize(p);
398        return p.y;
399    }
400
401    /**
402     * Perform a click at arbitrary coordinates specified by the user
403     *
404     * @param x coordinate
405     * @param y coordinate
406     * @return true if the click succeeded else false
407     * @since API Level 16
408     */
409    public boolean click(int x, int y) {
410        Tracer.trace(x, y);
411        if (x >= getDisplayWidth() || y >= getDisplayHeight()) {
412            return (false);
413        }
414        return getAutomatorBridge().getInteractionController().clickNoSync(x, y);
415    }
416
417    /**
418     * Performs a swipe from one coordinate to another using the number of steps
419     * to determine smoothness and speed. Each step execution is throttled to 5ms
420     * per step. So for a 100 steps, the swipe will take about 1/2 second to complete.
421     *
422     * @param startX
423     * @param startY
424     * @param endX
425     * @param endY
426     * @param steps is the number of move steps sent to the system
427     * @return false if the operation fails or the coordinates are invalid
428     * @since API Level 16
429     */
430    public boolean swipe(int startX, int startY, int endX, int endY, int steps) {
431        Tracer.trace(startX, startY, endX, endY, steps);
432        return getAutomatorBridge().getInteractionController()
433                .swipe(startX, startY, endX, endY, steps);
434    }
435
436    /**
437     * Performs a swipe from one coordinate to another coordinate. You can control
438     * the smoothness and speed of the swipe by specifying the number of steps.
439     * Each step execution is throttled to 5 milliseconds per step, so for a 100
440     * steps, the swipe will take around 0.5 seconds to complete.
441     *
442     * @param startX X-axis value for the starting coordinate
443     * @param startY Y-axis value for the starting coordinate
444     * @param endX X-axis value for the ending coordinate
445     * @param endY Y-axis value for the ending coordinate
446     * @param steps is the number of steps for the swipe action
447     * @return true if swipe is performed, false if the operation fails
448     * or the coordinates are invalid
449     * @since API Level 18
450     */
451    public boolean drag(int startX, int startY, int endX, int endY, int steps) {
452        Tracer.trace(startX, startY, endX, endY, steps);
453        return getAutomatorBridge().getInteractionController()
454                .swipe(startX, startY, endX, endY, steps, true);
455    }
456
457    /**
458     * Performs a swipe between points in the Point array. Each step execution is throttled
459     * to 5ms per step. So for a 100 steps, the swipe will take about 1/2 second to complete
460     *
461     * @param segments is Point array containing at least one Point object
462     * @param segmentSteps steps to inject between two Points
463     * @return true on success
464     * @since API Level 16
465     */
466    public boolean swipe(Point[] segments, int segmentSteps) {
467        Tracer.trace(segments, segmentSteps);
468        return getAutomatorBridge().getInteractionController().swipe(segments, segmentSteps);
469    }
470
471    /**
472     * Waits for the current application to idle.
473     * Default wait timeout is 10 seconds
474     * @since API Level 16
475     */
476    public void waitForIdle() {
477        Tracer.trace();
478        waitForIdle(Configurator.getInstance().getWaitForIdleTimeout());
479    }
480
481    /**
482     * Waits for the current application to idle.
483     * @param timeout in milliseconds
484     * @since API Level 16
485     */
486    public void waitForIdle(long timeout) {
487        Tracer.trace(timeout);
488        getAutomatorBridge().waitForIdle(timeout);
489    }
490
491    /**
492     * Retrieves the last activity to report accessibility events.
493     * @deprecated The results returned should be considered unreliable
494     * @return String name of activity
495     * @since API Level 16
496     */
497    @Deprecated
498    public String getCurrentActivityName() {
499        Tracer.trace();
500        return getAutomatorBridge().getQueryController().getCurrentActivityName();
501    }
502
503    /**
504     * Retrieves the name of the last package to report accessibility events.
505     * @return String name of package
506     * @since API Level 16
507     */
508    public String getCurrentPackageName() {
509        Tracer.trace();
510        return getAutomatorBridge().getQueryController().getCurrentPackageName();
511    }
512
513    /**
514     * Registers a {@link UiWatcher} to run automatically when the testing framework is unable to
515     * find a match using a {@link UiSelector}. See {@link #runWatchers()}
516     *
517     * @param name to register the UiWatcher
518     * @param watcher {@link UiWatcher}
519     * @since API Level 16
520     */
521    public void registerWatcher(String name, UiWatcher watcher) {
522        Tracer.trace(name, watcher);
523        if (mInWatcherContext) {
524            throw new IllegalStateException("Cannot register new watcher from within another");
525        }
526        mWatchers.put(name, watcher);
527    }
528
529    /**
530     * Removes a previously registered {@link UiWatcher}.
531     *
532     * See {@link #registerWatcher(String, UiWatcher)}
533     * @param name used to register the UiWatcher
534     * @since API Level 16
535     */
536    public void removeWatcher(String name) {
537        Tracer.trace(name);
538        if (mInWatcherContext) {
539            throw new IllegalStateException("Cannot remove a watcher from within another");
540        }
541        mWatchers.remove(name);
542    }
543
544    /**
545     * This method forces all registered watchers to run.
546     * See {@link #registerWatcher(String, UiWatcher)}
547     * @since API Level 16
548     */
549    public void runWatchers() {
550        Tracer.trace();
551        if (mInWatcherContext) {
552            return;
553        }
554
555        for (String watcherName : mWatchers.keySet()) {
556            UiWatcher watcher = mWatchers.get(watcherName);
557            if (watcher != null) {
558                try {
559                    mInWatcherContext = true;
560                    if (watcher.checkForCondition()) {
561                        setWatcherTriggered(watcherName);
562                    }
563                } catch (Exception e) {
564                    Log.e(LOG_TAG, "Exceuting watcher: " + watcherName, e);
565                } finally {
566                    mInWatcherContext = false;
567                }
568            }
569        }
570    }
571
572    /**
573     * Resets a {@link UiWatcher} that has been triggered.
574     * If a UiWatcher runs and its {@link UiWatcher#checkForCondition()} call
575     * returned <code>true</code>, then the UiWatcher is considered triggered.
576     * See {@link #registerWatcher(String, UiWatcher)}
577     * @since API Level 16
578     */
579    public void resetWatcherTriggers() {
580        Tracer.trace();
581        mWatchersTriggers.clear();
582    }
583
584    /**
585     * Checks if a specific registered  {@link UiWatcher} has triggered.
586     * See {@link #registerWatcher(String, UiWatcher)}. If a UiWatcher runs and its
587     * {@link UiWatcher#checkForCondition()} call returned <code>true</code>, then
588     * the UiWatcher is considered triggered. This is helpful if a watcher is detecting errors
589     * from ANR or crash dialogs and the test needs to know if a UiWatcher has been triggered.
590     *
591     * @param watcherName
592     * @return true if triggered else false
593     * @since API Level 16
594     */
595    public boolean hasWatcherTriggered(String watcherName) {
596        Tracer.trace(watcherName);
597        return mWatchersTriggers.contains(watcherName);
598    }
599
600    /**
601     * Checks if any registered {@link UiWatcher} have triggered.
602     *
603     * See {@link #registerWatcher(String, UiWatcher)}
604     * See {@link #hasWatcherTriggered(String)}
605     * @since API Level 16
606     */
607    public boolean hasAnyWatcherTriggered() {
608        Tracer.trace();
609        return mWatchersTriggers.size() > 0;
610    }
611
612    /**
613     * Used internally by this class to set a {@link UiWatcher} state as triggered.
614     * @param watcherName
615     */
616    private void setWatcherTriggered(String watcherName) {
617        Tracer.trace(watcherName);
618        if (!hasWatcherTriggered(watcherName)) {
619            mWatchersTriggers.add(watcherName);
620        }
621    }
622
623    /**
624     * Check if the device is in its natural orientation. This is determined by checking if the
625     * orientation is at 0 or 180 degrees.
626     * @return true if it is in natural orientation
627     * @since API Level 17
628     */
629    public boolean isNaturalOrientation() {
630        Tracer.trace();
631        waitForIdle();
632        int ret = getAutomatorBridge().getRotation();
633        return ret == UiAutomation.ROTATION_FREEZE_0 ||
634                ret == UiAutomation.ROTATION_FREEZE_180;
635    }
636
637    /**
638     * Returns the current rotation of the display, as defined in {@link Surface}
639     * @since API Level 17
640     */
641    public int getDisplayRotation() {
642        Tracer.trace();
643        waitForIdle();
644        return getAutomatorBridge().getRotation();
645    }
646
647    /**
648     * Disables the sensors and freezes the device rotation at its
649     * current rotation state.
650     * @throws RemoteException
651     * @since API Level 16
652     */
653    public void freezeRotation() throws RemoteException {
654        Tracer.trace();
655        getAutomatorBridge().getInteractionController().freezeRotation();
656    }
657
658    /**
659     * Re-enables the sensors and un-freezes the device rotation allowing its contents
660     * to rotate with the device physical rotation. During a test execution, it is best to
661     * keep the device frozen in a specific orientation until the test case execution has completed.
662     * @throws RemoteException
663     */
664    public void unfreezeRotation() throws RemoteException {
665        Tracer.trace();
666        getAutomatorBridge().getInteractionController().unfreezeRotation();
667    }
668
669    /**
670     * Simulates orienting the device to the left and also freezes rotation
671     * by disabling the sensors.
672     *
673     * If you want to un-freeze the rotation and re-enable the sensors
674     * see {@link #unfreezeRotation()}.
675     * @throws RemoteException
676     * @since API Level 17
677     */
678    public void setOrientationLeft() throws RemoteException {
679        Tracer.trace();
680        getAutomatorBridge().getInteractionController().setRotationLeft();
681        waitForIdle(); // we don't need to check for idle on entry for this. We'll sync on exit
682    }
683
684    /**
685     * Simulates orienting the device to the right and also freezes rotation
686     * by disabling the sensors.
687     *
688     * If you want to un-freeze the rotation and re-enable the sensors
689     * see {@link #unfreezeRotation()}.
690     * @throws RemoteException
691     * @since API Level 17
692     */
693    public void setOrientationRight() throws RemoteException {
694        Tracer.trace();
695        getAutomatorBridge().getInteractionController().setRotationRight();
696        waitForIdle(); // we don't need to check for idle on entry for this. We'll sync on exit
697    }
698
699    /**
700     * Simulates orienting the device into its natural orientation and also freezes rotation
701     * by disabling the sensors.
702     *
703     * If you want to un-freeze the rotation and re-enable the sensors
704     * see {@link #unfreezeRotation()}.
705     * @throws RemoteException
706     * @since API Level 17
707     */
708    public void setOrientationNatural() throws RemoteException {
709        Tracer.trace();
710        getAutomatorBridge().getInteractionController().setRotationNatural();
711        waitForIdle(); // we don't need to check for idle on entry for this. We'll sync on exit
712    }
713
714    /**
715     * This method simulates pressing the power button if the screen is OFF else
716     * it does nothing if the screen is already ON.
717     *
718     * If the screen was OFF and it just got turned ON, this method will insert a 500ms delay
719     * to allow the device time to wake up and accept input.
720     * @throws RemoteException
721     * @since API Level 16
722     */
723    public void wakeUp() throws RemoteException {
724        Tracer.trace();
725        if(getAutomatorBridge().getInteractionController().wakeDevice()) {
726            // sync delay to allow the window manager to start accepting input
727            // after the device is awakened.
728            SystemClock.sleep(500);
729        }
730    }
731
732    /**
733     * Checks the power manager if the screen is ON.
734     *
735     * @return true if the screen is ON else false
736     * @throws RemoteException
737     * @since API Level 16
738     */
739    public boolean isScreenOn() throws RemoteException {
740        Tracer.trace();
741        return getAutomatorBridge().getInteractionController().isScreenOn();
742    }
743
744    /**
745     * This method simply presses the power button if the screen is ON else
746     * it does nothing if the screen is already OFF.
747     *
748     * @throws RemoteException
749     * @since API Level 16
750     */
751    public void sleep() throws RemoteException {
752        Tracer.trace();
753        getAutomatorBridge().getInteractionController().sleepDevice();
754    }
755
756    /**
757     * Helper method used for debugging to dump the current window's layout hierarchy.
758     * The file root location is /data/local/tmp
759     *
760     * @param fileName
761     * @since API Level 16
762     */
763    public void dumpWindowHierarchy(String fileName) {
764        Tracer.trace(fileName);
765        AccessibilityNodeInfo root =
766                getAutomatorBridge().getQueryController().getAccessibilityRootNode();
767        if(root != null) {
768            Display display = getAutomatorBridge().getDefaultDisplay();
769            Point size = new Point();
770            display.getSize(size);
771            AccessibilityNodeInfoDumper.dumpWindowToFile(root,
772                    new File(new File(Environment.getDataDirectory(), "local/tmp"), fileName),
773                    display.getRotation(), size.x, size.y);
774        }
775    }
776
777    /**
778     * Waits for a window content update event to occur.
779     *
780     * If a package name for the window is specified, but the current window
781     * does not have the same package name, the function returns immediately.
782     *
783     * @param packageName the specified window package name (can be <code>null</code>).
784     *        If <code>null</code>, a window update from any front-end window will end the wait
785     * @param timeout the timeout for the wait
786     *
787     * @return true if a window update occurred, false if timeout has elapsed or if the current
788     *         window does not have the specified package name
789     * @since API Level 16
790     */
791    public boolean waitForWindowUpdate(final String packageName, long timeout) {
792        Tracer.trace(packageName, timeout);
793        if (packageName != null) {
794            if (!packageName.equals(getCurrentPackageName())) {
795                return false;
796            }
797        }
798        Runnable emptyRunnable = new Runnable() {
799            @Override
800            public void run() {
801            }
802        };
803        AccessibilityEventFilter checkWindowUpdate = new AccessibilityEventFilter() {
804            @Override
805            public boolean accept(AccessibilityEvent t) {
806                if (t.getEventType() == AccessibilityEvent.TYPE_WINDOW_CONTENT_CHANGED) {
807                    return packageName == null || packageName.equals(t.getPackageName());
808                }
809                return false;
810            }
811        };
812        try {
813            getAutomatorBridge().executeCommandAndWaitForAccessibilityEvent(
814                    emptyRunnable, checkWindowUpdate, timeout);
815        } catch (TimeoutException e) {
816            return false;
817        } catch (Exception e) {
818            Log.e(LOG_TAG, "waitForWindowUpdate: general exception from bridge", e);
819            return false;
820        }
821        return true;
822    }
823
824    /**
825     * Take a screenshot of current window and store it as PNG
826     *
827     * Default scale of 1.0f (original size) and 90% quality is used
828     * The screenshot is adjusted per screen rotation
829     *
830     * @param storePath where the PNG should be written to
831     * @return true if screen shot is created successfully, false otherwise
832     * @since API Level 17
833     */
834    public boolean takeScreenshot(File storePath) {
835        Tracer.trace(storePath);
836        return takeScreenshot(storePath, 1.0f, 90);
837    }
838
839    /**
840     * Take a screenshot of current window and store it as PNG
841     *
842     * The screenshot is adjusted per screen rotation
843     *
844     * @param storePath where the PNG should be written to
845     * @param scale scale the screenshot down if needed; 1.0f for original size
846     * @param quality quality of the PNG compression; range: 0-100
847     * @return true if screen shot is created successfully, false otherwise
848     * @since API Level 17
849     */
850    public boolean takeScreenshot(File storePath, float scale, int quality) {
851        Tracer.trace(storePath, scale, quality);
852        return getAutomatorBridge().takeScreenshot(storePath, quality);
853    }
854}
855