1 /* 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 17 package android.app; 18 19 import static android.view.Display.DEFAULT_DISPLAY; 20 21 import android.accessibilityservice.AccessibilityServiceInfo; 22 import android.accessibilityservice.IAccessibilityServiceClient; 23 import android.annotation.NonNull; 24 import android.annotation.Nullable; 25 import android.annotation.UserIdInt; 26 import android.compat.annotation.UnsupportedAppUsage; 27 import android.content.Context; 28 import android.graphics.Rect; 29 import android.hardware.input.InputManager; 30 import android.hardware.input.InputManagerGlobal; 31 import android.os.Binder; 32 import android.os.Build; 33 import android.os.IBinder; 34 import android.os.ParcelFileDescriptor; 35 import android.os.Process; 36 import android.os.RemoteException; 37 import android.os.ServiceManager; 38 import android.os.UserHandle; 39 import android.permission.IPermissionManager; 40 import android.util.Log; 41 import android.view.IWindowManager; 42 import android.view.InputDevice; 43 import android.view.InputEvent; 44 import android.view.KeyEvent; 45 import android.view.MotionEvent; 46 import android.view.SurfaceControl; 47 import android.view.WindowAnimationFrameStats; 48 import android.view.WindowContentFrameStats; 49 import android.view.accessibility.AccessibilityEvent; 50 import android.view.accessibility.IAccessibilityManager; 51 import android.window.ScreenCapture; 52 import android.window.ScreenCapture.CaptureArgs; 53 54 import libcore.io.IoUtils; 55 56 import java.io.FileInputStream; 57 import java.io.FileOutputStream; 58 import java.io.IOException; 59 import java.io.InputStream; 60 import java.io.OutputStream; 61 import java.util.List; 62 63 /** 64 * This is a remote object that is passed from the shell to an instrumentation 65 * for enabling access to privileged operations which the shell can do and the 66 * instrumentation cannot. These privileged operations are needed for implementing 67 * a {@link UiAutomation} that enables across application testing by simulating 68 * user actions and performing screen introspection. 69 * 70 * @hide 71 */ 72 public final class UiAutomationConnection extends IUiAutomationConnection.Stub { 73 74 private static final String TAG = "UiAutomationConnection"; 75 76 private static final int INITIAL_FROZEN_ROTATION_UNSPECIFIED = -1; 77 78 private final IWindowManager mWindowManager = IWindowManager.Stub.asInterface( 79 ServiceManager.getService(Service.WINDOW_SERVICE)); 80 81 private final IAccessibilityManager mAccessibilityManager = IAccessibilityManager.Stub 82 .asInterface(ServiceManager.getService(Service.ACCESSIBILITY_SERVICE)); 83 84 private final IPermissionManager mPermissionManager = IPermissionManager.Stub 85 .asInterface(ServiceManager.getService("permissionmgr")); 86 87 private final IActivityManager mActivityManager = IActivityManager.Stub 88 .asInterface(ServiceManager.getService("activity")); 89 90 private final Object mLock = new Object(); 91 92 private final Binder mToken = new Binder(); 93 94 private int mInitialFrozenRotation = INITIAL_FROZEN_ROTATION_UNSPECIFIED; 95 96 private IAccessibilityServiceClient mClient; 97 98 private boolean mIsShutdown; 99 100 private int mOwningUid; 101 102 @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553) UiAutomationConnection()103 public UiAutomationConnection() { 104 Log.d(TAG, "Created on user " + Process.myUserHandle()); 105 } 106 107 @Override connect(IAccessibilityServiceClient client, int flags)108 public void connect(IAccessibilityServiceClient client, int flags) { 109 if (client == null) { 110 throw new IllegalArgumentException("Client cannot be null!"); 111 } 112 synchronized (mLock) { 113 throwIfShutdownLocked(); 114 if (isConnectedLocked()) { 115 throw new IllegalStateException("Already connected."); 116 } 117 mOwningUid = Binder.getCallingUid(); 118 registerUiTestAutomationServiceLocked(client, 119 Binder.getCallingUserHandle().getIdentifier(), flags); 120 storeRotationStateLocked(); 121 } 122 } 123 124 @Override disconnect()125 public void disconnect() { 126 synchronized (mLock) { 127 throwIfCalledByNotTrustedUidLocked(); 128 throwIfShutdownLocked(); 129 if (!isConnectedLocked()) { 130 throw new IllegalStateException("Already disconnected."); 131 } 132 mOwningUid = -1; 133 unregisterUiTestAutomationServiceLocked(); 134 restoreRotationStateLocked(); 135 } 136 } 137 138 @Override injectInputEvent(InputEvent event, boolean sync, boolean waitForAnimations)139 public boolean injectInputEvent(InputEvent event, boolean sync, boolean waitForAnimations) { 140 synchronized (mLock) { 141 throwIfCalledByNotTrustedUidLocked(); 142 throwIfShutdownLocked(); 143 throwIfNotConnectedLocked(); 144 } 145 146 final boolean syncTransactionsBefore; 147 final boolean syncTransactionsAfter; 148 if (event instanceof KeyEvent) { 149 KeyEvent keyEvent = (KeyEvent) event; 150 syncTransactionsBefore = keyEvent.getAction() == KeyEvent.ACTION_DOWN; 151 syncTransactionsAfter = keyEvent.getAction() == KeyEvent.ACTION_UP; 152 } else { 153 MotionEvent motionEvent = (MotionEvent) event; 154 syncTransactionsBefore = motionEvent.getAction() == MotionEvent.ACTION_DOWN 155 || motionEvent.isFromSource(InputDevice.SOURCE_MOUSE); 156 syncTransactionsAfter = motionEvent.getAction() == MotionEvent.ACTION_UP; 157 } 158 159 final long identity = Binder.clearCallingIdentity(); 160 try { 161 if (syncTransactionsBefore) { 162 mWindowManager.syncInputTransactions(waitForAnimations); 163 } 164 165 final boolean result = InputManagerGlobal.getInstance().injectInputEvent(event, 166 sync ? InputManager.INJECT_INPUT_EVENT_MODE_WAIT_FOR_FINISH 167 : InputManager.INJECT_INPUT_EVENT_MODE_ASYNC); 168 169 if (syncTransactionsAfter) { 170 mWindowManager.syncInputTransactions(waitForAnimations); 171 } 172 return result; 173 } catch (RemoteException e) { 174 e.rethrowFromSystemServer(); 175 } finally { 176 Binder.restoreCallingIdentity(identity); 177 } 178 return false; 179 } 180 181 @Override injectInputEventToInputFilter(InputEvent event)182 public void injectInputEventToInputFilter(InputEvent event) throws RemoteException { 183 synchronized (mLock) { 184 throwIfCalledByNotTrustedUidLocked(); 185 throwIfShutdownLocked(); 186 throwIfNotConnectedLocked(); 187 } 188 mAccessibilityManager.injectInputEventToInputFilter(event); 189 } 190 191 @Override syncInputTransactions(boolean waitForAnimations)192 public void syncInputTransactions(boolean waitForAnimations) { 193 synchronized (mLock) { 194 throwIfCalledByNotTrustedUidLocked(); 195 throwIfShutdownLocked(); 196 throwIfNotConnectedLocked(); 197 } 198 199 try { 200 mWindowManager.syncInputTransactions(waitForAnimations); 201 } catch (RemoteException e) { 202 } 203 } 204 205 @Override setRotation(int rotation)206 public boolean setRotation(int rotation) { 207 synchronized (mLock) { 208 throwIfCalledByNotTrustedUidLocked(); 209 throwIfShutdownLocked(); 210 throwIfNotConnectedLocked(); 211 } 212 final long identity = Binder.clearCallingIdentity(); 213 try { 214 if (rotation == UiAutomation.ROTATION_UNFREEZE) { 215 mWindowManager.thawRotation(); 216 } else { 217 mWindowManager.freezeRotation(rotation); 218 } 219 return true; 220 } catch (RemoteException re) { 221 /* ignore */ 222 } finally { 223 Binder.restoreCallingIdentity(identity); 224 } 225 return false; 226 } 227 228 @Override takeScreenshot(Rect crop, ScreenCapture.ScreenCaptureListener listener)229 public boolean takeScreenshot(Rect crop, ScreenCapture.ScreenCaptureListener listener) { 230 synchronized (mLock) { 231 throwIfCalledByNotTrustedUidLocked(); 232 throwIfShutdownLocked(); 233 throwIfNotConnectedLocked(); 234 } 235 236 final long identity = Binder.clearCallingIdentity(); 237 try { 238 final CaptureArgs captureArgs = new CaptureArgs.Builder<>() 239 .setSourceCrop(crop) 240 .build(); 241 mWindowManager.captureDisplay(DEFAULT_DISPLAY, captureArgs, listener); 242 } catch (RemoteException re) { 243 re.rethrowAsRuntimeException(); 244 } finally { 245 Binder.restoreCallingIdentity(identity); 246 } 247 248 return true; 249 } 250 251 @Nullable 252 @Override takeSurfaceControlScreenshot(@onNull SurfaceControl surfaceControl, ScreenCapture.ScreenCaptureListener listener)253 public boolean takeSurfaceControlScreenshot(@NonNull SurfaceControl surfaceControl, 254 ScreenCapture.ScreenCaptureListener listener) { 255 synchronized (mLock) { 256 throwIfCalledByNotTrustedUidLocked(); 257 throwIfShutdownLocked(); 258 throwIfNotConnectedLocked(); 259 } 260 261 final long identity = Binder.clearCallingIdentity(); 262 try { 263 ScreenCapture.LayerCaptureArgs args = 264 new ScreenCapture.LayerCaptureArgs.Builder(surfaceControl) 265 .setChildrenOnly(false) 266 .build(); 267 int status = ScreenCapture.captureLayers(args, listener); 268 269 if (status != 0) { 270 return false; 271 } 272 } finally { 273 Binder.restoreCallingIdentity(identity); 274 } 275 276 return true; 277 } 278 279 @Override clearWindowContentFrameStats(int windowId)280 public boolean clearWindowContentFrameStats(int windowId) throws RemoteException { 281 synchronized (mLock) { 282 throwIfCalledByNotTrustedUidLocked(); 283 throwIfShutdownLocked(); 284 throwIfNotConnectedLocked(); 285 } 286 int callingUserId = UserHandle.getCallingUserId(); 287 final long identity = Binder.clearCallingIdentity(); 288 try { 289 IBinder token = mAccessibilityManager.getWindowToken(windowId, callingUserId); 290 if (token == null) { 291 return false; 292 } 293 return mWindowManager.clearWindowContentFrameStats(token); 294 } finally { 295 Binder.restoreCallingIdentity(identity); 296 } 297 } 298 299 @Override getWindowContentFrameStats(int windowId)300 public WindowContentFrameStats getWindowContentFrameStats(int windowId) throws RemoteException { 301 synchronized (mLock) { 302 throwIfCalledByNotTrustedUidLocked(); 303 throwIfShutdownLocked(); 304 throwIfNotConnectedLocked(); 305 } 306 int callingUserId = UserHandle.getCallingUserId(); 307 final long identity = Binder.clearCallingIdentity(); 308 try { 309 IBinder token = mAccessibilityManager.getWindowToken(windowId, callingUserId); 310 if (token == null) { 311 return null; 312 } 313 return mWindowManager.getWindowContentFrameStats(token); 314 } finally { 315 Binder.restoreCallingIdentity(identity); 316 } 317 } 318 319 @Override clearWindowAnimationFrameStats()320 public void clearWindowAnimationFrameStats() { 321 synchronized (mLock) { 322 throwIfCalledByNotTrustedUidLocked(); 323 throwIfShutdownLocked(); 324 throwIfNotConnectedLocked(); 325 } 326 final long identity = Binder.clearCallingIdentity(); 327 try { 328 SurfaceControl.clearAnimationFrameStats(); 329 } finally { 330 Binder.restoreCallingIdentity(identity); 331 } 332 } 333 334 @Override getWindowAnimationFrameStats()335 public WindowAnimationFrameStats getWindowAnimationFrameStats() { 336 synchronized (mLock) { 337 throwIfCalledByNotTrustedUidLocked(); 338 throwIfShutdownLocked(); 339 throwIfNotConnectedLocked(); 340 } 341 final long identity = Binder.clearCallingIdentity(); 342 try { 343 WindowAnimationFrameStats stats = new WindowAnimationFrameStats(); 344 SurfaceControl.getAnimationFrameStats(stats); 345 return stats; 346 } finally { 347 Binder.restoreCallingIdentity(identity); 348 } 349 } 350 351 @Override grantRuntimePermission(String packageName, String permission, int userId)352 public void grantRuntimePermission(String packageName, String permission, int userId) 353 throws RemoteException { 354 synchronized (mLock) { 355 throwIfCalledByNotTrustedUidLocked(); 356 throwIfShutdownLocked(); 357 throwIfNotConnectedLocked(); 358 } 359 final long identity = Binder.clearCallingIdentity(); 360 try { 361 mPermissionManager.grantRuntimePermission(packageName, permission, userId); 362 } finally { 363 Binder.restoreCallingIdentity(identity); 364 } 365 } 366 367 @Override revokeRuntimePermission(String packageName, String permission, int userId)368 public void revokeRuntimePermission(String packageName, String permission, int userId) 369 throws RemoteException { 370 synchronized (mLock) { 371 throwIfCalledByNotTrustedUidLocked(); 372 throwIfShutdownLocked(); 373 throwIfNotConnectedLocked(); 374 } 375 final long identity = Binder.clearCallingIdentity(); 376 try { 377 mPermissionManager.revokeRuntimePermission(packageName, permission, userId, null); 378 } finally { 379 Binder.restoreCallingIdentity(identity); 380 } 381 } 382 383 @Override adoptShellPermissionIdentity(int uid, @Nullable String[] permissions)384 public void adoptShellPermissionIdentity(int uid, @Nullable String[] permissions) 385 throws RemoteException { 386 synchronized (mLock) { 387 throwIfCalledByNotTrustedUidLocked(); 388 throwIfShutdownLocked(); 389 throwIfNotConnectedLocked(); 390 } 391 final long identity = Binder.clearCallingIdentity(); 392 try { 393 mActivityManager.startDelegateShellPermissionIdentity(uid, permissions); 394 } finally { 395 Binder.restoreCallingIdentity(identity); 396 } 397 } 398 399 @Override dropShellPermissionIdentity()400 public void dropShellPermissionIdentity() throws RemoteException { 401 synchronized (mLock) { 402 throwIfCalledByNotTrustedUidLocked(); 403 throwIfShutdownLocked(); 404 throwIfNotConnectedLocked(); 405 } 406 final long identity = Binder.clearCallingIdentity(); 407 try { 408 mActivityManager.stopDelegateShellPermissionIdentity(); 409 } finally { 410 Binder.restoreCallingIdentity(identity); 411 } 412 } 413 414 @Override 415 @Nullable getAdoptedShellPermissions()416 public List<String> getAdoptedShellPermissions() throws RemoteException { 417 synchronized (mLock) { 418 throwIfCalledByNotTrustedUidLocked(); 419 throwIfShutdownLocked(); 420 throwIfNotConnectedLocked(); 421 } 422 final long identity = Binder.clearCallingIdentity(); 423 try { 424 return mActivityManager.getDelegatedShellPermissions(); 425 } finally { 426 Binder.restoreCallingIdentity(identity); 427 } 428 } 429 430 public class Repeater implements Runnable { 431 // Continuously read readFrom and write back to writeTo until EOF is encountered 432 private final InputStream readFrom; 433 private final OutputStream writeTo; Repeater(InputStream readFrom, OutputStream writeTo)434 public Repeater (InputStream readFrom, OutputStream writeTo) { 435 this.readFrom = readFrom; 436 this.writeTo = writeTo; 437 } 438 @Override run()439 public void run() { 440 try { 441 final byte[] buffer = new byte[8192]; 442 int readByteCount; 443 while (true) { 444 readByteCount = readFrom.read(buffer); 445 if (readByteCount < 0) { 446 break; 447 } 448 writeTo.write(buffer, 0, readByteCount); 449 writeTo.flush(); 450 } 451 } catch (IOException ignored) { 452 } finally { 453 IoUtils.closeQuietly(readFrom); 454 IoUtils.closeQuietly(writeTo); 455 } 456 } 457 } 458 459 @Override executeShellCommand(final String command, final ParcelFileDescriptor sink, final ParcelFileDescriptor source)460 public void executeShellCommand(final String command, final ParcelFileDescriptor sink, 461 final ParcelFileDescriptor source) throws RemoteException { 462 executeShellCommandWithStderr(command, sink, source, null /* stderrSink */); 463 } 464 465 @Override executeShellCommandWithStderr(final String command, final ParcelFileDescriptor sink, final ParcelFileDescriptor source, final ParcelFileDescriptor stderrSink)466 public void executeShellCommandWithStderr(final String command, final ParcelFileDescriptor sink, 467 final ParcelFileDescriptor source, final ParcelFileDescriptor stderrSink) 468 throws RemoteException { 469 synchronized (mLock) { 470 throwIfCalledByNotTrustedUidLocked(); 471 throwIfShutdownLocked(); 472 throwIfNotConnectedLocked(); 473 } 474 final java.lang.Process process; 475 476 try { 477 process = Runtime.getRuntime().exec(command); 478 } catch (IOException exc) { 479 throw new RuntimeException("Error running shell command '" + command + "'", exc); 480 } 481 482 // Read from process and write to pipe 483 final Thread readFromProcess; 484 if (sink != null) { 485 InputStream sink_in = process.getInputStream();; 486 OutputStream sink_out = new FileOutputStream(sink.getFileDescriptor()); 487 488 readFromProcess = new Thread(new Repeater(sink_in, sink_out)); 489 readFromProcess.start(); 490 } else { 491 readFromProcess = null; 492 } 493 494 // Read from pipe and write to process 495 final Thread writeToProcess; 496 if (source != null) { 497 OutputStream source_out = process.getOutputStream(); 498 InputStream source_in = new FileInputStream(source.getFileDescriptor()); 499 500 writeToProcess = new Thread(new Repeater(source_in, source_out)); 501 writeToProcess.start(); 502 } else { 503 writeToProcess = null; 504 } 505 506 // Read from process stderr and write to pipe 507 final Thread readStderrFromProcess; 508 if (stderrSink != null) { 509 InputStream sink_in = process.getErrorStream(); 510 OutputStream sink_out = new FileOutputStream(stderrSink.getFileDescriptor()); 511 512 readStderrFromProcess = new Thread(new Repeater(sink_in, sink_out)); 513 readStderrFromProcess.start(); 514 } else { 515 readStderrFromProcess = null; 516 } 517 518 Thread cleanup = new Thread(new Runnable() { 519 @Override 520 public void run() { 521 try { 522 if (writeToProcess != null) { 523 writeToProcess.join(); 524 } 525 if (readFromProcess != null) { 526 readFromProcess.join(); 527 } 528 if (readStderrFromProcess != null) { 529 readStderrFromProcess.join(); 530 } 531 } catch (InterruptedException exc) { 532 Log.e(TAG, "At least one of the threads was interrupted"); 533 } 534 IoUtils.closeQuietly(sink); 535 IoUtils.closeQuietly(source); 536 IoUtils.closeQuietly(stderrSink); 537 process.destroy(); 538 } 539 }); 540 cleanup.start(); 541 } 542 543 @Override shutdown()544 public void shutdown() { 545 synchronized (mLock) { 546 if (isConnectedLocked()) { 547 throwIfCalledByNotTrustedUidLocked(); 548 } 549 throwIfShutdownLocked(); 550 mIsShutdown = true; 551 if (isConnectedLocked()) { 552 disconnect(); 553 } 554 } 555 } 556 registerUiTestAutomationServiceLocked(IAccessibilityServiceClient client, @UserIdInt int userId, int flags)557 private void registerUiTestAutomationServiceLocked(IAccessibilityServiceClient client, 558 @UserIdInt int userId, int flags) { 559 IAccessibilityManager manager = IAccessibilityManager.Stub.asInterface( 560 ServiceManager.getService(Context.ACCESSIBILITY_SERVICE)); 561 final AccessibilityServiceInfo info = new AccessibilityServiceInfo(); 562 info.eventTypes = AccessibilityEvent.TYPES_ALL_MASK; 563 info.feedbackType = AccessibilityServiceInfo.FEEDBACK_GENERIC; 564 info.flags |= AccessibilityServiceInfo.FLAG_INCLUDE_NOT_IMPORTANT_VIEWS 565 | AccessibilityServiceInfo.FLAG_REPORT_VIEW_IDS 566 | AccessibilityServiceInfo.FLAG_FORCE_DIRECT_BOOT_AWARE; 567 info.setCapabilities(AccessibilityServiceInfo.CAPABILITY_CAN_RETRIEVE_WINDOW_CONTENT 568 | AccessibilityServiceInfo.CAPABILITY_CAN_REQUEST_TOUCH_EXPLORATION 569 | AccessibilityServiceInfo.CAPABILITY_CAN_REQUEST_FILTER_KEY_EVENTS); 570 if ((flags & UiAutomation.FLAG_NOT_ACCESSIBILITY_TOOL) == 0) { 571 info.setAccessibilityTool(true); 572 } 573 try { 574 // Calling out with a lock held is fine since if the system 575 // process is gone the client calling in will be killed. 576 manager.registerUiTestAutomationService(mToken, client, info, userId, flags); 577 mClient = client; 578 } catch (RemoteException re) { 579 throw new IllegalStateException("Error while registering UiTestAutomationService for " 580 + "user " + userId + ".", re); 581 } 582 } 583 unregisterUiTestAutomationServiceLocked()584 private void unregisterUiTestAutomationServiceLocked() { 585 IAccessibilityManager manager = IAccessibilityManager.Stub.asInterface( 586 ServiceManager.getService(Context.ACCESSIBILITY_SERVICE)); 587 try { 588 // Calling out with a lock held is fine since if the system 589 // process is gone the client calling in will be killed. 590 manager.unregisterUiTestAutomationService(mClient); 591 mClient = null; 592 } catch (RemoteException re) { 593 throw new IllegalStateException("Error while unregistering UiTestAutomationService", 594 re); 595 } 596 } 597 storeRotationStateLocked()598 private void storeRotationStateLocked() { 599 try { 600 if (mWindowManager.isRotationFrozen()) { 601 // Calling out with a lock held is fine since if the system 602 // process is gone the client calling in will be killed. 603 mInitialFrozenRotation = mWindowManager.getDefaultDisplayRotation(); 604 } 605 } catch (RemoteException re) { 606 /* ignore */ 607 } 608 } 609 restoreRotationStateLocked()610 private void restoreRotationStateLocked() { 611 try { 612 if (mInitialFrozenRotation != INITIAL_FROZEN_ROTATION_UNSPECIFIED) { 613 // Calling out with a lock held is fine since if the system 614 // process is gone the client calling in will be killed. 615 mWindowManager.freezeRotation(mInitialFrozenRotation); 616 } else { 617 // Calling out with a lock held is fine since if the system 618 // process is gone the client calling in will be killed. 619 mWindowManager.thawRotation(); 620 } 621 } catch (RemoteException re) { 622 /* ignore */ 623 } 624 } 625 isConnectedLocked()626 private boolean isConnectedLocked() { 627 return mClient != null; 628 } 629 throwIfShutdownLocked()630 private void throwIfShutdownLocked() { 631 if (mIsShutdown) { 632 throw new IllegalStateException("Connection shutdown!"); 633 } 634 } 635 throwIfNotConnectedLocked()636 private void throwIfNotConnectedLocked() { 637 if (!isConnectedLocked()) { 638 throw new IllegalStateException("Not connected!"); 639 } 640 } 641 throwIfCalledByNotTrustedUidLocked()642 private void throwIfCalledByNotTrustedUidLocked() { 643 final int callingUid = Binder.getCallingUid(); 644 if (callingUid != mOwningUid && mOwningUid != Process.SYSTEM_UID 645 && callingUid != 0 /*root*/) { 646 throw new SecurityException("Calling from not trusted UID!"); 647 } 648 } 649 } 650