blob: efed2e09733e9f475444a2fc91871bdb7ee5b9df [file] [log] [blame]
Svetoslav Ganov80943d82013-01-02 10:25:37 -08001/*
2 * Copyright (C) 2013 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 android.app;
18
19import android.accessibilityservice.AccessibilityService.Callbacks;
20import android.accessibilityservice.AccessibilityService.IAccessibilityServiceClientWrapper;
Svetoslavbbfa5852013-02-11 19:38:12 -080021import android.accessibilityservice.AccessibilityServiceInfo;
Svetoslav Ganov80943d82013-01-02 10:25:37 -080022import android.accessibilityservice.IAccessibilityServiceClient;
Svetoslavbbfa5852013-02-11 19:38:12 -080023import android.accessibilityservice.IAccessibilityServiceConnection;
Svetoslav Ganov80943d82013-01-02 10:25:37 -080024import android.graphics.Bitmap;
25import android.graphics.Canvas;
26import android.graphics.Point;
27import android.hardware.display.DisplayManagerGlobal;
Svetoslav3a5c7212014-10-14 09:54:26 -070028import android.os.IBinder;
Svetoslav Ganov80943d82013-01-02 10:25:37 -080029import android.os.Looper;
Svetoslav121e0c02014-05-08 18:51:25 -070030import android.os.ParcelFileDescriptor;
Svetoslav Ganov80943d82013-01-02 10:25:37 -080031import android.os.RemoteException;
32import android.os.SystemClock;
Svet Ganov52153f42015-08-11 08:59:12 -070033import android.os.UserHandle;
Svetoslav Ganov80943d82013-01-02 10:25:37 -080034import android.util.Log;
35import android.view.Display;
36import android.view.InputEvent;
Svetoslavc4fccd12013-04-09 12:58:41 -070037import android.view.KeyEvent;
Svetoslav Ganov80943d82013-01-02 10:25:37 -080038import android.view.Surface;
Svetoslav1376d602014-03-13 11:17:26 -070039import android.view.WindowAnimationFrameStats;
40import android.view.WindowContentFrameStats;
Svetoslav Ganov80943d82013-01-02 10:25:37 -080041import android.view.accessibility.AccessibilityEvent;
42import android.view.accessibility.AccessibilityInteractionClient;
43import android.view.accessibility.AccessibilityNodeInfo;
Svetoslav8e3feb12014-02-24 13:46:47 -080044import android.view.accessibility.AccessibilityWindowInfo;
Svetoslav Ganov80943d82013-01-02 10:25:37 -080045import android.view.accessibility.IAccessibilityInteractionConnection;
Svetoslav121e0c02014-05-08 18:51:25 -070046import libcore.io.IoUtils;
Svetoslav Ganov80943d82013-01-02 10:25:37 -080047
Svetoslav121e0c02014-05-08 18:51:25 -070048import java.io.IOException;
Svetoslav Ganov80943d82013-01-02 10:25:37 -080049import java.util.ArrayList;
Svetoslav8e3feb12014-02-24 13:46:47 -080050import java.util.List;
Svetoslav Ganov80943d82013-01-02 10:25:37 -080051import java.util.concurrent.TimeoutException;
52
53/**
54 * Class for interacting with the device's UI by simulation user actions and
55 * introspection of the screen content. It relies on the platform accessibility
56 * APIs to introspect the screen and to perform some actions on the remote view
57 * tree. It also allows injecting of arbitrary raw input events simulating user
Svetoslavbbfa5852013-02-11 19:38:12 -080058 * interaction with keyboards and touch devices. One can think of a UiAutomation
59 * as a special type of {@link android.accessibilityservice.AccessibilityService}
60 * which does not provide hooks for the service life cycle and exposes other
61 * APIs that are useful for UI test automation.
Svetoslav Ganov80943d82013-01-02 10:25:37 -080062 * <p>
63 * The APIs exposed by this class are low-level to maximize flexibility when
64 * developing UI test automation tools and libraries. Generally, a UiAutomation
65 * client should be using a higher-level library or implement high-level functions.
66 * For example, performing a tap on the screen requires construction and injecting
67 * of a touch down and up events which have to be delivered to the system by a
68 * call to {@link #injectInputEvent(InputEvent, boolean)}.
69 * </p>
70 * <p>
71 * The APIs exposed by this class operate across applications enabling a client
72 * to write tests that cover use cases spanning over multiple applications. For
73 * example, going to the settings application to change a setting and then
74 * interacting with another application whose behavior depends on that setting.
75 * </p>
76 */
77public final class UiAutomation {
78
79 private static final String LOG_TAG = UiAutomation.class.getSimpleName();
80
81 private static final boolean DEBUG = false;
82
83 private static final int CONNECTION_ID_UNDEFINED = -1;
84
85 private static final long CONNECT_TIMEOUT_MILLIS = 5000;
86
87 /** Rotation constant: Unfreeze rotation (rotating the device changes its rotation state). */
88 public static final int ROTATION_UNFREEZE = -2;
89
90 /** Rotation constant: Freeze rotation to its current state. */
91 public static final int ROTATION_FREEZE_CURRENT = -1;
92
93 /** Rotation constant: Freeze rotation to 0 degrees (natural orientation) */
94 public static final int ROTATION_FREEZE_0 = Surface.ROTATION_0;
95
96 /** Rotation constant: Freeze rotation to 90 degrees . */
97 public static final int ROTATION_FREEZE_90 = Surface.ROTATION_90;
98
99 /** Rotation constant: Freeze rotation to 180 degrees . */
100 public static final int ROTATION_FREEZE_180 = Surface.ROTATION_180;
101
102 /** Rotation constant: Freeze rotation to 270 degrees . */
103 public static final int ROTATION_FREEZE_270 = Surface.ROTATION_270;
104
105 private final Object mLock = new Object();
106
107 private final ArrayList<AccessibilityEvent> mEventQueue = new ArrayList<AccessibilityEvent>();
108
109 private final IAccessibilityServiceClient mClient;
110
111 private final IUiAutomationConnection mUiAutomationConnection;
112
113 private int mConnectionId = CONNECTION_ID_UNDEFINED;
114
115 private OnAccessibilityEventListener mOnAccessibilityEventListener;
116
117 private boolean mWaitingForEventDelivery;
118
119 private long mLastEventTimeMillis;
120
121 private boolean mIsConnecting;
122
123 /**
124 * Listener for observing the {@link AccessibilityEvent} stream.
125 */
126 public static interface OnAccessibilityEventListener {
127
128 /**
129 * Callback for receiving an {@link AccessibilityEvent}.
130 * <p>
131 * <strong>Note:</strong> This method is <strong>NOT</strong> executed
132 * on the main test thread. The client is responsible for proper
133 * synchronization.
134 * </p>
135 * <p>
136 * <strong>Note:</strong> It is responsibility of the client
137 * to recycle the received events to minimize object creation.
138 * </p>
139 *
140 * @param event The received event.
141 */
142 public void onAccessibilityEvent(AccessibilityEvent event);
143 }
144
145 /**
Svetoslav550b48f2013-02-12 14:56:29 -0800146 * Listener for filtering accessibility events.
147 */
148 public static interface AccessibilityEventFilter {
149
150 /**
151 * Callback for determining whether an event is accepted or
152 * it is filtered out.
153 *
154 * @param event The event to process.
155 * @return True if the event is accepted, false to filter it out.
156 */
157 public boolean accept(AccessibilityEvent event);
158 }
159
160 /**
Svetoslav Ganov80943d82013-01-02 10:25:37 -0800161 * Creates a new instance that will handle callbacks from the accessibility
162 * layer on the thread of the provided looper and perform requests for privileged
163 * operations on the provided connection.
164 *
165 * @param looper The looper on which to execute accessibility callbacks.
166 * @param connection The connection for performing privileged operations.
167 *
168 * @hide
169 */
170 public UiAutomation(Looper looper, IUiAutomationConnection connection) {
171 if (looper == null) {
172 throw new IllegalArgumentException("Looper cannot be null!");
173 }
174 if (connection == null) {
175 throw new IllegalArgumentException("Connection cannot be null!");
176 }
177 mUiAutomationConnection = connection;
178 mClient = new IAccessibilityServiceClientImpl(looper);
179 }
180
181 /**
182 * Connects this UiAutomation to the accessibility introspection APIs.
183 *
184 * @hide
185 */
186 public void connect() {
187 synchronized (mLock) {
188 throwIfConnectedLocked();
189 if (mIsConnecting) {
190 return;
191 }
192 mIsConnecting = true;
193 }
194
195 try {
196 // Calling out without a lock held.
197 mUiAutomationConnection.connect(mClient);
198 } catch (RemoteException re) {
199 throw new RuntimeException("Error while connecting UiAutomation", re);
200 }
201
202 synchronized (mLock) {
203 final long startTimeMillis = SystemClock.uptimeMillis();
204 try {
205 while (true) {
206 if (isConnectedLocked()) {
207 break;
208 }
209 final long elapsedTimeMillis = SystemClock.uptimeMillis() - startTimeMillis;
210 final long remainingTimeMillis = CONNECT_TIMEOUT_MILLIS - elapsedTimeMillis;
211 if (remainingTimeMillis <= 0) {
212 throw new RuntimeException("Error while connecting UiAutomation");
213 }
214 try {
215 mLock.wait(remainingTimeMillis);
216 } catch (InterruptedException ie) {
217 /* ignore */
218 }
219 }
220 } finally {
221 mIsConnecting = false;
222 }
223 }
224 }
225
226 /**
227 * Disconnects this UiAutomation from the accessibility introspection APIs.
228 *
229 * @hide
230 */
231 public void disconnect() {
232 synchronized (mLock) {
233 if (mIsConnecting) {
234 throw new IllegalStateException(
235 "Cannot call disconnect() while connecting!");
236 }
237 throwIfNotConnectedLocked();
238 mConnectionId = CONNECTION_ID_UNDEFINED;
239 }
240 try {
241 // Calling out without a lock held.
242 mUiAutomationConnection.disconnect();
243 } catch (RemoteException re) {
244 throw new RuntimeException("Error while disconnecting UiAutomation", re);
245 }
246 }
247
248 /**
249 * The id of the {@link IAccessibilityInteractionConnection} for querying
250 * the screen content. This is here for legacy purposes since some tools use
251 * hidden APIs to introspect the screen.
252 *
253 * @hide
254 */
255 public int getConnectionId() {
256 synchronized (mLock) {
257 throwIfNotConnectedLocked();
258 return mConnectionId;
259 }
260 }
261
262 /**
263 * Sets a callback for observing the stream of {@link AccessibilityEvent}s.
264 *
265 * @param listener The callback.
266 */
267 public void setOnAccessibilityEventListener(OnAccessibilityEventListener listener) {
268 synchronized (mLock) {
269 mOnAccessibilityEventListener = listener;
270 }
271 }
272
273 /**
Svetoslavbbfa5852013-02-11 19:38:12 -0800274 * Performs a global action. Such an action can be performed at any moment
275 * regardless of the current application or user location in that application.
276 * For example going back, going home, opening recents, etc.
277 *
278 * @param action The action to perform.
279 * @return Whether the action was successfully performed.
280 *
Svetoslav8e3feb12014-02-24 13:46:47 -0800281 * @see android.accessibilityservice.AccessibilityService#GLOBAL_ACTION_BACK
282 * @see android.accessibilityservice.AccessibilityService#GLOBAL_ACTION_HOME
283 * @see android.accessibilityservice.AccessibilityService#GLOBAL_ACTION_NOTIFICATIONS
284 * @see android.accessibilityservice.AccessibilityService#GLOBAL_ACTION_RECENTS
Svetoslavbbfa5852013-02-11 19:38:12 -0800285 */
286 public final boolean performGlobalAction(int action) {
287 final IAccessibilityServiceConnection connection;
288 synchronized (mLock) {
289 throwIfNotConnectedLocked();
290 connection = AccessibilityInteractionClient.getInstance()
291 .getConnection(mConnectionId);
292 }
293 // Calling out without a lock held.
294 if (connection != null) {
295 try {
296 return connection.performGlobalAction(action);
297 } catch (RemoteException re) {
298 Log.w(LOG_TAG, "Error while calling performGlobalAction", re);
299 }
300 }
301 return false;
302 }
303
304 /**
Svetoslav1e0d4af2014-04-10 17:41:29 -0700305 * Find the view that has the specified focus type. The search is performed
306 * across all windows.
307 * <p>
308 * <strong>Note:</strong> In order to access the windows you have to opt-in
309 * to retrieve the interactive windows by setting the
310 * {@link AccessibilityServiceInfo#FLAG_RETRIEVE_INTERACTIVE_WINDOWS} flag.
311 * Otherwise, the search will be performed only in the active window.
312 * </p>
313 *
314 * @param focus The focus to find. One of {@link AccessibilityNodeInfo#FOCUS_INPUT} or
315 * {@link AccessibilityNodeInfo#FOCUS_ACCESSIBILITY}.
316 * @return The node info of the focused view or null.
317 *
318 * @see AccessibilityNodeInfo#FOCUS_INPUT
319 * @see AccessibilityNodeInfo#FOCUS_ACCESSIBILITY
320 */
321 public AccessibilityNodeInfo findFocus(int focus) {
322 return AccessibilityInteractionClient.getInstance().findFocus(mConnectionId,
323 AccessibilityNodeInfo.ANY_WINDOW_ID, AccessibilityNodeInfo.ROOT_NODE_ID, focus);
324 }
325
326 /**
Svetoslavbbfa5852013-02-11 19:38:12 -0800327 * Gets the an {@link AccessibilityServiceInfo} describing this UiAutomation.
328 * This method is useful if one wants to change some of the dynamically
329 * configurable properties at runtime.
330 *
331 * @return The accessibility service info.
332 *
333 * @see AccessibilityServiceInfo
334 */
335 public final AccessibilityServiceInfo getServiceInfo() {
336 final IAccessibilityServiceConnection connection;
337 synchronized (mLock) {
338 throwIfNotConnectedLocked();
339 connection = AccessibilityInteractionClient.getInstance()
340 .getConnection(mConnectionId);
341 }
342 // Calling out without a lock held.
343 if (connection != null) {
344 try {
345 return connection.getServiceInfo();
346 } catch (RemoteException re) {
347 Log.w(LOG_TAG, "Error while getting AccessibilityServiceInfo", re);
348 }
349 }
350 return null;
351 }
352
353 /**
354 * Sets the {@link AccessibilityServiceInfo} that describes how this
355 * UiAutomation will be handled by the platform accessibility layer.
356 *
357 * @param info The info.
358 *
359 * @see AccessibilityServiceInfo
360 */
361 public final void setServiceInfo(AccessibilityServiceInfo info) {
362 final IAccessibilityServiceConnection connection;
363 synchronized (mLock) {
364 throwIfNotConnectedLocked();
365 AccessibilityInteractionClient.getInstance().clearCache();
366 connection = AccessibilityInteractionClient.getInstance()
367 .getConnection(mConnectionId);
368 }
369 // Calling out without a lock held.
370 if (connection != null) {
371 try {
372 connection.setServiceInfo(info);
373 } catch (RemoteException re) {
374 Log.w(LOG_TAG, "Error while setting AccessibilityServiceInfo", re);
375 }
376 }
377 }
378
379 /**
Svetoslav8e3feb12014-02-24 13:46:47 -0800380 * Gets the windows on the screen. This method returns only the windows
381 * that a sighted user can interact with, as opposed to all windows.
382 * For example, if there is a modal dialog shown and the user cannot touch
383 * anything behind it, then only the modal window will be reported
384 * (assuming it is the top one). For convenience the returned windows
385 * are ordered in a descending layer order, which is the windows that
386 * are higher in the Z-order are reported first.
387 * <p>
388 * <strong>Note:</strong> In order to access the windows you have to opt-in
389 * to retrieve the interactive windows by setting the
390 * {@link AccessibilityServiceInfo#FLAG_RETRIEVE_INTERACTIVE_WINDOWS} flag.
391 * </p>
392 *
393 * @return The windows if there are windows such, otherwise an empty list.
394 */
395 public List<AccessibilityWindowInfo> getWindows() {
396 final int connectionId;
397 synchronized (mLock) {
398 throwIfNotConnectedLocked();
399 connectionId = mConnectionId;
400 }
401 // Calling out without a lock held.
402 return AccessibilityInteractionClient.getInstance()
403 .getWindows(connectionId);
404 }
405
406 /**
Svetoslav Ganov80943d82013-01-02 10:25:37 -0800407 * Gets the root {@link AccessibilityNodeInfo} in the active window.
408 *
409 * @return The root info.
410 */
411 public AccessibilityNodeInfo getRootInActiveWindow() {
412 final int connectionId;
413 synchronized (mLock) {
414 throwIfNotConnectedLocked();
415 connectionId = mConnectionId;
416 }
417 // Calling out without a lock held.
418 return AccessibilityInteractionClient.getInstance()
419 .getRootInActiveWindow(connectionId);
420 }
421
422 /**
423 * A method for injecting an arbitrary input event.
424 * <p>
425 * <strong>Note:</strong> It is caller's responsibility to recycle the event.
426 * </p>
427 * @param event The event to inject.
428 * @param sync Whether to inject the event synchronously.
429 * @return Whether event injection succeeded.
430 */
431 public boolean injectInputEvent(InputEvent event, boolean sync) {
432 synchronized (mLock) {
433 throwIfNotConnectedLocked();
434 }
435 try {
436 if (DEBUG) {
437 Log.i(LOG_TAG, "Injecting: " + event + " sync: " + sync);
438 }
439 // Calling out without a lock held.
440 return mUiAutomationConnection.injectInputEvent(event, sync);
441 } catch (RemoteException re) {
442 Log.e(LOG_TAG, "Error while injecting input event!", re);
443 }
444 return false;
445 }
446
447 /**
448 * Sets the device rotation. A client can freeze the rotation in
449 * desired state or freeze the rotation to its current state or
450 * unfreeze the rotation (rotating the device changes its rotation
451 * state).
452 *
453 * @param rotation The desired rotation.
454 * @return Whether the rotation was set successfully.
455 *
456 * @see #ROTATION_FREEZE_0
457 * @see #ROTATION_FREEZE_90
458 * @see #ROTATION_FREEZE_180
459 * @see #ROTATION_FREEZE_270
460 * @see #ROTATION_FREEZE_CURRENT
461 * @see #ROTATION_UNFREEZE
462 */
463 public boolean setRotation(int rotation) {
464 synchronized (mLock) {
465 throwIfNotConnectedLocked();
466 }
467 switch (rotation) {
468 case ROTATION_FREEZE_0:
469 case ROTATION_FREEZE_90:
470 case ROTATION_FREEZE_180:
471 case ROTATION_FREEZE_270:
472 case ROTATION_UNFREEZE:
473 case ROTATION_FREEZE_CURRENT: {
474 try {
475 // Calling out without a lock held.
476 mUiAutomationConnection.setRotation(rotation);
477 return true;
478 } catch (RemoteException re) {
479 Log.e(LOG_TAG, "Error while setting rotation!", re);
480 }
481 } return false;
482 default: {
483 throw new IllegalArgumentException("Invalid rotation.");
484 }
485 }
486 }
487
488 /**
489 * Executes a command and waits for a specific accessibility event up to a
490 * given wait timeout. To detect a sequence of events one can implement a
491 * filter that keeps track of seen events of the expected sequence and
492 * returns true after the last event of that sequence is received.
493 * <p>
494 * <strong>Note:</strong> It is caller's responsibility to recycle the returned event.
495 * </p>
496 * @param command The command to execute.
497 * @param filter Filter that recognizes the expected event.
498 * @param timeoutMillis The wait timeout in milliseconds.
499 *
500 * @throws TimeoutException If the expected event is not received within the timeout.
501 */
502 public AccessibilityEvent executeAndWaitForEvent(Runnable command,
Svetoslav550b48f2013-02-12 14:56:29 -0800503 AccessibilityEventFilter filter, long timeoutMillis) throws TimeoutException {
Svetoslavdb7da0e2013-04-22 18:34:02 -0700504 // Acquire the lock and prepare for receiving events.
Svetoslav Ganov80943d82013-01-02 10:25:37 -0800505 synchronized (mLock) {
506 throwIfNotConnectedLocked();
Svetoslav Ganov80943d82013-01-02 10:25:37 -0800507 mEventQueue.clear();
508 // Prepare to wait for an event.
509 mWaitingForEventDelivery = true;
Svetoslavdb7da0e2013-04-22 18:34:02 -0700510 }
Svetoslav Ganov80943d82013-01-02 10:25:37 -0800511
Svetoslavdb7da0e2013-04-22 18:34:02 -0700512 // Note: We have to release the lock since calling out with this lock held
513 // can bite. We will correctly filter out events from other interactions,
514 // so starting to collect events before running the action is just fine.
Svetoslav Ganov80943d82013-01-02 10:25:37 -0800515
Svetoslavdb7da0e2013-04-22 18:34:02 -0700516 // We will ignore events from previous interactions.
517 final long executionStartTimeMillis = SystemClock.uptimeMillis();
518 // Execute the command *without* the lock being held.
519 command.run();
520
521 // Acquire the lock and wait for the event.
522 synchronized (mLock) {
Svetoslav Ganov80943d82013-01-02 10:25:37 -0800523 try {
524 // Wait for the event.
525 final long startTimeMillis = SystemClock.uptimeMillis();
526 while (true) {
527 // Drain the event queue
528 while (!mEventQueue.isEmpty()) {
529 AccessibilityEvent event = mEventQueue.remove(0);
530 // Ignore events from previous interactions.
Svetoslavdb7da0e2013-04-22 18:34:02 -0700531 if (event.getEventTime() < executionStartTimeMillis) {
Svetoslav Ganov80943d82013-01-02 10:25:37 -0800532 continue;
533 }
Svetoslav550b48f2013-02-12 14:56:29 -0800534 if (filter.accept(event)) {
Svetoslav Ganov80943d82013-01-02 10:25:37 -0800535 return event;
536 }
537 event.recycle();
538 }
539 // Check if timed out and if not wait.
540 final long elapsedTimeMillis = SystemClock.uptimeMillis() - startTimeMillis;
541 final long remainingTimeMillis = timeoutMillis - elapsedTimeMillis;
542 if (remainingTimeMillis <= 0) {
543 throw new TimeoutException("Expected event not received within: "
544 + timeoutMillis + " ms.");
545 }
546 try {
547 mLock.wait(remainingTimeMillis);
548 } catch (InterruptedException ie) {
549 /* ignore */
550 }
551 }
552 } finally {
553 mWaitingForEventDelivery = false;
554 mEventQueue.clear();
555 mLock.notifyAll();
556 }
557 }
558 }
559
560 /**
561 * Waits for the accessibility event stream to become idle, which is not to
562 * have received an accessibility event within <code>idleTimeoutMillis</code>.
563 * The total time spent to wait for an idle accessibility event stream is bounded
564 * by the <code>globalTimeoutMillis</code>.
565 *
566 * @param idleTimeoutMillis The timeout in milliseconds between two events
567 * to consider the device idle.
568 * @param globalTimeoutMillis The maximal global timeout in milliseconds in
569 * which to wait for an idle state.
570 *
571 * @throws TimeoutException If no idle state was detected within
572 * <code>globalTimeoutMillis.</code>
573 */
574 public void waitForIdle(long idleTimeoutMillis, long globalTimeoutMillis)
575 throws TimeoutException {
576 synchronized (mLock) {
577 throwIfNotConnectedLocked();
578
579 final long startTimeMillis = SystemClock.uptimeMillis();
580 if (mLastEventTimeMillis <= 0) {
581 mLastEventTimeMillis = startTimeMillis;
582 }
583
584 while (true) {
585 final long currentTimeMillis = SystemClock.uptimeMillis();
586 // Did we get idle state within the global timeout?
587 final long elapsedGlobalTimeMillis = currentTimeMillis - startTimeMillis;
588 final long remainingGlobalTimeMillis =
589 globalTimeoutMillis - elapsedGlobalTimeMillis;
590 if (remainingGlobalTimeMillis <= 0) {
591 throw new TimeoutException("No idle state with idle timeout: "
592 + idleTimeoutMillis + " within global timeout: "
593 + globalTimeoutMillis);
594 }
595 // Did we get an idle state within the idle timeout?
596 final long elapsedIdleTimeMillis = currentTimeMillis - mLastEventTimeMillis;
597 final long remainingIdleTimeMillis = idleTimeoutMillis - elapsedIdleTimeMillis;
598 if (remainingIdleTimeMillis <= 0) {
599 return;
600 }
601 try {
602 mLock.wait(remainingIdleTimeMillis);
603 } catch (InterruptedException ie) {
604 /* ignore */
605 }
606 }
607 }
608 }
609
610 /**
611 * Takes a screenshot.
612 *
613 * @return The screenshot bitmap on success, null otherwise.
614 */
615 public Bitmap takeScreenshot() {
616 synchronized (mLock) {
617 throwIfNotConnectedLocked();
618 }
619 Display display = DisplayManagerGlobal.getInstance()
620 .getRealDisplay(Display.DEFAULT_DISPLAY);
621 Point displaySize = new Point();
622 display.getRealSize(displaySize);
623 final int displayWidth = displaySize.x;
624 final int displayHeight = displaySize.y;
625
626 final float screenshotWidth;
627 final float screenshotHeight;
628
629 final int rotation = display.getRotation();
630 switch (rotation) {
631 case ROTATION_FREEZE_0: {
632 screenshotWidth = displayWidth;
633 screenshotHeight = displayHeight;
634 } break;
635 case ROTATION_FREEZE_90: {
636 screenshotWidth = displayHeight;
637 screenshotHeight = displayWidth;
638 } break;
639 case ROTATION_FREEZE_180: {
640 screenshotWidth = displayWidth;
641 screenshotHeight = displayHeight;
642 } break;
643 case ROTATION_FREEZE_270: {
644 screenshotWidth = displayHeight;
645 screenshotHeight = displayWidth;
646 } break;
647 default: {
648 throw new IllegalArgumentException("Invalid rotation: "
649 + rotation);
650 }
651 }
652
653 // Take the screenshot
654 Bitmap screenShot = null;
655 try {
656 // Calling out without a lock held.
657 screenShot = mUiAutomationConnection.takeScreenshot((int) screenshotWidth,
658 (int) screenshotHeight);
659 if (screenShot == null) {
660 return null;
661 }
662 } catch (RemoteException re) {
663 Log.e(LOG_TAG, "Error while taking screnshot!", re);
664 return null;
665 }
666
667 // Rotate the screenshot to the current orientation
668 if (rotation != ROTATION_FREEZE_0) {
669 Bitmap unrotatedScreenShot = Bitmap.createBitmap(displayWidth, displayHeight,
670 Bitmap.Config.ARGB_8888);
671 Canvas canvas = new Canvas(unrotatedScreenShot);
672 canvas.translate(unrotatedScreenShot.getWidth() / 2,
673 unrotatedScreenShot.getHeight() / 2);
674 canvas.rotate(getDegreesForRotation(rotation));
675 canvas.translate(- screenshotWidth / 2, - screenshotHeight / 2);
676 canvas.drawBitmap(screenShot, 0, 0, null);
677 canvas.setBitmap(null);
Leon Scroggins IIIb101ebe2014-07-02 17:09:34 -0400678 screenShot.recycle();
Svetoslav Ganov80943d82013-01-02 10:25:37 -0800679 screenShot = unrotatedScreenShot;
680 }
681
682 // Optimization
683 screenShot.setHasAlpha(false);
684
685 return screenShot;
686 }
687
Adam Momtaz8f6f1f42013-04-10 12:42:58 -0700688 /**
689 * Sets whether this UiAutomation to run in a "monkey" mode. Applications can query whether
690 * they are executed in a "monkey" mode, i.e. run by a test framework, and avoid doing
691 * potentially undesirable actions such as calling 911 or posting on public forums etc.
692 *
693 * @param enable whether to run in a "monkey" mode or not. Default is not.
Quddus Chonga9f6a9b2015-04-07 13:03:29 -0700694 * @see ActivityManager#isUserAMonkey()
Adam Momtaz8f6f1f42013-04-10 12:42:58 -0700695 */
696 public void setRunAsMonkey(boolean enable) {
697 synchronized (mLock) {
698 throwIfNotConnectedLocked();
699 }
700 try {
701 ActivityManagerNative.getDefault().setUserIsMonkey(enable);
702 } catch (RemoteException re) {
703 Log.e(LOG_TAG, "Error while setting run as monkey!", re);
704 }
705 }
706
Svetoslav1376d602014-03-13 11:17:26 -0700707 /**
708 * Clears the frame statistics for the content of a given window. These
709 * statistics contain information about the most recently rendered content
710 * frames.
711 *
712 * @param windowId The window id.
713 * @return Whether the window is present and its frame statistics
714 * were cleared.
715 *
716 * @see android.view.WindowContentFrameStats
717 * @see #getWindowContentFrameStats(int)
718 * @see #getWindows()
719 * @see AccessibilityWindowInfo#getId() AccessibilityWindowInfo.getId()
720 */
721 public boolean clearWindowContentFrameStats(int windowId) {
722 synchronized (mLock) {
723 throwIfNotConnectedLocked();
724 }
725 try {
726 if (DEBUG) {
727 Log.i(LOG_TAG, "Clearing content frame stats for window: " + windowId);
728 }
729 // Calling out without a lock held.
730 return mUiAutomationConnection.clearWindowContentFrameStats(windowId);
731 } catch (RemoteException re) {
732 Log.e(LOG_TAG, "Error clearing window content frame stats!", re);
733 }
734 return false;
735 }
736
737 /**
738 * Gets the frame statistics for a given window. These statistics contain
739 * information about the most recently rendered content frames.
740 * <p>
741 * A typical usage requires clearing the window frame statistics via {@link
742 * #clearWindowContentFrameStats(int)} followed by an interaction with the UI and
743 * finally getting the window frame statistics via calling this method.
744 * </p>
745 * <pre>
746 * // Assume we have at least one window.
747 * final int windowId = getWindows().get(0).getId();
748 *
749 * // Start with a clean slate.
750 * uiAutimation.clearWindowContentFrameStats(windowId);
751 *
752 * // Do stuff with the UI.
753 *
754 * // Get the frame statistics.
755 * WindowContentFrameStats stats = uiAutomation.getWindowContentFrameStats(windowId);
756 * </pre>
757 *
758 * @param windowId The window id.
759 * @return The window frame statistics, or null if the window is not present.
760 *
761 * @see android.view.WindowContentFrameStats
762 * @see #clearWindowContentFrameStats(int)
763 * @see #getWindows()
764 * @see AccessibilityWindowInfo#getId() AccessibilityWindowInfo.getId()
765 */
766 public WindowContentFrameStats getWindowContentFrameStats(int windowId) {
767 synchronized (mLock) {
768 throwIfNotConnectedLocked();
769 }
770 try {
771 if (DEBUG) {
772 Log.i(LOG_TAG, "Getting content frame stats for window: " + windowId);
773 }
774 // Calling out without a lock held.
775 return mUiAutomationConnection.getWindowContentFrameStats(windowId);
776 } catch (RemoteException re) {
777 Log.e(LOG_TAG, "Error getting window content frame stats!", re);
778 }
779 return null;
780 }
781
782 /**
783 * Clears the window animation rendering statistics. These statistics contain
784 * information about the most recently rendered window animation frames, i.e.
785 * for window transition animations.
786 *
787 * @see android.view.WindowAnimationFrameStats
788 * @see #getWindowAnimationFrameStats()
789 * @see android.R.styleable#WindowAnimation
790 */
791 public void clearWindowAnimationFrameStats() {
792 synchronized (mLock) {
793 throwIfNotConnectedLocked();
794 }
795 try {
796 if (DEBUG) {
797 Log.i(LOG_TAG, "Clearing window animation frame stats");
798 }
799 // Calling out without a lock held.
800 mUiAutomationConnection.clearWindowAnimationFrameStats();
801 } catch (RemoteException re) {
802 Log.e(LOG_TAG, "Error clearing window animation frame stats!", re);
803 }
804 }
805
806 /**
807 * Gets the window animation frame statistics. These statistics contain
808 * information about the most recently rendered window animation frames, i.e.
809 * for window transition animations.
810 *
811 * <p>
812 * A typical usage requires clearing the window animation frame statistics via
813 * {@link #clearWindowAnimationFrameStats()} followed by an interaction that causes
814 * a window transition which uses a window animation and finally getting the window
815 * animation frame statistics by calling this method.
816 * </p>
817 * <pre>
818 * // Start with a clean slate.
819 * uiAutimation.clearWindowAnimationFrameStats();
820 *
821 * // Do stuff to trigger a window transition.
822 *
823 * // Get the frame statistics.
824 * WindowAnimationFrameStats stats = uiAutomation.getWindowAnimationFrameStats();
825 * </pre>
826 *
827 * @return The window animation frame statistics.
828 *
829 * @see android.view.WindowAnimationFrameStats
830 * @see #clearWindowAnimationFrameStats()
831 * @see android.R.styleable#WindowAnimation
832 */
833 public WindowAnimationFrameStats getWindowAnimationFrameStats() {
834 synchronized (mLock) {
835 throwIfNotConnectedLocked();
836 }
837 try {
838 if (DEBUG) {
839 Log.i(LOG_TAG, "Getting window animation frame stats");
840 }
841 // Calling out without a lock held.
842 return mUiAutomationConnection.getWindowAnimationFrameStats();
843 } catch (RemoteException re) {
844 Log.e(LOG_TAG, "Error getting window animation frame stats!", re);
845 }
846 return null;
847 }
848
Svetoslav121e0c02014-05-08 18:51:25 -0700849 /**
Svet Ganov52153f42015-08-11 08:59:12 -0700850 * Grants a runtime permission to a package for a user.
851 * @param packageName The package to which to grant.
852 * @param permission The permission to grant.
853 * @return Whether granting succeeded.
854 *
855 * @hide
856 */
857 public boolean grantRuntimePermission(String packageName, String permission,
858 UserHandle userHandle) {
859 synchronized (mLock) {
860 throwIfNotConnectedLocked();
861 }
862 try {
863 if (DEBUG) {
864 Log.i(LOG_TAG, "Granting runtime permission");
865 }
866 // Calling out without a lock held.
867 mUiAutomationConnection.grantRuntimePermission(packageName,
868 permission, userHandle.getIdentifier());
869 // TODO: The package manager API should return boolean.
870 return true;
871 } catch (RemoteException re) {
872 Log.e(LOG_TAG, "Error granting runtime permission", re);
873 }
874 return false;
875 }
876
877 /**
878 * Revokes a runtime permission from a package for a user.
879 * @param packageName The package from which to revoke.
880 * @param permission The permission to revoke.
881 * @return Whether revoking succeeded.
882 *
883 * @hide
884 */
885 public boolean revokeRuntimePermission(String packageName, String permission,
886 UserHandle userHandle) {
887 synchronized (mLock) {
888 throwIfNotConnectedLocked();
889 }
890 try {
891 if (DEBUG) {
892 Log.i(LOG_TAG, "Revoking runtime permission");
893 }
894 // Calling out without a lock held.
895 mUiAutomationConnection.revokeRuntimePermission(packageName,
896 permission, userHandle.getIdentifier());
897 // TODO: The package manager API should return boolean.
898 return true;
899 } catch (RemoteException re) {
900 Log.e(LOG_TAG, "Error revoking runtime permission", re);
901 }
902 return false;
903 }
904
905 /**
Svetoslav121e0c02014-05-08 18:51:25 -0700906 * Executes a shell command. This method returs a file descriptor that points
907 * to the standard output stream. The command execution is similar to running
908 * "adb shell <command>" from a host connected to the device.
909 * <p>
910 * <strong>Note:</strong> It is your responsibility to close the retunred file
911 * descriptor once you are done reading.
912 * </p>
913 *
914 * @param command The command to execute.
915 * @return A file descriptor to the standard output stream.
916 */
917 public ParcelFileDescriptor executeShellCommand(String command) {
918 synchronized (mLock) {
919 throwIfNotConnectedLocked();
920 }
921
922 ParcelFileDescriptor source = null;
923 ParcelFileDescriptor sink = null;
924
925 try {
926 ParcelFileDescriptor[] pipe = ParcelFileDescriptor.createPipe();
927 source = pipe[0];
928 sink = pipe[1];
929
930 // Calling out without a lock held.
931 mUiAutomationConnection.executeShellCommand(command, sink);
932 } catch (IOException ioe) {
933 Log.e(LOG_TAG, "Error executing shell command!", ioe);
934 } catch (RemoteException re) {
935 Log.e(LOG_TAG, "Error executing shell command!", re);
936 } finally {
937 IoUtils.closeQuietly(sink);
938 }
939
940 return source;
941 }
942
Svetoslav Ganov80943d82013-01-02 10:25:37 -0800943 private static float getDegreesForRotation(int value) {
944 switch (value) {
945 case Surface.ROTATION_90: {
946 return 360f - 90f;
947 }
948 case Surface.ROTATION_180: {
949 return 360f - 180f;
950 }
951 case Surface.ROTATION_270: {
952 return 360f - 270f;
953 } default: {
954 return 0;
955 }
956 }
957 }
958
959 private boolean isConnectedLocked() {
960 return mConnectionId != CONNECTION_ID_UNDEFINED;
961 }
962
963 private void throwIfConnectedLocked() {
964 if (mConnectionId != CONNECTION_ID_UNDEFINED) {
965 throw new IllegalStateException("UiAutomation not connected!");
966 }
967 }
968
969 private void throwIfNotConnectedLocked() {
970 if (!isConnectedLocked()) {
971 throw new IllegalStateException("UiAutomation not connected!");
972 }
973 }
974
975 private class IAccessibilityServiceClientImpl extends IAccessibilityServiceClientWrapper {
976
977 public IAccessibilityServiceClientImpl(Looper looper) {
978 super(null, looper, new Callbacks() {
979 @Override
Svetoslav3a5c7212014-10-14 09:54:26 -0700980 public void init(int connectionId, IBinder windowToken) {
Svetoslav Ganov80943d82013-01-02 10:25:37 -0800981 synchronized (mLock) {
982 mConnectionId = connectionId;
983 mLock.notifyAll();
984 }
985 }
986
987 @Override
988 public void onServiceConnected() {
989 /* do nothing */
990 }
991
992 @Override
993 public void onInterrupt() {
994 /* do nothing */
995 }
996
997 @Override
998 public boolean onGesture(int gestureId) {
999 /* do nothing */
1000 return false;
1001 }
1002
1003 @Override
1004 public void onAccessibilityEvent(AccessibilityEvent event) {
1005 synchronized (mLock) {
1006 mLastEventTimeMillis = event.getEventTime();
1007 if (mWaitingForEventDelivery) {
1008 mEventQueue.add(AccessibilityEvent.obtain(event));
1009 }
1010 mLock.notifyAll();
1011 }
1012 // Calling out only without a lock held.
1013 final OnAccessibilityEventListener listener = mOnAccessibilityEventListener;
1014 if (listener != null) {
1015 listener.onAccessibilityEvent(AccessibilityEvent.obtain(event));
1016 }
1017 }
Svetoslavc4fccd12013-04-09 12:58:41 -07001018
1019 @Override
1020 public boolean onKeyEvent(KeyEvent event) {
1021 return false;
1022 }
Svetoslav Ganov80943d82013-01-02 10:25:37 -08001023 });
1024 }
1025 }
1026}