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