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