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