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