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