blob: ce51dba76780ce08f3f3d7c6b4b0f765f9197ed6 [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.AccessibilityServiceInfo;
20import android.accessibilityservice.IAccessibilityServiceClient;
Svet Ganov5e869592018-11-09 09:30:52 -080021import android.annotation.Nullable;
Artur Satayevc895b1b2019-12-10 17:47:51 +000022import android.compat.annotation.UnsupportedAppUsage;
Svetoslav Ganov80943d82013-01-02 10:25:37 -080023import android.content.Context;
Jacky Kao3e33bed2020-01-31 10:31:11 +080024import android.graphics.Bitmap;
25import android.graphics.Rect;
Svetoslav Ganov80943d82013-01-02 10:25:37 -080026import android.hardware.input.InputManager;
27import android.os.Binder;
Svetoslav1376d602014-03-13 11:17:26 -070028import android.os.IBinder;
Svetoslav121e0c02014-05-08 18:51:25 -070029import android.os.ParcelFileDescriptor;
Svetoslav Ganov80943d82013-01-02 10:25:37 -080030import android.os.Process;
31import android.os.RemoteException;
32import android.os.ServiceManager;
Fyodor Kupolove2239c92016-01-12 16:46:13 -080033import android.os.UserHandle;
Todd Kennedyc971a452019-07-08 16:04:52 -070034import android.permission.IPermissionManager;
Svet Ganovd873ae62018-06-25 16:39:23 -070035import android.util.Log;
Svetoslav Ganov80943d82013-01-02 10:25:37 -080036import android.view.IWindowManager;
37import android.view.InputEvent;
Mathias Agopian3866f0d2013-02-11 22:08:48 -080038import android.view.SurfaceControl;
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.IAccessibilityManager;
Jeff Sharkeyb5e89c62016-04-01 23:20:31 -060043
Svetoslav121e0c02014-05-08 18:51:25 -070044import libcore.io.IoUtils;
45
Siarhei Vishniakou55656e42017-03-31 17:23:39 -070046import java.io.FileInputStream;
Svetoslav121e0c02014-05-08 18:51:25 -070047import java.io.FileOutputStream;
48import java.io.IOException;
49import java.io.InputStream;
50import java.io.OutputStream;
Svetoslav Ganov80943d82013-01-02 10:25:37 -080051
52/**
53 * This is a remote object that is passed from the shell to an instrumentation
54 * for enabling access to privileged operations which the shell can do and the
55 * instrumentation cannot. These privileged operations are needed for implementing
56 * a {@link UiAutomation} that enables across application testing by simulating
57 * user actions and performing screen introspection.
58 *
59 * @hide
60 */
61public final class UiAutomationConnection extends IUiAutomationConnection.Stub {
62
Siarhei Vishniakou55656e42017-03-31 17:23:39 -070063 private static final String TAG = "UiAutomationConnection";
64
Svetoslav Ganov80943d82013-01-02 10:25:37 -080065 private static final int INITIAL_FROZEN_ROTATION_UNSPECIFIED = -1;
66
67 private final IWindowManager mWindowManager = IWindowManager.Stub.asInterface(
68 ServiceManager.getService(Service.WINDOW_SERVICE));
69
Svetoslav121e0c02014-05-08 18:51:25 -070070 private final IAccessibilityManager mAccessibilityManager = IAccessibilityManager.Stub
71 .asInterface(ServiceManager.getService(Service.ACCESSIBILITY_SERVICE));
Svetoslav1376d602014-03-13 11:17:26 -070072
Todd Kennedyc971a452019-07-08 16:04:52 -070073 private final IPermissionManager mPermissionManager = IPermissionManager.Stub
74 .asInterface(ServiceManager.getService("permissionmgr"));
Svet Ganov52153f42015-08-11 08:59:12 -070075
Svet Ganovd873ae62018-06-25 16:39:23 -070076 private final IActivityManager mActivityManager = IActivityManager.Stub
77 .asInterface(ServiceManager.getService("activity"));
78
Svetoslav Ganov80943d82013-01-02 10:25:37 -080079 private final Object mLock = new Object();
80
Svetoslav3c55e5c2013-02-27 18:24:28 -080081 private final Binder mToken = new Binder();
82
Svetoslav Ganov80943d82013-01-02 10:25:37 -080083 private int mInitialFrozenRotation = INITIAL_FROZEN_ROTATION_UNSPECIFIED;
84
85 private IAccessibilityServiceClient mClient;
86
87 private boolean mIsShutdown;
88
89 private int mOwningUid;
90
Artur Satayev751e5512019-11-15 19:12:49 +000091 @UnsupportedAppUsage
92 public UiAutomationConnection() {
93 }
94
Jeff Sharkeyb5e89c62016-04-01 23:20:31 -060095 @Override
Phil Weaver1dd87222016-01-26 17:15:15 -080096 public void connect(IAccessibilityServiceClient client, int flags) {
Svetoslav Ganov80943d82013-01-02 10:25:37 -080097 if (client == null) {
98 throw new IllegalArgumentException("Client cannot be null!");
99 }
100 synchronized (mLock) {
101 throwIfShutdownLocked();
102 if (isConnectedLocked()) {
103 throw new IllegalStateException("Already connected.");
104 }
105 mOwningUid = Binder.getCallingUid();
Phil Weaver1dd87222016-01-26 17:15:15 -0800106 registerUiTestAutomationServiceLocked(client, flags);
Svetoslav Ganov80943d82013-01-02 10:25:37 -0800107 storeRotationStateLocked();
108 }
109 }
110
111 @Override
112 public void disconnect() {
113 synchronized (mLock) {
114 throwIfCalledByNotTrustedUidLocked();
115 throwIfShutdownLocked();
116 if (!isConnectedLocked()) {
117 throw new IllegalStateException("Already disconnected.");
118 }
119 mOwningUid = -1;
120 unregisterUiTestAutomationServiceLocked();
121 restoreRotationStateLocked();
122 }
123 }
124
125 @Override
126 public boolean injectInputEvent(InputEvent event, boolean sync) {
127 synchronized (mLock) {
128 throwIfCalledByNotTrustedUidLocked();
129 throwIfShutdownLocked();
130 throwIfNotConnectedLocked();
131 }
132 final int mode = (sync) ? InputManager.INJECT_INPUT_EVENT_MODE_WAIT_FOR_FINISH
133 : InputManager.INJECT_INPUT_EVENT_MODE_ASYNC;
134 final long identity = Binder.clearCallingIdentity();
135 try {
chaviw84dec9f2019-04-22 13:39:46 -0700136 return mWindowManager.injectInputAfterTransactionsApplied(event, mode);
137 } catch (RemoteException e) {
Svetoslav Ganov80943d82013-01-02 10:25:37 -0800138 } finally {
139 Binder.restoreCallingIdentity(identity);
140 }
chaviw84dec9f2019-04-22 13:39:46 -0700141 return false;
Svetoslav Ganov80943d82013-01-02 10:25:37 -0800142 }
143
144 @Override
chaviw84dec9f2019-04-22 13:39:46 -0700145 public void syncInputTransactions() {
146 synchronized (mLock) {
147 throwIfCalledByNotTrustedUidLocked();
148 throwIfShutdownLocked();
149 throwIfNotConnectedLocked();
150 }
151
152 try {
153 mWindowManager.syncInputTransactions();
154 } catch (RemoteException e) {
155 }
156 }
157
158
159 @Override
Svetoslav Ganov80943d82013-01-02 10:25:37 -0800160 public boolean setRotation(int rotation) {
161 synchronized (mLock) {
162 throwIfCalledByNotTrustedUidLocked();
163 throwIfShutdownLocked();
164 throwIfNotConnectedLocked();
165 }
166 final long identity = Binder.clearCallingIdentity();
167 try {
168 if (rotation == UiAutomation.ROTATION_UNFREEZE) {
169 mWindowManager.thawRotation();
170 } else {
171 mWindowManager.freezeRotation(rotation);
172 }
173 return true;
174 } catch (RemoteException re) {
175 /* ignore */
176 } finally {
177 Binder.restoreCallingIdentity(identity);
178 }
179 return false;
180 }
181
182 @Override
Jacky Kao3e33bed2020-01-31 10:31:11 +0800183 public Bitmap takeScreenshot(Rect crop, int rotation) {
184 synchronized (mLock) {
185 throwIfCalledByNotTrustedUidLocked();
186 throwIfShutdownLocked();
187 throwIfNotConnectedLocked();
188 }
189 final long identity = Binder.clearCallingIdentity();
190 try {
191 int width = crop.width();
192 int height = crop.height();
193 return SurfaceControl.screenshot(crop, width, height, rotation);
194 } finally {
195 Binder.restoreCallingIdentity(identity);
196 }
197 }
198
199 @Override
Svetoslav1376d602014-03-13 11:17:26 -0700200 public boolean clearWindowContentFrameStats(int windowId) throws RemoteException {
201 synchronized (mLock) {
202 throwIfCalledByNotTrustedUidLocked();
203 throwIfShutdownLocked();
204 throwIfNotConnectedLocked();
205 }
Fyodor Kupolove2239c92016-01-12 16:46:13 -0800206 int callingUserId = UserHandle.getCallingUserId();
Svetoslav1376d602014-03-13 11:17:26 -0700207 final long identity = Binder.clearCallingIdentity();
208 try {
Fyodor Kupolove2239c92016-01-12 16:46:13 -0800209 IBinder token = mAccessibilityManager.getWindowToken(windowId, callingUserId);
Svetoslav1376d602014-03-13 11:17:26 -0700210 if (token == null) {
211 return false;
212 }
213 return mWindowManager.clearWindowContentFrameStats(token);
214 } finally {
215 Binder.restoreCallingIdentity(identity);
216 }
217 }
218
219 @Override
220 public WindowContentFrameStats getWindowContentFrameStats(int windowId) throws RemoteException {
221 synchronized (mLock) {
222 throwIfCalledByNotTrustedUidLocked();
223 throwIfShutdownLocked();
224 throwIfNotConnectedLocked();
225 }
Fyodor Kupolove2239c92016-01-12 16:46:13 -0800226 int callingUserId = UserHandle.getCallingUserId();
Svetoslav1376d602014-03-13 11:17:26 -0700227 final long identity = Binder.clearCallingIdentity();
228 try {
Fyodor Kupolove2239c92016-01-12 16:46:13 -0800229 IBinder token = mAccessibilityManager.getWindowToken(windowId, callingUserId);
Svetoslav1376d602014-03-13 11:17:26 -0700230 if (token == null) {
231 return null;
232 }
233 return mWindowManager.getWindowContentFrameStats(token);
234 } finally {
235 Binder.restoreCallingIdentity(identity);
236 }
237 }
238
239 @Override
240 public void clearWindowAnimationFrameStats() {
241 synchronized (mLock) {
242 throwIfCalledByNotTrustedUidLocked();
243 throwIfShutdownLocked();
244 throwIfNotConnectedLocked();
245 }
246 final long identity = Binder.clearCallingIdentity();
247 try {
248 SurfaceControl.clearAnimationFrameStats();
249 } finally {
250 Binder.restoreCallingIdentity(identity);
251 }
252 }
253
254 @Override
255 public WindowAnimationFrameStats getWindowAnimationFrameStats() {
256 synchronized (mLock) {
257 throwIfCalledByNotTrustedUidLocked();
258 throwIfShutdownLocked();
259 throwIfNotConnectedLocked();
260 }
261 final long identity = Binder.clearCallingIdentity();
262 try {
263 WindowAnimationFrameStats stats = new WindowAnimationFrameStats();
264 SurfaceControl.getAnimationFrameStats(stats);
265 return stats;
266 } finally {
267 Binder.restoreCallingIdentity(identity);
268 }
269 }
270
271 @Override
Svet Ganov52153f42015-08-11 08:59:12 -0700272 public void grantRuntimePermission(String packageName, String permission, int userId)
273 throws RemoteException {
274 synchronized (mLock) {
275 throwIfCalledByNotTrustedUidLocked();
276 throwIfShutdownLocked();
277 throwIfNotConnectedLocked();
278 }
279 final long identity = Binder.clearCallingIdentity();
280 try {
Todd Kennedyc971a452019-07-08 16:04:52 -0700281 mPermissionManager.grantRuntimePermission(packageName, permission, userId);
Svet Ganov52153f42015-08-11 08:59:12 -0700282 } finally {
283 Binder.restoreCallingIdentity(identity);
284 }
285 }
286
287 @Override
288 public void revokeRuntimePermission(String packageName, String permission, int userId)
289 throws RemoteException {
290 synchronized (mLock) {
291 throwIfCalledByNotTrustedUidLocked();
292 throwIfShutdownLocked();
293 throwIfNotConnectedLocked();
294 }
295 final long identity = Binder.clearCallingIdentity();
296 try {
Evan Seversonaacd48b2020-06-22 16:45:03 -0700297 mPermissionManager.revokeRuntimePermission(packageName, permission, userId, null);
Svet Ganov52153f42015-08-11 08:59:12 -0700298 } finally {
299 Binder.restoreCallingIdentity(identity);
300 }
301 }
302
Svet Ganovd873ae62018-06-25 16:39:23 -0700303 @Override
Svet Ganov5e869592018-11-09 09:30:52 -0800304 public void adoptShellPermissionIdentity(int uid, @Nullable String[] permissions)
305 throws RemoteException {
Svet Ganovd873ae62018-06-25 16:39:23 -0700306 synchronized (mLock) {
307 throwIfCalledByNotTrustedUidLocked();
308 throwIfShutdownLocked();
309 throwIfNotConnectedLocked();
310 }
311 final long identity = Binder.clearCallingIdentity();
312 try {
Svet Ganov5e869592018-11-09 09:30:52 -0800313 mActivityManager.startDelegateShellPermissionIdentity(uid, permissions);
Svet Ganovd873ae62018-06-25 16:39:23 -0700314 } finally {
315 Binder.restoreCallingIdentity(identity);
316 }
317 }
318
319 @Override
320 public void dropShellPermissionIdentity() throws RemoteException {
321 synchronized (mLock) {
322 throwIfCalledByNotTrustedUidLocked();
323 throwIfShutdownLocked();
324 throwIfNotConnectedLocked();
325 }
326 final long identity = Binder.clearCallingIdentity();
327 try {
328 mActivityManager.stopDelegateShellPermissionIdentity();
329 } finally {
330 Binder.restoreCallingIdentity(identity);
331 }
332 }
333
Siarhei Vishniakou55656e42017-03-31 17:23:39 -0700334 public class Repeater implements Runnable {
335 // Continuously read readFrom and write back to writeTo until EOF is encountered
336 private final InputStream readFrom;
337 private final OutputStream writeTo;
338 public Repeater (InputStream readFrom, OutputStream writeTo) {
339 this.readFrom = readFrom;
340 this.writeTo = writeTo;
341 }
342 @Override
343 public void run() {
344 try {
345 final byte[] buffer = new byte[8192];
346 int readByteCount;
347 while (true) {
348 readByteCount = readFrom.read(buffer);
349 if (readByteCount < 0) {
350 break;
351 }
352 writeTo.write(buffer, 0, readByteCount);
353 writeTo.flush();
354 }
355 } catch (IOException ioe) {
Amith Yamasani6283cb82018-12-07 09:41:20 -0800356 Log.w(TAG, "Error while reading/writing to streams");
Siarhei Vishniakou55656e42017-03-31 17:23:39 -0700357 } finally {
358 IoUtils.closeQuietly(readFrom);
359 IoUtils.closeQuietly(writeTo);
360 }
361 }
362 }
363
Svet Ganov52153f42015-08-11 08:59:12 -0700364 @Override
Siarhei Vishniakou55656e42017-03-31 17:23:39 -0700365 public void executeShellCommand(final String command, final ParcelFileDescriptor sink,
366 final ParcelFileDescriptor source) throws RemoteException {
Svetoslav121e0c02014-05-08 18:51:25 -0700367 synchronized (mLock) {
368 throwIfCalledByNotTrustedUidLocked();
369 throwIfShutdownLocked();
370 throwIfNotConnectedLocked();
371 }
Siarhei Vishniakou55656e42017-03-31 17:23:39 -0700372 final java.lang.Process process;
Svetoslav121e0c02014-05-08 18:51:25 -0700373
Siarhei Vishniakou55656e42017-03-31 17:23:39 -0700374 try {
375 process = Runtime.getRuntime().exec(command);
376 } catch (IOException exc) {
377 throw new RuntimeException("Error running shell command '" + command + "'", exc);
378 }
379
380 // Read from process and write to pipe
381 final Thread readFromProcess;
382 if (sink != null) {
383 InputStream sink_in = process.getInputStream();;
384 OutputStream sink_out = new FileOutputStream(sink.getFileDescriptor());
385
386 readFromProcess = new Thread(new Repeater(sink_in, sink_out));
387 readFromProcess.start();
388 } else {
389 readFromProcess = null;
390 }
391
392 // Read from pipe and write to process
393 final Thread writeToProcess;
394 if (source != null) {
395 OutputStream source_out = process.getOutputStream();
396 InputStream source_in = new FileInputStream(source.getFileDescriptor());
397
398 writeToProcess = new Thread(new Repeater(source_in, source_out));
399 writeToProcess.start();
400 } else {
401 writeToProcess = null;
402 }
403
404 Thread cleanup = new Thread(new Runnable() {
405 @Override
Guang Zhu14e26012015-03-18 20:54:46 -0700406 public void run() {
Guang Zhu14e26012015-03-18 20:54:46 -0700407 try {
Siarhei Vishniakou55656e42017-03-31 17:23:39 -0700408 if (writeToProcess != null) {
409 writeToProcess.join();
Guang Zhu14e26012015-03-18 20:54:46 -0700410 }
Siarhei Vishniakou55656e42017-03-31 17:23:39 -0700411 if (readFromProcess != null) {
412 readFromProcess.join();
Guang Zhu843b9922015-06-07 16:16:15 -0700413 }
Siarhei Vishniakou55656e42017-03-31 17:23:39 -0700414 } catch (InterruptedException exc) {
415 Log.e(TAG, "At least one of the threads was interrupted");
Svetoslav121e0c02014-05-08 18:51:25 -0700416 }
Siarhei Vishniakou55656e42017-03-31 17:23:39 -0700417 IoUtils.closeQuietly(sink);
418 IoUtils.closeQuietly(source);
419 process.destroy();
420 }
421 });
422 cleanup.start();
Svetoslav121e0c02014-05-08 18:51:25 -0700423 }
424
425 @Override
Svetoslav Ganov80943d82013-01-02 10:25:37 -0800426 public void shutdown() {
427 synchronized (mLock) {
Svetoslav9663fd092013-10-31 16:28:20 -0700428 if (isConnectedLocked()) {
429 throwIfCalledByNotTrustedUidLocked();
430 }
Svetoslav Ganov80943d82013-01-02 10:25:37 -0800431 throwIfShutdownLocked();
432 mIsShutdown = true;
433 if (isConnectedLocked()) {
434 disconnect();
435 }
436 }
437 }
438
Phil Weaver1dd87222016-01-26 17:15:15 -0800439 private void registerUiTestAutomationServiceLocked(IAccessibilityServiceClient client,
440 int flags) {
Svetoslav Ganov80943d82013-01-02 10:25:37 -0800441 IAccessibilityManager manager = IAccessibilityManager.Stub.asInterface(
442 ServiceManager.getService(Context.ACCESSIBILITY_SERVICE));
Jeff Sharkeyb5e89c62016-04-01 23:20:31 -0600443 final AccessibilityServiceInfo info = new AccessibilityServiceInfo();
Svetoslav Ganov80943d82013-01-02 10:25:37 -0800444 info.eventTypes = AccessibilityEvent.TYPES_ALL_MASK;
445 info.feedbackType = AccessibilityServiceInfo.FEEDBACK_GENERIC;
446 info.flags |= AccessibilityServiceInfo.FLAG_INCLUDE_NOT_IMPORTANT_VIEWS
Jeff Sharkeyb5e89c62016-04-01 23:20:31 -0600447 | AccessibilityServiceInfo.FLAG_REPORT_VIEW_IDS
448 | AccessibilityServiceInfo.FLAG_FORCE_DIRECT_BOOT_AWARE;
Svetoslav11adf6d2013-04-24 14:51:29 -0700449 info.setCapabilities(AccessibilityServiceInfo.CAPABILITY_CAN_RETRIEVE_WINDOW_CONTENT
450 | AccessibilityServiceInfo.CAPABILITY_CAN_REQUEST_TOUCH_EXPLORATION
451 | AccessibilityServiceInfo.CAPABILITY_CAN_REQUEST_ENHANCED_WEB_ACCESSIBILITY
Jacky Kao3e33bed2020-01-31 10:31:11 +0800452 | AccessibilityServiceInfo.CAPABILITY_CAN_REQUEST_FILTER_KEY_EVENTS);
Svetoslav Ganov80943d82013-01-02 10:25:37 -0800453 try {
454 // Calling out with a lock held is fine since if the system
455 // process is gone the client calling in will be killed.
Phil Weaver1dd87222016-01-26 17:15:15 -0800456 manager.registerUiTestAutomationService(mToken, client, info, flags);
Svetoslav Ganov80943d82013-01-02 10:25:37 -0800457 mClient = client;
458 } catch (RemoteException re) {
459 throw new IllegalStateException("Error while registering UiTestAutomationService.", re);
460 }
461 }
462
463 private void unregisterUiTestAutomationServiceLocked() {
464 IAccessibilityManager manager = IAccessibilityManager.Stub.asInterface(
465 ServiceManager.getService(Context.ACCESSIBILITY_SERVICE));
466 try {
467 // Calling out with a lock held is fine since if the system
468 // process is gone the client calling in will be killed.
469 manager.unregisterUiTestAutomationService(mClient);
470 mClient = null;
471 } catch (RemoteException re) {
472 throw new IllegalStateException("Error while unregistering UiTestAutomationService",
473 re);
474 }
475 }
476
477 private void storeRotationStateLocked() {
478 try {
479 if (mWindowManager.isRotationFrozen()) {
480 // Calling out with a lock held is fine since if the system
481 // process is gone the client calling in will be killed.
Andrii Kulian8ee72852017-03-10 10:36:45 -0800482 mInitialFrozenRotation = mWindowManager.getDefaultDisplayRotation();
Svetoslav Ganov80943d82013-01-02 10:25:37 -0800483 }
484 } catch (RemoteException re) {
485 /* ignore */
486 }
487 }
488
489 private void restoreRotationStateLocked() {
490 try {
491 if (mInitialFrozenRotation != INITIAL_FROZEN_ROTATION_UNSPECIFIED) {
492 // Calling out with a lock held is fine since if the system
493 // process is gone the client calling in will be killed.
494 mWindowManager.freezeRotation(mInitialFrozenRotation);
495 } else {
496 // Calling out with a lock held is fine since if the system
497 // process is gone the client calling in will be killed.
498 mWindowManager.thawRotation();
499 }
500 } catch (RemoteException re) {
501 /* ignore */
502 }
503 }
504
505 private boolean isConnectedLocked() {
506 return mClient != null;
507 }
508
509 private void throwIfShutdownLocked() {
510 if (mIsShutdown) {
511 throw new IllegalStateException("Connection shutdown!");
512 }
513 }
514
515 private void throwIfNotConnectedLocked() {
516 if (!isConnectedLocked()) {
517 throw new IllegalStateException("Not connected!");
518 }
519 }
520
521 private void throwIfCalledByNotTrustedUidLocked() {
522 final int callingUid = Binder.getCallingUid();
523 if (callingUid != mOwningUid && mOwningUid != Process.SYSTEM_UID
524 && callingUid != 0 /*root*/) {
525 throw new SecurityException("Calling from not trusted UID!");
526 }
527 }
528}