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