2 * Copyright (C) 2013 The Android Open Source Project
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
8 * http://www.apache.org/licenses/LICENSE-2.0
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.
19 import android.accessibilityservice.AccessibilityService.Callbacks;
20 import android.accessibilityservice.AccessibilityService.IAccessibilityServiceClientWrapper;
21 import android.accessibilityservice.AccessibilityServiceInfo;
22 import android.accessibilityservice.IAccessibilityServiceClient;
23 import android.accessibilityservice.IAccessibilityServiceConnection;
24 import android.annotation.NonNull;
25 import android.annotation.TestApi;
26 import android.graphics.Bitmap;
27 import android.graphics.Canvas;
28 import android.graphics.Point;
29 import android.graphics.Region;
30 import android.hardware.display.DisplayManagerGlobal;
31 import android.os.IBinder;
32 import android.os.Looper;
33 import android.os.ParcelFileDescriptor;
34 import android.os.RemoteException;
35 import android.os.SystemClock;
36 import android.os.UserHandle;
37 import android.util.Log;
38 import android.view.Display;
39 import android.view.InputEvent;
40 import android.view.KeyEvent;
41 import android.view.Surface;
42 import android.view.WindowAnimationFrameStats;
43 import android.view.WindowContentFrameStats;
44 import android.view.accessibility.AccessibilityEvent;
45 import android.view.accessibility.AccessibilityInteractionClient;
46 import android.view.accessibility.AccessibilityNodeInfo;
47 import android.view.accessibility.AccessibilityWindowInfo;
48 import android.view.accessibility.IAccessibilityInteractionConnection;
49 import libcore.io.IoUtils;
51 import java.io.IOException;
52 import java.util.ArrayList;
53 import java.util.List;
54 import java.util.concurrent.TimeoutException;
57 * Class for interacting with the device's UI by simulation user actions and
58 * introspection of the screen content. It relies on the platform accessibility
59 * APIs to introspect the screen and to perform some actions on the remote view
60 * tree. It also allows injecting of arbitrary raw input events simulating user
61 * interaction with keyboards and touch devices. One can think of a UiAutomation
62 * as a special type of {@link android.accessibilityservice.AccessibilityService}
63 * which does not provide hooks for the service life cycle and exposes other
64 * APIs that are useful for UI test automation.
66 * The APIs exposed by this class are low-level to maximize flexibility when
67 * developing UI test automation tools and libraries. Generally, a UiAutomation
68 * client should be using a higher-level library or implement high-level functions.
69 * For example, performing a tap on the screen requires construction and injecting
70 * of a touch down and up events which have to be delivered to the system by a
71 * call to {@link #injectInputEvent(InputEvent, boolean)}.
74 * The APIs exposed by this class operate across applications enabling a client
75 * to write tests that cover use cases spanning over multiple applications. For
76 * example, going to the settings application to change a setting and then
77 * interacting with another application whose behavior depends on that setting.
80 public final class UiAutomation {
82 private static final String LOG_TAG = UiAutomation.class.getSimpleName();
84 private static final boolean DEBUG = false;
86 private static final int CONNECTION_ID_UNDEFINED = -1;
88 private static final long CONNECT_TIMEOUT_MILLIS = 5000;
90 /** Rotation constant: Unfreeze rotation (rotating the device changes its rotation state). */
91 public static final int ROTATION_UNFREEZE = -2;
93 /** Rotation constant: Freeze rotation to its current state. */
94 public static final int ROTATION_FREEZE_CURRENT = -1;
96 /** Rotation constant: Freeze rotation to 0 degrees (natural orientation) */
97 public static final int ROTATION_FREEZE_0 = Surface.ROTATION_0;
99 /** Rotation constant: Freeze rotation to 90 degrees . */
100 public static final int ROTATION_FREEZE_90 = Surface.ROTATION_90;
102 /** Rotation constant: Freeze rotation to 180 degrees . */
103 public static final int ROTATION_FREEZE_180 = Surface.ROTATION_180;
105 /** Rotation constant: Freeze rotation to 270 degrees . */
106 public static final int ROTATION_FREEZE_270 = Surface.ROTATION_270;
109 * UiAutomation supresses accessibility services by default. This flag specifies that
110 * existing accessibility services should continue to run, and that new ones may start.
111 * This flag is set when obtaining the UiAutomation from
112 * {@link Instrumentation#getUiAutomation(int)}.
114 public static final int FLAG_DONT_SUPPRESS_ACCESSIBILITY_SERVICES = 0x00000001;
116 private final Object mLock = new Object();
118 private final ArrayList<AccessibilityEvent> mEventQueue = new ArrayList<AccessibilityEvent>();
120 private final IAccessibilityServiceClient mClient;
122 private final IUiAutomationConnection mUiAutomationConnection;
124 private int mConnectionId = CONNECTION_ID_UNDEFINED;
126 private OnAccessibilityEventListener mOnAccessibilityEventListener;
128 private boolean mWaitingForEventDelivery;
130 private long mLastEventTimeMillis;
132 private boolean mIsConnecting;
134 private boolean mIsDestroyed;
139 * Listener for observing the {@link AccessibilityEvent} stream.
141 public static interface OnAccessibilityEventListener {
144 * Callback for receiving an {@link AccessibilityEvent}.
146 * <strong>Note:</strong> This method is <strong>NOT</strong> executed
147 * on the main test thread. The client is responsible for proper
151 * <strong>Note:</strong> It is responsibility of the client
152 * to recycle the received events to minimize object creation.
155 * @param event The received event.
157 public void onAccessibilityEvent(AccessibilityEvent event);
161 * Listener for filtering accessibility events.
163 public static interface AccessibilityEventFilter {
166 * Callback for determining whether an event is accepted or
167 * it is filtered out.
169 * @param event The event to process.
170 * @return True if the event is accepted, false to filter it out.
172 public boolean accept(AccessibilityEvent event);
176 * Creates a new instance that will handle callbacks from the accessibility
177 * layer on the thread of the provided looper and perform requests for privileged
178 * operations on the provided connection.
180 * @param looper The looper on which to execute accessibility callbacks.
181 * @param connection The connection for performing privileged operations.
185 public UiAutomation(Looper looper, IUiAutomationConnection connection) {
186 if (looper == null) {
187 throw new IllegalArgumentException("Looper cannot be null!");
189 if (connection == null) {
190 throw new IllegalArgumentException("Connection cannot be null!");
192 mUiAutomationConnection = connection;
193 mClient = new IAccessibilityServiceClientImpl(looper);
197 * Connects this UiAutomation to the accessibility introspection APIs with default flags.
201 public void connect() {
206 * Connects this UiAutomation to the accessibility introspection APIs.
208 * @param flags Any flags to apply to the automation as it gets connected
212 public void connect(int flags) {
213 synchronized (mLock) {
214 throwIfConnectedLocked();
218 mIsConnecting = true;
222 // Calling out without a lock held.
223 mUiAutomationConnection.connect(mClient, flags);
225 } catch (RemoteException re) {
226 throw new RuntimeException("Error while connecting UiAutomation", re);
229 synchronized (mLock) {
230 final long startTimeMillis = SystemClock.uptimeMillis();
233 if (isConnectedLocked()) {
236 final long elapsedTimeMillis = SystemClock.uptimeMillis() - startTimeMillis;
237 final long remainingTimeMillis = CONNECT_TIMEOUT_MILLIS - elapsedTimeMillis;
238 if (remainingTimeMillis <= 0) {
239 throw new RuntimeException("Error while connecting UiAutomation");
242 mLock.wait(remainingTimeMillis);
243 } catch (InterruptedException ie) {
248 mIsConnecting = false;
254 * Get the flags used to connect the service.
256 * @return The flags used to connect
260 public int getFlags() {
265 * Disconnects this UiAutomation from the accessibility introspection APIs.
269 public void disconnect() {
270 synchronized (mLock) {
272 throw new IllegalStateException(
273 "Cannot call disconnect() while connecting!");
275 throwIfNotConnectedLocked();
276 mConnectionId = CONNECTION_ID_UNDEFINED;
279 // Calling out without a lock held.
280 mUiAutomationConnection.disconnect();
281 } catch (RemoteException re) {
282 throw new RuntimeException("Error while disconnecting UiAutomation", re);
287 * The id of the {@link IAccessibilityInteractionConnection} for querying
288 * the screen content. This is here for legacy purposes since some tools use
289 * hidden APIs to introspect the screen.
293 public int getConnectionId() {
294 synchronized (mLock) {
295 throwIfNotConnectedLocked();
296 return mConnectionId;
301 * Reports if the object has been destroyed
303 * @return {code true} if the object has been destroyed.
307 public boolean isDestroyed() {
312 * Sets a callback for observing the stream of {@link AccessibilityEvent}s.
314 * @param listener The callback.
316 public void setOnAccessibilityEventListener(OnAccessibilityEventListener listener) {
317 synchronized (mLock) {
318 mOnAccessibilityEventListener = listener;
323 * Destroy this UiAutomation. After calling this method, attempting to use the object will
329 public void destroy() {
335 * Performs a global action. Such an action can be performed at any moment
336 * regardless of the current application or user location in that application.
337 * For example going back, going home, opening recents, etc.
339 * @param action The action to perform.
340 * @return Whether the action was successfully performed.
342 * @see android.accessibilityservice.AccessibilityService#GLOBAL_ACTION_BACK
343 * @see android.accessibilityservice.AccessibilityService#GLOBAL_ACTION_HOME
344 * @see android.accessibilityservice.AccessibilityService#GLOBAL_ACTION_NOTIFICATIONS
345 * @see android.accessibilityservice.AccessibilityService#GLOBAL_ACTION_RECENTS
347 public final boolean performGlobalAction(int action) {
348 final IAccessibilityServiceConnection connection;
349 synchronized (mLock) {
350 throwIfNotConnectedLocked();
351 connection = AccessibilityInteractionClient.getInstance()
352 .getConnection(mConnectionId);
354 // Calling out without a lock held.
355 if (connection != null) {
357 return connection.performGlobalAction(action);
358 } catch (RemoteException re) {
359 Log.w(LOG_TAG, "Error while calling performGlobalAction", re);
366 * Find the view that has the specified focus type. The search is performed
367 * across all windows.
369 * <strong>Note:</strong> In order to access the windows you have to opt-in
370 * to retrieve the interactive windows by setting the
371 * {@link AccessibilityServiceInfo#FLAG_RETRIEVE_INTERACTIVE_WINDOWS} flag.
372 * Otherwise, the search will be performed only in the active window.
375 * @param focus The focus to find. One of {@link AccessibilityNodeInfo#FOCUS_INPUT} or
376 * {@link AccessibilityNodeInfo#FOCUS_ACCESSIBILITY}.
377 * @return The node info of the focused view or null.
379 * @see AccessibilityNodeInfo#FOCUS_INPUT
380 * @see AccessibilityNodeInfo#FOCUS_ACCESSIBILITY
382 public AccessibilityNodeInfo findFocus(int focus) {
383 return AccessibilityInteractionClient.getInstance().findFocus(mConnectionId,
384 AccessibilityNodeInfo.ANY_WINDOW_ID, AccessibilityNodeInfo.ROOT_NODE_ID, focus);
388 * Gets the an {@link AccessibilityServiceInfo} describing this UiAutomation.
389 * This method is useful if one wants to change some of the dynamically
390 * configurable properties at runtime.
392 * @return The accessibility service info.
394 * @see AccessibilityServiceInfo
396 public final AccessibilityServiceInfo getServiceInfo() {
397 final IAccessibilityServiceConnection connection;
398 synchronized (mLock) {
399 throwIfNotConnectedLocked();
400 connection = AccessibilityInteractionClient.getInstance()
401 .getConnection(mConnectionId);
403 // Calling out without a lock held.
404 if (connection != null) {
406 return connection.getServiceInfo();
407 } catch (RemoteException re) {
408 Log.w(LOG_TAG, "Error while getting AccessibilityServiceInfo", re);
415 * Sets the {@link AccessibilityServiceInfo} that describes how this
416 * UiAutomation will be handled by the platform accessibility layer.
418 * @param info The info.
420 * @see AccessibilityServiceInfo
422 public final void setServiceInfo(AccessibilityServiceInfo info) {
423 final IAccessibilityServiceConnection connection;
424 synchronized (mLock) {
425 throwIfNotConnectedLocked();
426 AccessibilityInteractionClient.getInstance().clearCache();
427 connection = AccessibilityInteractionClient.getInstance()
428 .getConnection(mConnectionId);
430 // Calling out without a lock held.
431 if (connection != null) {
433 connection.setServiceInfo(info);
434 } catch (RemoteException re) {
435 Log.w(LOG_TAG, "Error while setting AccessibilityServiceInfo", re);
441 * Gets the windows on the screen. This method returns only the windows
442 * that a sighted user can interact with, as opposed to all windows.
443 * For example, if there is a modal dialog shown and the user cannot touch
444 * anything behind it, then only the modal window will be reported
445 * (assuming it is the top one). For convenience the returned windows
446 * are ordered in a descending layer order, which is the windows that
447 * are higher in the Z-order are reported first.
449 * <strong>Note:</strong> In order to access the windows you have to opt-in
450 * to retrieve the interactive windows by setting the
451 * {@link AccessibilityServiceInfo#FLAG_RETRIEVE_INTERACTIVE_WINDOWS} flag.
454 * @return The windows if there are windows such, otherwise an empty list.
456 public List<AccessibilityWindowInfo> getWindows() {
457 final int connectionId;
458 synchronized (mLock) {
459 throwIfNotConnectedLocked();
460 connectionId = mConnectionId;
462 // Calling out without a lock held.
463 return AccessibilityInteractionClient.getInstance()
464 .getWindows(connectionId);
468 * Gets the root {@link AccessibilityNodeInfo} in the active window.
470 * @return The root info.
472 public AccessibilityNodeInfo getRootInActiveWindow() {
473 final int connectionId;
474 synchronized (mLock) {
475 throwIfNotConnectedLocked();
476 connectionId = mConnectionId;
478 // Calling out without a lock held.
479 return AccessibilityInteractionClient.getInstance()
480 .getRootInActiveWindow(connectionId);
484 * A method for injecting an arbitrary input event.
486 * <strong>Note:</strong> It is caller's responsibility to recycle the event.
488 * @param event The event to inject.
489 * @param sync Whether to inject the event synchronously.
490 * @return Whether event injection succeeded.
492 public boolean injectInputEvent(InputEvent event, boolean sync) {
493 synchronized (mLock) {
494 throwIfNotConnectedLocked();
498 Log.i(LOG_TAG, "Injecting: " + event + " sync: " + sync);
500 // Calling out without a lock held.
501 return mUiAutomationConnection.injectInputEvent(event, sync);
502 } catch (RemoteException re) {
503 Log.e(LOG_TAG, "Error while injecting input event!", re);
509 * Sets the device rotation. A client can freeze the rotation in
510 * desired state or freeze the rotation to its current state or
511 * unfreeze the rotation (rotating the device changes its rotation
514 * @param rotation The desired rotation.
515 * @return Whether the rotation was set successfully.
517 * @see #ROTATION_FREEZE_0
518 * @see #ROTATION_FREEZE_90
519 * @see #ROTATION_FREEZE_180
520 * @see #ROTATION_FREEZE_270
521 * @see #ROTATION_FREEZE_CURRENT
522 * @see #ROTATION_UNFREEZE
524 public boolean setRotation(int rotation) {
525 synchronized (mLock) {
526 throwIfNotConnectedLocked();
529 case ROTATION_FREEZE_0:
530 case ROTATION_FREEZE_90:
531 case ROTATION_FREEZE_180:
532 case ROTATION_FREEZE_270:
533 case ROTATION_UNFREEZE:
534 case ROTATION_FREEZE_CURRENT: {
536 // Calling out without a lock held.
537 mUiAutomationConnection.setRotation(rotation);
539 } catch (RemoteException re) {
540 Log.e(LOG_TAG, "Error while setting rotation!", re);
544 throw new IllegalArgumentException("Invalid rotation.");
550 * Executes a command and waits for a specific accessibility event up to a
551 * given wait timeout. To detect a sequence of events one can implement a
552 * filter that keeps track of seen events of the expected sequence and
553 * returns true after the last event of that sequence is received.
555 * <strong>Note:</strong> It is caller's responsibility to recycle the returned event.
557 * @param command The command to execute.
558 * @param filter Filter that recognizes the expected event.
559 * @param timeoutMillis The wait timeout in milliseconds.
561 * @throws TimeoutException If the expected event is not received within the timeout.
563 public AccessibilityEvent executeAndWaitForEvent(Runnable command,
564 AccessibilityEventFilter filter, long timeoutMillis) throws TimeoutException {
565 // Acquire the lock and prepare for receiving events.
566 synchronized (mLock) {
567 throwIfNotConnectedLocked();
569 // Prepare to wait for an event.
570 mWaitingForEventDelivery = true;
573 // Note: We have to release the lock since calling out with this lock held
574 // can bite. We will correctly filter out events from other interactions,
575 // so starting to collect events before running the action is just fine.
577 // We will ignore events from previous interactions.
578 final long executionStartTimeMillis = SystemClock.uptimeMillis();
579 // Execute the command *without* the lock being held.
582 // Acquire the lock and wait for the event.
583 synchronized (mLock) {
585 // Wait for the event.
586 final long startTimeMillis = SystemClock.uptimeMillis();
588 // Drain the event queue
589 while (!mEventQueue.isEmpty()) {
590 AccessibilityEvent event = mEventQueue.remove(0);
591 // Ignore events from previous interactions.
592 if (event.getEventTime() < executionStartTimeMillis) {
595 if (filter.accept(event)) {
600 // Check if timed out and if not wait.
601 final long elapsedTimeMillis = SystemClock.uptimeMillis() - startTimeMillis;
602 final long remainingTimeMillis = timeoutMillis - elapsedTimeMillis;
603 if (remainingTimeMillis <= 0) {
604 throw new TimeoutException("Expected event not received within: "
605 + timeoutMillis + " ms.");
608 mLock.wait(remainingTimeMillis);
609 } catch (InterruptedException ie) {
614 mWaitingForEventDelivery = false;
622 * Waits for the accessibility event stream to become idle, which is not to
623 * have received an accessibility event within <code>idleTimeoutMillis</code>.
624 * The total time spent to wait for an idle accessibility event stream is bounded
625 * by the <code>globalTimeoutMillis</code>.
627 * @param idleTimeoutMillis The timeout in milliseconds between two events
628 * to consider the device idle.
629 * @param globalTimeoutMillis The maximal global timeout in milliseconds in
630 * which to wait for an idle state.
632 * @throws TimeoutException If no idle state was detected within
633 * <code>globalTimeoutMillis.</code>
635 public void waitForIdle(long idleTimeoutMillis, long globalTimeoutMillis)
636 throws TimeoutException {
637 synchronized (mLock) {
638 throwIfNotConnectedLocked();
640 final long startTimeMillis = SystemClock.uptimeMillis();
641 if (mLastEventTimeMillis <= 0) {
642 mLastEventTimeMillis = startTimeMillis;
646 final long currentTimeMillis = SystemClock.uptimeMillis();
647 // Did we get idle state within the global timeout?
648 final long elapsedGlobalTimeMillis = currentTimeMillis - startTimeMillis;
649 final long remainingGlobalTimeMillis =
650 globalTimeoutMillis - elapsedGlobalTimeMillis;
651 if (remainingGlobalTimeMillis <= 0) {
652 throw new TimeoutException("No idle state with idle timeout: "
653 + idleTimeoutMillis + " within global timeout: "
654 + globalTimeoutMillis);
656 // Did we get an idle state within the idle timeout?
657 final long elapsedIdleTimeMillis = currentTimeMillis - mLastEventTimeMillis;
658 final long remainingIdleTimeMillis = idleTimeoutMillis - elapsedIdleTimeMillis;
659 if (remainingIdleTimeMillis <= 0) {
663 mLock.wait(remainingIdleTimeMillis);
664 } catch (InterruptedException ie) {
672 * Takes a screenshot.
674 * @return The screenshot bitmap on success, null otherwise.
676 public Bitmap takeScreenshot() {
677 synchronized (mLock) {
678 throwIfNotConnectedLocked();
680 Display display = DisplayManagerGlobal.getInstance()
681 .getRealDisplay(Display.DEFAULT_DISPLAY);
682 Point displaySize = new Point();
683 display.getRealSize(displaySize);
684 final int displayWidth = displaySize.x;
685 final int displayHeight = displaySize.y;
687 final float screenshotWidth;
688 final float screenshotHeight;
690 final int rotation = display.getRotation();
692 case ROTATION_FREEZE_0: {
693 screenshotWidth = displayWidth;
694 screenshotHeight = displayHeight;
696 case ROTATION_FREEZE_90: {
697 screenshotWidth = displayHeight;
698 screenshotHeight = displayWidth;
700 case ROTATION_FREEZE_180: {
701 screenshotWidth = displayWidth;
702 screenshotHeight = displayHeight;
704 case ROTATION_FREEZE_270: {
705 screenshotWidth = displayHeight;
706 screenshotHeight = displayWidth;
709 throw new IllegalArgumentException("Invalid rotation: "
714 // Take the screenshot
715 Bitmap screenShot = null;
717 // Calling out without a lock held.
718 screenShot = mUiAutomationConnection.takeScreenshot((int) screenshotWidth,
719 (int) screenshotHeight);
720 if (screenShot == null) {
723 } catch (RemoteException re) {
724 Log.e(LOG_TAG, "Error while taking screnshot!", re);
728 // Rotate the screenshot to the current orientation
729 if (rotation != ROTATION_FREEZE_0) {
730 Bitmap unrotatedScreenShot = Bitmap.createBitmap(displayWidth, displayHeight,
731 Bitmap.Config.ARGB_8888);
732 Canvas canvas = new Canvas(unrotatedScreenShot);
733 canvas.translate(unrotatedScreenShot.getWidth() / 2,
734 unrotatedScreenShot.getHeight() / 2);
735 canvas.rotate(getDegreesForRotation(rotation));
736 canvas.translate(- screenshotWidth / 2, - screenshotHeight / 2);
737 canvas.drawBitmap(screenShot, 0, 0, null);
738 canvas.setBitmap(null);
739 screenShot.recycle();
740 screenShot = unrotatedScreenShot;
744 screenShot.setHasAlpha(false);
750 * Sets whether this UiAutomation to run in a "monkey" mode. Applications can query whether
751 * they are executed in a "monkey" mode, i.e. run by a test framework, and avoid doing
752 * potentially undesirable actions such as calling 911 or posting on public forums etc.
754 * @param enable whether to run in a "monkey" mode or not. Default is not.
755 * @see ActivityManager#isUserAMonkey()
757 public void setRunAsMonkey(boolean enable) {
758 synchronized (mLock) {
759 throwIfNotConnectedLocked();
762 ActivityManagerNative.getDefault().setUserIsMonkey(enable);
763 } catch (RemoteException re) {
764 Log.e(LOG_TAG, "Error while setting run as monkey!", re);
769 * Clears the frame statistics for the content of a given window. These
770 * statistics contain information about the most recently rendered content
773 * @param windowId The window id.
774 * @return Whether the window is present and its frame statistics
777 * @see android.view.WindowContentFrameStats
778 * @see #getWindowContentFrameStats(int)
780 * @see AccessibilityWindowInfo#getId() AccessibilityWindowInfo.getId()
782 public boolean clearWindowContentFrameStats(int windowId) {
783 synchronized (mLock) {
784 throwIfNotConnectedLocked();
788 Log.i(LOG_TAG, "Clearing content frame stats for window: " + windowId);
790 // Calling out without a lock held.
791 return mUiAutomationConnection.clearWindowContentFrameStats(windowId);
792 } catch (RemoteException re) {
793 Log.e(LOG_TAG, "Error clearing window content frame stats!", re);
799 * Gets the frame statistics for a given window. These statistics contain
800 * information about the most recently rendered content frames.
802 * A typical usage requires clearing the window frame statistics via {@link
803 * #clearWindowContentFrameStats(int)} followed by an interaction with the UI and
804 * finally getting the window frame statistics via calling this method.
807 * // Assume we have at least one window.
808 * final int windowId = getWindows().get(0).getId();
810 * // Start with a clean slate.
811 * uiAutimation.clearWindowContentFrameStats(windowId);
813 * // Do stuff with the UI.
815 * // Get the frame statistics.
816 * WindowContentFrameStats stats = uiAutomation.getWindowContentFrameStats(windowId);
819 * @param windowId The window id.
820 * @return The window frame statistics, or null if the window is not present.
822 * @see android.view.WindowContentFrameStats
823 * @see #clearWindowContentFrameStats(int)
825 * @see AccessibilityWindowInfo#getId() AccessibilityWindowInfo.getId()
827 public WindowContentFrameStats getWindowContentFrameStats(int windowId) {
828 synchronized (mLock) {
829 throwIfNotConnectedLocked();
833 Log.i(LOG_TAG, "Getting content frame stats for window: " + windowId);
835 // Calling out without a lock held.
836 return mUiAutomationConnection.getWindowContentFrameStats(windowId);
837 } catch (RemoteException re) {
838 Log.e(LOG_TAG, "Error getting window content frame stats!", re);
844 * Clears the window animation rendering statistics. These statistics contain
845 * information about the most recently rendered window animation frames, i.e.
846 * for window transition animations.
848 * @see android.view.WindowAnimationFrameStats
849 * @see #getWindowAnimationFrameStats()
850 * @see android.R.styleable#WindowAnimation
852 public void clearWindowAnimationFrameStats() {
853 synchronized (mLock) {
854 throwIfNotConnectedLocked();
858 Log.i(LOG_TAG, "Clearing window animation frame stats");
860 // Calling out without a lock held.
861 mUiAutomationConnection.clearWindowAnimationFrameStats();
862 } catch (RemoteException re) {
863 Log.e(LOG_TAG, "Error clearing window animation frame stats!", re);
868 * Gets the window animation frame statistics. These statistics contain
869 * information about the most recently rendered window animation frames, i.e.
870 * for window transition animations.
873 * A typical usage requires clearing the window animation frame statistics via
874 * {@link #clearWindowAnimationFrameStats()} followed by an interaction that causes
875 * a window transition which uses a window animation and finally getting the window
876 * animation frame statistics by calling this method.
879 * // Start with a clean slate.
880 * uiAutimation.clearWindowAnimationFrameStats();
882 * // Do stuff to trigger a window transition.
884 * // Get the frame statistics.
885 * WindowAnimationFrameStats stats = uiAutomation.getWindowAnimationFrameStats();
888 * @return The window animation frame statistics.
890 * @see android.view.WindowAnimationFrameStats
891 * @see #clearWindowAnimationFrameStats()
892 * @see android.R.styleable#WindowAnimation
894 public WindowAnimationFrameStats getWindowAnimationFrameStats() {
895 synchronized (mLock) {
896 throwIfNotConnectedLocked();
900 Log.i(LOG_TAG, "Getting window animation frame stats");
902 // Calling out without a lock held.
903 return mUiAutomationConnection.getWindowAnimationFrameStats();
904 } catch (RemoteException re) {
905 Log.e(LOG_TAG, "Error getting window animation frame stats!", re);
911 * Grants a runtime permission to a package for a user.
912 * @param packageName The package to which to grant.
913 * @param permission The permission to grant.
914 * @return Whether granting succeeded.
919 public boolean grantRuntimePermission(String packageName, String permission,
920 UserHandle userHandle) {
921 synchronized (mLock) {
922 throwIfNotConnectedLocked();
926 Log.i(LOG_TAG, "Granting runtime permission");
928 // Calling out without a lock held.
929 mUiAutomationConnection.grantRuntimePermission(packageName,
930 permission, userHandle.getIdentifier());
931 // TODO: The package manager API should return boolean.
933 } catch (RemoteException re) {
934 Log.e(LOG_TAG, "Error granting runtime permission", re);
940 * Revokes a runtime permission from a package for a user.
941 * @param packageName The package from which to revoke.
942 * @param permission The permission to revoke.
943 * @return Whether revoking succeeded.
948 public boolean revokeRuntimePermission(String packageName, String permission,
949 UserHandle userHandle) {
950 synchronized (mLock) {
951 throwIfNotConnectedLocked();
955 Log.i(LOG_TAG, "Revoking runtime permission");
957 // Calling out without a lock held.
958 mUiAutomationConnection.revokeRuntimePermission(packageName,
959 permission, userHandle.getIdentifier());
960 // TODO: The package manager API should return boolean.
962 } catch (RemoteException re) {
963 Log.e(LOG_TAG, "Error revoking runtime permission", re);
969 * Executes a shell command. This method returs a file descriptor that points
970 * to the standard output stream. The command execution is similar to running
971 * "adb shell <command>" from a host connected to the device.
973 * <strong>Note:</strong> It is your responsibility to close the retunred file
974 * descriptor once you are done reading.
977 * @param command The command to execute.
978 * @return A file descriptor to the standard output stream.
980 public ParcelFileDescriptor executeShellCommand(String command) {
981 synchronized (mLock) {
982 throwIfNotConnectedLocked();
985 ParcelFileDescriptor source = null;
986 ParcelFileDescriptor sink = null;
989 ParcelFileDescriptor[] pipe = ParcelFileDescriptor.createPipe();
993 // Calling out without a lock held.
994 mUiAutomationConnection.executeShellCommand(command, sink);
995 } catch (IOException ioe) {
996 Log.e(LOG_TAG, "Error executing shell command!", ioe);
997 } catch (RemoteException re) {
998 Log.e(LOG_TAG, "Error executing shell command!", re);
1000 IoUtils.closeQuietly(sink);
1006 private static float getDegreesForRotation(int value) {
1008 case Surface.ROTATION_90: {
1011 case Surface.ROTATION_180: {
1014 case Surface.ROTATION_270: {
1022 private boolean isConnectedLocked() {
1023 return mConnectionId != CONNECTION_ID_UNDEFINED;
1026 private void throwIfConnectedLocked() {
1027 if (mConnectionId != CONNECTION_ID_UNDEFINED) {
1028 throw new IllegalStateException("UiAutomation not connected!");
1032 private void throwIfNotConnectedLocked() {
1033 if (!isConnectedLocked()) {
1034 throw new IllegalStateException("UiAutomation not connected!");
1038 private class IAccessibilityServiceClientImpl extends IAccessibilityServiceClientWrapper {
1040 public IAccessibilityServiceClientImpl(Looper looper) {
1041 super(null, looper, new Callbacks() {
1043 public void init(int connectionId, IBinder windowToken) {
1044 synchronized (mLock) {
1045 mConnectionId = connectionId;
1051 public void onServiceConnected() {
1056 public void onInterrupt() {
1061 public boolean onGesture(int gestureId) {
1067 public void onAccessibilityEvent(AccessibilityEvent event) {
1068 synchronized (mLock) {
1069 mLastEventTimeMillis = event.getEventTime();
1070 if (mWaitingForEventDelivery) {
1071 mEventQueue.add(AccessibilityEvent.obtain(event));
1075 // Calling out only without a lock held.
1076 final OnAccessibilityEventListener listener = mOnAccessibilityEventListener;
1077 if (listener != null) {
1078 listener.onAccessibilityEvent(AccessibilityEvent.obtain(event));
1083 public boolean onKeyEvent(KeyEvent event) {
1088 public void onMagnificationChanged(@NonNull Region region,
1089 float scale, float centerX, float centerY) {
1094 public void onSoftKeyboardShowModeChanged(int showMode) {
1099 public void onPerformGestureResult(int sequence, boolean completedSuccessfully) {