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