blob: 5ec59b4675107c2a13d41d3c813e2f08249ff9ce [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;
Alan Viverette214fb682015-11-17 09:47:11 -050024import android.annotation.NonNull;
Jeff Sharkeya73b8fd2016-01-06 17:02:08 -070025import android.annotation.TestApi;
Svetoslav Ganov80943d82013-01-02 10:25:37 -080026import android.graphics.Bitmap;
27import android.graphics.Canvas;
28import android.graphics.Point;
Alan Viverette214fb682015-11-17 09:47:11 -050029import android.graphics.Region;
Svetoslav Ganov80943d82013-01-02 10:25:37 -080030import android.hardware.display.DisplayManagerGlobal;
Svetoslav3a5c7212014-10-14 09:54:26 -070031import android.os.IBinder;
Svetoslav Ganov80943d82013-01-02 10:25:37 -080032import android.os.Looper;
Svetoslav121e0c02014-05-08 18:51:25 -070033import android.os.ParcelFileDescriptor;
Svetoslav Ganov80943d82013-01-02 10:25:37 -080034import android.os.RemoteException;
35import android.os.SystemClock;
Svet Ganov52153f42015-08-11 08:59:12 -070036import android.os.UserHandle;
Svetoslav Ganov80943d82013-01-02 10:25:37 -080037import android.util.Log;
38import android.view.Display;
39import android.view.InputEvent;
Svetoslavc4fccd12013-04-09 12:58:41 -070040import android.view.KeyEvent;
Svetoslav Ganov80943d82013-01-02 10:25:37 -080041import android.view.Surface;
Svetoslav1376d602014-03-13 11:17:26 -070042import android.view.WindowAnimationFrameStats;
43import android.view.WindowContentFrameStats;
Svetoslav Ganov80943d82013-01-02 10:25:37 -080044import android.view.accessibility.AccessibilityEvent;
45import android.view.accessibility.AccessibilityInteractionClient;
46import android.view.accessibility.AccessibilityNodeInfo;
Svetoslav8e3feb12014-02-24 13:46:47 -080047import android.view.accessibility.AccessibilityWindowInfo;
Svetoslav Ganov80943d82013-01-02 10:25:37 -080048import android.view.accessibility.IAccessibilityInteractionConnection;
Svetoslav121e0c02014-05-08 18:51:25 -070049import libcore.io.IoUtils;
Svetoslav Ganov80943d82013-01-02 10:25:37 -080050
Svetoslav121e0c02014-05-08 18:51:25 -070051import java.io.IOException;
Svetoslav Ganov80943d82013-01-02 10:25:37 -080052import java.util.ArrayList;
Svetoslav8e3feb12014-02-24 13:46:47 -080053import java.util.List;
Svetoslav Ganov80943d82013-01-02 10:25:37 -080054import java.util.concurrent.TimeoutException;
55
56/**
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
Svetoslavbbfa5852013-02-11 19:38:12 -080061 * 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.
Svetoslav Ganov80943d82013-01-02 10:25:37 -080065 * <p>
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)}.
72 * </p>
73 * <p>
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.
78 * </p>
79 */
80public final class UiAutomation {
81
82 private static final String LOG_TAG = UiAutomation.class.getSimpleName();
83
84 private static final boolean DEBUG = false;
85
86 private static final int CONNECTION_ID_UNDEFINED = -1;
87
88 private static final long CONNECT_TIMEOUT_MILLIS = 5000;
89
90 /** Rotation constant: Unfreeze rotation (rotating the device changes its rotation state). */
91 public static final int ROTATION_UNFREEZE = -2;
92
93 /** Rotation constant: Freeze rotation to its current state. */
94 public static final int ROTATION_FREEZE_CURRENT = -1;
95
96 /** Rotation constant: Freeze rotation to 0 degrees (natural orientation) */
97 public static final int ROTATION_FREEZE_0 = Surface.ROTATION_0;
98
99 /** Rotation constant: Freeze rotation to 90 degrees . */
100 public static final int ROTATION_FREEZE_90 = Surface.ROTATION_90;
101
102 /** Rotation constant: Freeze rotation to 180 degrees . */
103 public static final int ROTATION_FREEZE_180 = Surface.ROTATION_180;
104
105 /** Rotation constant: Freeze rotation to 270 degrees . */
106 public static final int ROTATION_FREEZE_270 = Surface.ROTATION_270;
107
Phil Weaver1dd87222016-01-26 17:15:15 -0800108 /**
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)}.
113 */
114 public static final int FLAG_DONT_SUPPRESS_ACCESSIBILITY_SERVICES = 0x00000001;
115
Svetoslav Ganov80943d82013-01-02 10:25:37 -0800116 private final Object mLock = new Object();
117
118 private final ArrayList<AccessibilityEvent> mEventQueue = new ArrayList<AccessibilityEvent>();
119
120 private final IAccessibilityServiceClient mClient;
121
122 private final IUiAutomationConnection mUiAutomationConnection;
123
124 private int mConnectionId = CONNECTION_ID_UNDEFINED;
125
126 private OnAccessibilityEventListener mOnAccessibilityEventListener;
127
128 private boolean mWaitingForEventDelivery;
129
130 private long mLastEventTimeMillis;
131
132 private boolean mIsConnecting;
133
Phil Weaver1dd87222016-01-26 17:15:15 -0800134 private boolean mIsDestroyed;
135
136 private int mFlags;
137
Svetoslav Ganov80943d82013-01-02 10:25:37 -0800138 /**
139 * Listener for observing the {@link AccessibilityEvent} stream.
140 */
141 public static interface OnAccessibilityEventListener {
142
143 /**
144 * Callback for receiving an {@link AccessibilityEvent}.
145 * <p>
146 * <strong>Note:</strong> This method is <strong>NOT</strong> executed
147 * on the main test thread. The client is responsible for proper
148 * synchronization.
149 * </p>
150 * <p>
151 * <strong>Note:</strong> It is responsibility of the client
152 * to recycle the received events to minimize object creation.
153 * </p>
154 *
155 * @param event The received event.
156 */
157 public void onAccessibilityEvent(AccessibilityEvent event);
158 }
159
160 /**
Svetoslav550b48f2013-02-12 14:56:29 -0800161 * Listener for filtering accessibility events.
162 */
163 public static interface AccessibilityEventFilter {
164
165 /**
166 * Callback for determining whether an event is accepted or
167 * it is filtered out.
168 *
169 * @param event The event to process.
170 * @return True if the event is accepted, false to filter it out.
171 */
172 public boolean accept(AccessibilityEvent event);
173 }
174
175 /**
Svetoslav Ganov80943d82013-01-02 10:25:37 -0800176 * 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.
179 *
180 * @param looper The looper on which to execute accessibility callbacks.
181 * @param connection The connection for performing privileged operations.
182 *
183 * @hide
184 */
185 public UiAutomation(Looper looper, IUiAutomationConnection connection) {
186 if (looper == null) {
187 throw new IllegalArgumentException("Looper cannot be null!");
188 }
189 if (connection == null) {
190 throw new IllegalArgumentException("Connection cannot be null!");
191 }
192 mUiAutomationConnection = connection;
193 mClient = new IAccessibilityServiceClientImpl(looper);
194 }
195
196 /**
Phil Weaver1dd87222016-01-26 17:15:15 -0800197 * Connects this UiAutomation to the accessibility introspection APIs with default flags.
Svetoslav Ganov80943d82013-01-02 10:25:37 -0800198 *
199 * @hide
200 */
201 public void connect() {
Phil Weaver1dd87222016-01-26 17:15:15 -0800202 connect(0);
203 }
204
205 /**
206 * Connects this UiAutomation to the accessibility introspection APIs.
207 *
208 * @param flags Any flags to apply to the automation as it gets connected
209 *
210 * @hide
211 */
212 public void connect(int flags) {
Svetoslav Ganov80943d82013-01-02 10:25:37 -0800213 synchronized (mLock) {
214 throwIfConnectedLocked();
215 if (mIsConnecting) {
216 return;
217 }
218 mIsConnecting = true;
219 }
220
221 try {
222 // Calling out without a lock held.
Phil Weaver1dd87222016-01-26 17:15:15 -0800223 mUiAutomationConnection.connect(mClient, flags);
224 mFlags = flags;
Svetoslav Ganov80943d82013-01-02 10:25:37 -0800225 } catch (RemoteException re) {
226 throw new RuntimeException("Error while connecting UiAutomation", re);
227 }
228
229 synchronized (mLock) {
230 final long startTimeMillis = SystemClock.uptimeMillis();
231 try {
232 while (true) {
233 if (isConnectedLocked()) {
234 break;
235 }
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");
240 }
241 try {
242 mLock.wait(remainingTimeMillis);
243 } catch (InterruptedException ie) {
244 /* ignore */
245 }
246 }
247 } finally {
248 mIsConnecting = false;
249 }
250 }
251 }
252
253 /**
Phil Weaver1dd87222016-01-26 17:15:15 -0800254 * Get the flags used to connect the service.
255 *
256 * @return The flags used to connect
257 *
258 * @hide
259 */
260 public int getFlags() {
261 return mFlags;
262 }
263
264 /**
Svetoslav Ganov80943d82013-01-02 10:25:37 -0800265 * Disconnects this UiAutomation from the accessibility introspection APIs.
266 *
267 * @hide
268 */
269 public void disconnect() {
270 synchronized (mLock) {
271 if (mIsConnecting) {
272 throw new IllegalStateException(
273 "Cannot call disconnect() while connecting!");
274 }
275 throwIfNotConnectedLocked();
276 mConnectionId = CONNECTION_ID_UNDEFINED;
277 }
278 try {
279 // Calling out without a lock held.
280 mUiAutomationConnection.disconnect();
281 } catch (RemoteException re) {
282 throw new RuntimeException("Error while disconnecting UiAutomation", re);
283 }
284 }
285
286 /**
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.
290 *
291 * @hide
292 */
293 public int getConnectionId() {
294 synchronized (mLock) {
295 throwIfNotConnectedLocked();
296 return mConnectionId;
297 }
298 }
299
300 /**
Phil Weaver1dd87222016-01-26 17:15:15 -0800301 * Reports if the object has been destroyed
302 *
303 * @return {code true} if the object has been destroyed.
304 *
305 * @hide
306 */
307 public boolean isDestroyed() {
308 return mIsDestroyed;
309 }
310
311 /**
Svetoslav Ganov80943d82013-01-02 10:25:37 -0800312 * Sets a callback for observing the stream of {@link AccessibilityEvent}s.
313 *
314 * @param listener The callback.
315 */
316 public void setOnAccessibilityEventListener(OnAccessibilityEventListener listener) {
317 synchronized (mLock) {
318 mOnAccessibilityEventListener = listener;
319 }
320 }
321
322 /**
Phil Weaver1dd87222016-01-26 17:15:15 -0800323 * Destroy this UiAutomation. After calling this method, attempting to use the object will
324 * result in errors.
325 */
326 public void destroy() {
327 disconnect();
328 mIsDestroyed = true;
329 }
330
331 /**
Svetoslavbbfa5852013-02-11 19:38:12 -0800332 * Performs a global action. Such an action can be performed at any moment
333 * regardless of the current application or user location in that application.
334 * For example going back, going home, opening recents, etc.
335 *
336 * @param action The action to perform.
337 * @return Whether the action was successfully performed.
338 *
Svetoslav8e3feb12014-02-24 13:46:47 -0800339 * @see android.accessibilityservice.AccessibilityService#GLOBAL_ACTION_BACK
340 * @see android.accessibilityservice.AccessibilityService#GLOBAL_ACTION_HOME
341 * @see android.accessibilityservice.AccessibilityService#GLOBAL_ACTION_NOTIFICATIONS
342 * @see android.accessibilityservice.AccessibilityService#GLOBAL_ACTION_RECENTS
Svetoslavbbfa5852013-02-11 19:38:12 -0800343 */
344 public final boolean performGlobalAction(int action) {
345 final IAccessibilityServiceConnection connection;
346 synchronized (mLock) {
347 throwIfNotConnectedLocked();
348 connection = AccessibilityInteractionClient.getInstance()
349 .getConnection(mConnectionId);
350 }
351 // Calling out without a lock held.
352 if (connection != null) {
353 try {
354 return connection.performGlobalAction(action);
355 } catch (RemoteException re) {
356 Log.w(LOG_TAG, "Error while calling performGlobalAction", re);
357 }
358 }
359 return false;
360 }
361
362 /**
Svetoslav1e0d4af2014-04-10 17:41:29 -0700363 * Find the view that has the specified focus type. The search is performed
364 * across all windows.
365 * <p>
366 * <strong>Note:</strong> In order to access the windows you have to opt-in
367 * to retrieve the interactive windows by setting the
368 * {@link AccessibilityServiceInfo#FLAG_RETRIEVE_INTERACTIVE_WINDOWS} flag.
369 * Otherwise, the search will be performed only in the active window.
370 * </p>
371 *
372 * @param focus The focus to find. One of {@link AccessibilityNodeInfo#FOCUS_INPUT} or
373 * {@link AccessibilityNodeInfo#FOCUS_ACCESSIBILITY}.
374 * @return The node info of the focused view or null.
375 *
376 * @see AccessibilityNodeInfo#FOCUS_INPUT
377 * @see AccessibilityNodeInfo#FOCUS_ACCESSIBILITY
378 */
379 public AccessibilityNodeInfo findFocus(int focus) {
380 return AccessibilityInteractionClient.getInstance().findFocus(mConnectionId,
381 AccessibilityNodeInfo.ANY_WINDOW_ID, AccessibilityNodeInfo.ROOT_NODE_ID, focus);
382 }
383
384 /**
Svetoslavbbfa5852013-02-11 19:38:12 -0800385 * Gets the an {@link AccessibilityServiceInfo} describing this UiAutomation.
386 * This method is useful if one wants to change some of the dynamically
387 * configurable properties at runtime.
388 *
389 * @return The accessibility service info.
390 *
391 * @see AccessibilityServiceInfo
392 */
393 public final AccessibilityServiceInfo getServiceInfo() {
394 final IAccessibilityServiceConnection connection;
395 synchronized (mLock) {
396 throwIfNotConnectedLocked();
397 connection = AccessibilityInteractionClient.getInstance()
398 .getConnection(mConnectionId);
399 }
400 // Calling out without a lock held.
401 if (connection != null) {
402 try {
403 return connection.getServiceInfo();
404 } catch (RemoteException re) {
405 Log.w(LOG_TAG, "Error while getting AccessibilityServiceInfo", re);
406 }
407 }
408 return null;
409 }
410
411 /**
412 * Sets the {@link AccessibilityServiceInfo} that describes how this
413 * UiAutomation will be handled by the platform accessibility layer.
414 *
415 * @param info The info.
416 *
417 * @see AccessibilityServiceInfo
418 */
419 public final void setServiceInfo(AccessibilityServiceInfo info) {
420 final IAccessibilityServiceConnection connection;
421 synchronized (mLock) {
422 throwIfNotConnectedLocked();
423 AccessibilityInteractionClient.getInstance().clearCache();
424 connection = AccessibilityInteractionClient.getInstance()
425 .getConnection(mConnectionId);
426 }
427 // Calling out without a lock held.
428 if (connection != null) {
429 try {
430 connection.setServiceInfo(info);
431 } catch (RemoteException re) {
432 Log.w(LOG_TAG, "Error while setting AccessibilityServiceInfo", re);
433 }
434 }
435 }
436
437 /**
Svetoslav8e3feb12014-02-24 13:46:47 -0800438 * Gets the windows on the screen. This method returns only the windows
439 * that a sighted user can interact with, as opposed to all windows.
440 * For example, if there is a modal dialog shown and the user cannot touch
441 * anything behind it, then only the modal window will be reported
442 * (assuming it is the top one). For convenience the returned windows
443 * are ordered in a descending layer order, which is the windows that
444 * are higher in the Z-order are reported first.
445 * <p>
446 * <strong>Note:</strong> In order to access the windows you have to opt-in
447 * to retrieve the interactive windows by setting the
448 * {@link AccessibilityServiceInfo#FLAG_RETRIEVE_INTERACTIVE_WINDOWS} flag.
449 * </p>
450 *
451 * @return The windows if there are windows such, otherwise an empty list.
452 */
453 public List<AccessibilityWindowInfo> getWindows() {
454 final int connectionId;
455 synchronized (mLock) {
456 throwIfNotConnectedLocked();
457 connectionId = mConnectionId;
458 }
459 // Calling out without a lock held.
460 return AccessibilityInteractionClient.getInstance()
461 .getWindows(connectionId);
462 }
463
464 /**
Svetoslav Ganov80943d82013-01-02 10:25:37 -0800465 * Gets the root {@link AccessibilityNodeInfo} in the active window.
466 *
467 * @return The root info.
468 */
469 public AccessibilityNodeInfo getRootInActiveWindow() {
470 final int connectionId;
471 synchronized (mLock) {
472 throwIfNotConnectedLocked();
473 connectionId = mConnectionId;
474 }
475 // Calling out without a lock held.
476 return AccessibilityInteractionClient.getInstance()
477 .getRootInActiveWindow(connectionId);
478 }
479
480 /**
481 * A method for injecting an arbitrary input event.
482 * <p>
483 * <strong>Note:</strong> It is caller's responsibility to recycle the event.
484 * </p>
485 * @param event The event to inject.
486 * @param sync Whether to inject the event synchronously.
487 * @return Whether event injection succeeded.
488 */
489 public boolean injectInputEvent(InputEvent event, boolean sync) {
490 synchronized (mLock) {
491 throwIfNotConnectedLocked();
492 }
493 try {
494 if (DEBUG) {
495 Log.i(LOG_TAG, "Injecting: " + event + " sync: " + sync);
496 }
497 // Calling out without a lock held.
498 return mUiAutomationConnection.injectInputEvent(event, sync);
499 } catch (RemoteException re) {
500 Log.e(LOG_TAG, "Error while injecting input event!", re);
501 }
502 return false;
503 }
504
505 /**
506 * Sets the device rotation. A client can freeze the rotation in
507 * desired state or freeze the rotation to its current state or
508 * unfreeze the rotation (rotating the device changes its rotation
509 * state).
510 *
511 * @param rotation The desired rotation.
512 * @return Whether the rotation was set successfully.
513 *
514 * @see #ROTATION_FREEZE_0
515 * @see #ROTATION_FREEZE_90
516 * @see #ROTATION_FREEZE_180
517 * @see #ROTATION_FREEZE_270
518 * @see #ROTATION_FREEZE_CURRENT
519 * @see #ROTATION_UNFREEZE
520 */
521 public boolean setRotation(int rotation) {
522 synchronized (mLock) {
523 throwIfNotConnectedLocked();
524 }
525 switch (rotation) {
526 case ROTATION_FREEZE_0:
527 case ROTATION_FREEZE_90:
528 case ROTATION_FREEZE_180:
529 case ROTATION_FREEZE_270:
530 case ROTATION_UNFREEZE:
531 case ROTATION_FREEZE_CURRENT: {
532 try {
533 // Calling out without a lock held.
534 mUiAutomationConnection.setRotation(rotation);
535 return true;
536 } catch (RemoteException re) {
537 Log.e(LOG_TAG, "Error while setting rotation!", re);
538 }
539 } return false;
540 default: {
541 throw new IllegalArgumentException("Invalid rotation.");
542 }
543 }
544 }
545
546 /**
547 * Executes a command and waits for a specific accessibility event up to a
548 * given wait timeout. To detect a sequence of events one can implement a
549 * filter that keeps track of seen events of the expected sequence and
550 * returns true after the last event of that sequence is received.
551 * <p>
552 * <strong>Note:</strong> It is caller's responsibility to recycle the returned event.
553 * </p>
554 * @param command The command to execute.
555 * @param filter Filter that recognizes the expected event.
556 * @param timeoutMillis The wait timeout in milliseconds.
557 *
558 * @throws TimeoutException If the expected event is not received within the timeout.
559 */
560 public AccessibilityEvent executeAndWaitForEvent(Runnable command,
Svetoslav550b48f2013-02-12 14:56:29 -0800561 AccessibilityEventFilter filter, long timeoutMillis) throws TimeoutException {
Svetoslavdb7da0e2013-04-22 18:34:02 -0700562 // Acquire the lock and prepare for receiving events.
Svetoslav Ganov80943d82013-01-02 10:25:37 -0800563 synchronized (mLock) {
564 throwIfNotConnectedLocked();
Svetoslav Ganov80943d82013-01-02 10:25:37 -0800565 mEventQueue.clear();
566 // Prepare to wait for an event.
567 mWaitingForEventDelivery = true;
Svetoslavdb7da0e2013-04-22 18:34:02 -0700568 }
Svetoslav Ganov80943d82013-01-02 10:25:37 -0800569
Svetoslavdb7da0e2013-04-22 18:34:02 -0700570 // Note: We have to release the lock since calling out with this lock held
571 // can bite. We will correctly filter out events from other interactions,
572 // so starting to collect events before running the action is just fine.
Svetoslav Ganov80943d82013-01-02 10:25:37 -0800573
Svetoslavdb7da0e2013-04-22 18:34:02 -0700574 // We will ignore events from previous interactions.
575 final long executionStartTimeMillis = SystemClock.uptimeMillis();
576 // Execute the command *without* the lock being held.
577 command.run();
578
579 // Acquire the lock and wait for the event.
580 synchronized (mLock) {
Svetoslav Ganov80943d82013-01-02 10:25:37 -0800581 try {
582 // Wait for the event.
583 final long startTimeMillis = SystemClock.uptimeMillis();
584 while (true) {
585 // Drain the event queue
586 while (!mEventQueue.isEmpty()) {
587 AccessibilityEvent event = mEventQueue.remove(0);
588 // Ignore events from previous interactions.
Svetoslavdb7da0e2013-04-22 18:34:02 -0700589 if (event.getEventTime() < executionStartTimeMillis) {
Svetoslav Ganov80943d82013-01-02 10:25:37 -0800590 continue;
591 }
Svetoslav550b48f2013-02-12 14:56:29 -0800592 if (filter.accept(event)) {
Svetoslav Ganov80943d82013-01-02 10:25:37 -0800593 return event;
594 }
595 event.recycle();
596 }
597 // Check if timed out and if not wait.
598 final long elapsedTimeMillis = SystemClock.uptimeMillis() - startTimeMillis;
599 final long remainingTimeMillis = timeoutMillis - elapsedTimeMillis;
600 if (remainingTimeMillis <= 0) {
601 throw new TimeoutException("Expected event not received within: "
602 + timeoutMillis + " ms.");
603 }
604 try {
605 mLock.wait(remainingTimeMillis);
606 } catch (InterruptedException ie) {
607 /* ignore */
608 }
609 }
610 } finally {
611 mWaitingForEventDelivery = false;
612 mEventQueue.clear();
613 mLock.notifyAll();
614 }
615 }
616 }
617
618 /**
619 * Waits for the accessibility event stream to become idle, which is not to
620 * have received an accessibility event within <code>idleTimeoutMillis</code>.
621 * The total time spent to wait for an idle accessibility event stream is bounded
622 * by the <code>globalTimeoutMillis</code>.
623 *
624 * @param idleTimeoutMillis The timeout in milliseconds between two events
625 * to consider the device idle.
626 * @param globalTimeoutMillis The maximal global timeout in milliseconds in
627 * which to wait for an idle state.
628 *
629 * @throws TimeoutException If no idle state was detected within
630 * <code>globalTimeoutMillis.</code>
631 */
632 public void waitForIdle(long idleTimeoutMillis, long globalTimeoutMillis)
633 throws TimeoutException {
634 synchronized (mLock) {
635 throwIfNotConnectedLocked();
636
637 final long startTimeMillis = SystemClock.uptimeMillis();
638 if (mLastEventTimeMillis <= 0) {
639 mLastEventTimeMillis = startTimeMillis;
640 }
641
642 while (true) {
643 final long currentTimeMillis = SystemClock.uptimeMillis();
644 // Did we get idle state within the global timeout?
645 final long elapsedGlobalTimeMillis = currentTimeMillis - startTimeMillis;
646 final long remainingGlobalTimeMillis =
647 globalTimeoutMillis - elapsedGlobalTimeMillis;
648 if (remainingGlobalTimeMillis <= 0) {
649 throw new TimeoutException("No idle state with idle timeout: "
650 + idleTimeoutMillis + " within global timeout: "
651 + globalTimeoutMillis);
652 }
653 // Did we get an idle state within the idle timeout?
654 final long elapsedIdleTimeMillis = currentTimeMillis - mLastEventTimeMillis;
655 final long remainingIdleTimeMillis = idleTimeoutMillis - elapsedIdleTimeMillis;
656 if (remainingIdleTimeMillis <= 0) {
657 return;
658 }
659 try {
660 mLock.wait(remainingIdleTimeMillis);
661 } catch (InterruptedException ie) {
662 /* ignore */
663 }
664 }
665 }
666 }
667
668 /**
669 * Takes a screenshot.
670 *
671 * @return The screenshot bitmap on success, null otherwise.
672 */
673 public Bitmap takeScreenshot() {
674 synchronized (mLock) {
675 throwIfNotConnectedLocked();
676 }
677 Display display = DisplayManagerGlobal.getInstance()
678 .getRealDisplay(Display.DEFAULT_DISPLAY);
679 Point displaySize = new Point();
680 display.getRealSize(displaySize);
681 final int displayWidth = displaySize.x;
682 final int displayHeight = displaySize.y;
683
684 final float screenshotWidth;
685 final float screenshotHeight;
686
687 final int rotation = display.getRotation();
688 switch (rotation) {
689 case ROTATION_FREEZE_0: {
690 screenshotWidth = displayWidth;
691 screenshotHeight = displayHeight;
692 } break;
693 case ROTATION_FREEZE_90: {
694 screenshotWidth = displayHeight;
695 screenshotHeight = displayWidth;
696 } break;
697 case ROTATION_FREEZE_180: {
698 screenshotWidth = displayWidth;
699 screenshotHeight = displayHeight;
700 } break;
701 case ROTATION_FREEZE_270: {
702 screenshotWidth = displayHeight;
703 screenshotHeight = displayWidth;
704 } break;
705 default: {
706 throw new IllegalArgumentException("Invalid rotation: "
707 + rotation);
708 }
709 }
710
711 // Take the screenshot
712 Bitmap screenShot = null;
713 try {
714 // Calling out without a lock held.
715 screenShot = mUiAutomationConnection.takeScreenshot((int) screenshotWidth,
716 (int) screenshotHeight);
717 if (screenShot == null) {
718 return null;
719 }
720 } catch (RemoteException re) {
721 Log.e(LOG_TAG, "Error while taking screnshot!", re);
722 return null;
723 }
724
725 // Rotate the screenshot to the current orientation
726 if (rotation != ROTATION_FREEZE_0) {
727 Bitmap unrotatedScreenShot = Bitmap.createBitmap(displayWidth, displayHeight,
728 Bitmap.Config.ARGB_8888);
729 Canvas canvas = new Canvas(unrotatedScreenShot);
730 canvas.translate(unrotatedScreenShot.getWidth() / 2,
731 unrotatedScreenShot.getHeight() / 2);
732 canvas.rotate(getDegreesForRotation(rotation));
733 canvas.translate(- screenshotWidth / 2, - screenshotHeight / 2);
734 canvas.drawBitmap(screenShot, 0, 0, null);
735 canvas.setBitmap(null);
Leon Scroggins IIIb101ebe2014-07-02 17:09:34 -0400736 screenShot.recycle();
Svetoslav Ganov80943d82013-01-02 10:25:37 -0800737 screenShot = unrotatedScreenShot;
738 }
739
740 // Optimization
741 screenShot.setHasAlpha(false);
742
743 return screenShot;
744 }
745
Adam Momtaz8f6f1f42013-04-10 12:42:58 -0700746 /**
747 * Sets whether this UiAutomation to run in a "monkey" mode. Applications can query whether
748 * they are executed in a "monkey" mode, i.e. run by a test framework, and avoid doing
749 * potentially undesirable actions such as calling 911 or posting on public forums etc.
750 *
751 * @param enable whether to run in a "monkey" mode or not. Default is not.
Quddus Chonga9f6a9b2015-04-07 13:03:29 -0700752 * @see ActivityManager#isUserAMonkey()
Adam Momtaz8f6f1f42013-04-10 12:42:58 -0700753 */
754 public void setRunAsMonkey(boolean enable) {
755 synchronized (mLock) {
756 throwIfNotConnectedLocked();
757 }
758 try {
759 ActivityManagerNative.getDefault().setUserIsMonkey(enable);
760 } catch (RemoteException re) {
761 Log.e(LOG_TAG, "Error while setting run as monkey!", re);
762 }
763 }
764
Svetoslav1376d602014-03-13 11:17:26 -0700765 /**
766 * Clears the frame statistics for the content of a given window. These
767 * statistics contain information about the most recently rendered content
768 * frames.
769 *
770 * @param windowId The window id.
771 * @return Whether the window is present and its frame statistics
772 * were cleared.
773 *
774 * @see android.view.WindowContentFrameStats
775 * @see #getWindowContentFrameStats(int)
776 * @see #getWindows()
777 * @see AccessibilityWindowInfo#getId() AccessibilityWindowInfo.getId()
778 */
779 public boolean clearWindowContentFrameStats(int windowId) {
780 synchronized (mLock) {
781 throwIfNotConnectedLocked();
782 }
783 try {
784 if (DEBUG) {
785 Log.i(LOG_TAG, "Clearing content frame stats for window: " + windowId);
786 }
787 // Calling out without a lock held.
788 return mUiAutomationConnection.clearWindowContentFrameStats(windowId);
789 } catch (RemoteException re) {
790 Log.e(LOG_TAG, "Error clearing window content frame stats!", re);
791 }
792 return false;
793 }
794
795 /**
796 * Gets the frame statistics for a given window. These statistics contain
797 * information about the most recently rendered content frames.
798 * <p>
799 * A typical usage requires clearing the window frame statistics via {@link
800 * #clearWindowContentFrameStats(int)} followed by an interaction with the UI and
801 * finally getting the window frame statistics via calling this method.
802 * </p>
803 * <pre>
804 * // Assume we have at least one window.
805 * final int windowId = getWindows().get(0).getId();
806 *
807 * // Start with a clean slate.
808 * uiAutimation.clearWindowContentFrameStats(windowId);
809 *
810 * // Do stuff with the UI.
811 *
812 * // Get the frame statistics.
813 * WindowContentFrameStats stats = uiAutomation.getWindowContentFrameStats(windowId);
814 * </pre>
815 *
816 * @param windowId The window id.
817 * @return The window frame statistics, or null if the window is not present.
818 *
819 * @see android.view.WindowContentFrameStats
820 * @see #clearWindowContentFrameStats(int)
821 * @see #getWindows()
822 * @see AccessibilityWindowInfo#getId() AccessibilityWindowInfo.getId()
823 */
824 public WindowContentFrameStats getWindowContentFrameStats(int windowId) {
825 synchronized (mLock) {
826 throwIfNotConnectedLocked();
827 }
828 try {
829 if (DEBUG) {
830 Log.i(LOG_TAG, "Getting content frame stats for window: " + windowId);
831 }
832 // Calling out without a lock held.
833 return mUiAutomationConnection.getWindowContentFrameStats(windowId);
834 } catch (RemoteException re) {
835 Log.e(LOG_TAG, "Error getting window content frame stats!", re);
836 }
837 return null;
838 }
839
840 /**
841 * Clears the window animation rendering statistics. These statistics contain
842 * information about the most recently rendered window animation frames, i.e.
843 * for window transition animations.
844 *
845 * @see android.view.WindowAnimationFrameStats
846 * @see #getWindowAnimationFrameStats()
847 * @see android.R.styleable#WindowAnimation
848 */
849 public void clearWindowAnimationFrameStats() {
850 synchronized (mLock) {
851 throwIfNotConnectedLocked();
852 }
853 try {
854 if (DEBUG) {
855 Log.i(LOG_TAG, "Clearing window animation frame stats");
856 }
857 // Calling out without a lock held.
858 mUiAutomationConnection.clearWindowAnimationFrameStats();
859 } catch (RemoteException re) {
860 Log.e(LOG_TAG, "Error clearing window animation frame stats!", re);
861 }
862 }
863
864 /**
865 * Gets the window animation frame statistics. These statistics contain
866 * information about the most recently rendered window animation frames, i.e.
867 * for window transition animations.
868 *
869 * <p>
870 * A typical usage requires clearing the window animation frame statistics via
871 * {@link #clearWindowAnimationFrameStats()} followed by an interaction that causes
872 * a window transition which uses a window animation and finally getting the window
873 * animation frame statistics by calling this method.
874 * </p>
875 * <pre>
876 * // Start with a clean slate.
877 * uiAutimation.clearWindowAnimationFrameStats();
878 *
879 * // Do stuff to trigger a window transition.
880 *
881 * // Get the frame statistics.
882 * WindowAnimationFrameStats stats = uiAutomation.getWindowAnimationFrameStats();
883 * </pre>
884 *
885 * @return The window animation frame statistics.
886 *
887 * @see android.view.WindowAnimationFrameStats
888 * @see #clearWindowAnimationFrameStats()
889 * @see android.R.styleable#WindowAnimation
890 */
891 public WindowAnimationFrameStats getWindowAnimationFrameStats() {
892 synchronized (mLock) {
893 throwIfNotConnectedLocked();
894 }
895 try {
896 if (DEBUG) {
897 Log.i(LOG_TAG, "Getting window animation frame stats");
898 }
899 // Calling out without a lock held.
900 return mUiAutomationConnection.getWindowAnimationFrameStats();
901 } catch (RemoteException re) {
902 Log.e(LOG_TAG, "Error getting window animation frame stats!", re);
903 }
904 return null;
905 }
906
Svetoslav121e0c02014-05-08 18:51:25 -0700907 /**
Svet Ganov52153f42015-08-11 08:59:12 -0700908 * Grants a runtime permission to a package for a user.
909 * @param packageName The package to which to grant.
910 * @param permission The permission to grant.
911 * @return Whether granting succeeded.
912 *
913 * @hide
914 */
Jeff Sharkeya73b8fd2016-01-06 17:02:08 -0700915 @TestApi
Svet Ganov52153f42015-08-11 08:59:12 -0700916 public boolean grantRuntimePermission(String packageName, String permission,
917 UserHandle userHandle) {
918 synchronized (mLock) {
919 throwIfNotConnectedLocked();
920 }
921 try {
922 if (DEBUG) {
923 Log.i(LOG_TAG, "Granting runtime permission");
924 }
925 // Calling out without a lock held.
926 mUiAutomationConnection.grantRuntimePermission(packageName,
927 permission, userHandle.getIdentifier());
928 // TODO: The package manager API should return boolean.
929 return true;
930 } catch (RemoteException re) {
931 Log.e(LOG_TAG, "Error granting runtime permission", re);
932 }
933 return false;
934 }
935
936 /**
937 * Revokes a runtime permission from a package for a user.
938 * @param packageName The package from which to revoke.
939 * @param permission The permission to revoke.
940 * @return Whether revoking succeeded.
941 *
942 * @hide
943 */
Jeff Sharkeya73b8fd2016-01-06 17:02:08 -0700944 @TestApi
Svet Ganov52153f42015-08-11 08:59:12 -0700945 public boolean revokeRuntimePermission(String packageName, String permission,
946 UserHandle userHandle) {
947 synchronized (mLock) {
948 throwIfNotConnectedLocked();
949 }
950 try {
951 if (DEBUG) {
952 Log.i(LOG_TAG, "Revoking runtime permission");
953 }
954 // Calling out without a lock held.
955 mUiAutomationConnection.revokeRuntimePermission(packageName,
956 permission, userHandle.getIdentifier());
957 // TODO: The package manager API should return boolean.
958 return true;
959 } catch (RemoteException re) {
960 Log.e(LOG_TAG, "Error revoking runtime permission", re);
961 }
962 return false;
963 }
964
965 /**
Svetoslav121e0c02014-05-08 18:51:25 -0700966 * Executes a shell command. This method returs a file descriptor that points
967 * to the standard output stream. The command execution is similar to running
968 * "adb shell <command>" from a host connected to the device.
969 * <p>
970 * <strong>Note:</strong> It is your responsibility to close the retunred file
971 * descriptor once you are done reading.
972 * </p>
973 *
974 * @param command The command to execute.
975 * @return A file descriptor to the standard output stream.
976 */
977 public ParcelFileDescriptor executeShellCommand(String command) {
978 synchronized (mLock) {
979 throwIfNotConnectedLocked();
980 }
981
982 ParcelFileDescriptor source = null;
983 ParcelFileDescriptor sink = null;
984
985 try {
986 ParcelFileDescriptor[] pipe = ParcelFileDescriptor.createPipe();
987 source = pipe[0];
988 sink = pipe[1];
989
990 // Calling out without a lock held.
991 mUiAutomationConnection.executeShellCommand(command, sink);
992 } catch (IOException ioe) {
993 Log.e(LOG_TAG, "Error executing shell command!", ioe);
994 } catch (RemoteException re) {
995 Log.e(LOG_TAG, "Error executing shell command!", re);
996 } finally {
997 IoUtils.closeQuietly(sink);
998 }
999
1000 return source;
1001 }
1002
Svetoslav Ganov80943d82013-01-02 10:25:37 -08001003 private static float getDegreesForRotation(int value) {
1004 switch (value) {
1005 case Surface.ROTATION_90: {
1006 return 360f - 90f;
1007 }
1008 case Surface.ROTATION_180: {
1009 return 360f - 180f;
1010 }
1011 case Surface.ROTATION_270: {
1012 return 360f - 270f;
1013 } default: {
1014 return 0;
1015 }
1016 }
1017 }
1018
1019 private boolean isConnectedLocked() {
1020 return mConnectionId != CONNECTION_ID_UNDEFINED;
1021 }
1022
1023 private void throwIfConnectedLocked() {
1024 if (mConnectionId != CONNECTION_ID_UNDEFINED) {
1025 throw new IllegalStateException("UiAutomation not connected!");
1026 }
1027 }
1028
1029 private void throwIfNotConnectedLocked() {
1030 if (!isConnectedLocked()) {
1031 throw new IllegalStateException("UiAutomation not connected!");
1032 }
1033 }
1034
1035 private class IAccessibilityServiceClientImpl extends IAccessibilityServiceClientWrapper {
1036
1037 public IAccessibilityServiceClientImpl(Looper looper) {
1038 super(null, looper, new Callbacks() {
1039 @Override
Svetoslav3a5c7212014-10-14 09:54:26 -07001040 public void init(int connectionId, IBinder windowToken) {
Svetoslav Ganov80943d82013-01-02 10:25:37 -08001041 synchronized (mLock) {
1042 mConnectionId = connectionId;
1043 mLock.notifyAll();
1044 }
1045 }
1046
1047 @Override
1048 public void onServiceConnected() {
1049 /* do nothing */
1050 }
1051
1052 @Override
1053 public void onInterrupt() {
1054 /* do nothing */
1055 }
1056
1057 @Override
1058 public boolean onGesture(int gestureId) {
1059 /* do nothing */
1060 return false;
1061 }
1062
1063 @Override
1064 public void onAccessibilityEvent(AccessibilityEvent event) {
1065 synchronized (mLock) {
1066 mLastEventTimeMillis = event.getEventTime();
1067 if (mWaitingForEventDelivery) {
1068 mEventQueue.add(AccessibilityEvent.obtain(event));
1069 }
1070 mLock.notifyAll();
1071 }
1072 // Calling out only without a lock held.
1073 final OnAccessibilityEventListener listener = mOnAccessibilityEventListener;
1074 if (listener != null) {
1075 listener.onAccessibilityEvent(AccessibilityEvent.obtain(event));
1076 }
1077 }
Svetoslavc4fccd12013-04-09 12:58:41 -07001078
1079 @Override
1080 public boolean onKeyEvent(KeyEvent event) {
1081 return false;
1082 }
Alan Viverette214fb682015-11-17 09:47:11 -05001083
1084 @Override
1085 public void onMagnificationChanged(@NonNull Region region,
1086 float scale, float centerX, float centerY) {
1087 /* do nothing */
1088 }
Phil Weavera6b64f52015-12-04 15:21:35 -08001089
1090 @Override
Anna Galusza9b278112016-01-04 11:37:37 -08001091 public void onSoftKeyboardShowModeChanged(int showMode) {
1092 /* do nothing */
1093 }
1094
1095 @Override
Phil Weavera6b64f52015-12-04 15:21:35 -08001096 public void onPerformGestureResult(int sequence, boolean completedSuccessfully) {
1097 /* do nothing */
1098 }
Svetoslav Ganov80943d82013-01-02 10:25:37 -08001099 });
1100 }
1101 }
1102}