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.Nullable; 22 import android.compat.annotation.UnsupportedAppUsage; 23 import android.content.Context; 24 import android.graphics.Bitmap; 25 import android.graphics.Rect; 26 import android.hardware.input.InputManager; 27 import android.os.Binder; 28 import android.os.IBinder; 29 import android.os.ParcelFileDescriptor; 30 import android.os.Process; 31 import android.os.RemoteException; 32 import android.os.ServiceManager; 33 import android.os.UserHandle; 34 import android.permission.IPermissionManager; 35 import android.util.Log; 36 import android.view.IWindowManager; 37 import android.view.InputEvent; 38 import android.view.SurfaceControl; 39 import android.view.WindowAnimationFrameStats; 40 import android.view.WindowContentFrameStats; 41 import android.view.accessibility.AccessibilityEvent; 42 import android.view.accessibility.IAccessibilityManager; 43 44 import libcore.io.IoUtils; 45 46 import java.io.FileInputStream; 47 import java.io.FileOutputStream; 48 import java.io.IOException; 49 import java.io.InputStream; 50 import java.io.OutputStream; 51 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 */ 61 public final class UiAutomationConnection extends IUiAutomationConnection.Stub { 62 63 private static final String TAG = "UiAutomationConnection"; 64 65 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 70 private final IAccessibilityManager mAccessibilityManager = IAccessibilityManager.Stub 71 .asInterface(ServiceManager.getService(Service.ACCESSIBILITY_SERVICE)); 72 73 private final IPermissionManager mPermissionManager = IPermissionManager.Stub 74 .asInterface(ServiceManager.getService("permissionmgr")); 75 76 private final IActivityManager mActivityManager = IActivityManager.Stub 77 .asInterface(ServiceManager.getService("activity")); 78 79 private final Object mLock = new Object(); 80 81 private final Binder mToken = new Binder(); 82 83 private int mInitialFrozenRotation = INITIAL_FROZEN_ROTATION_UNSPECIFIED; 84 85 private IAccessibilityServiceClient mClient; 86 87 private boolean mIsShutdown; 88 89 private int mOwningUid; 90 91 @UnsupportedAppUsage UiAutomationConnection()92 public UiAutomationConnection() { 93 } 94 95 @Override connect(IAccessibilityServiceClient client, int flags)96 public void connect(IAccessibilityServiceClient client, int flags) { 97 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(); 106 registerUiTestAutomationServiceLocked(client, flags); 107 storeRotationStateLocked(); 108 } 109 } 110 111 @Override disconnect()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 injectInputEvent(InputEvent event, boolean sync)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 { 136 return mWindowManager.injectInputAfterTransactionsApplied(event, mode); 137 } catch (RemoteException e) { 138 } finally { 139 Binder.restoreCallingIdentity(identity); 140 } 141 return false; 142 } 143 144 @Override syncInputTransactions()145 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 setRotation(int rotation)160 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 takeScreenshot(Rect crop, int rotation)183 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 clearWindowContentFrameStats(int windowId)200 public boolean clearWindowContentFrameStats(int windowId) throws RemoteException { 201 synchronized (mLock) { 202 throwIfCalledByNotTrustedUidLocked(); 203 throwIfShutdownLocked(); 204 throwIfNotConnectedLocked(); 205 } 206 int callingUserId = UserHandle.getCallingUserId(); 207 final long identity = Binder.clearCallingIdentity(); 208 try { 209 IBinder token = mAccessibilityManager.getWindowToken(windowId, callingUserId); 210 if (token == null) { 211 return false; 212 } 213 return mWindowManager.clearWindowContentFrameStats(token); 214 } finally { 215 Binder.restoreCallingIdentity(identity); 216 } 217 } 218 219 @Override getWindowContentFrameStats(int windowId)220 public WindowContentFrameStats getWindowContentFrameStats(int windowId) throws RemoteException { 221 synchronized (mLock) { 222 throwIfCalledByNotTrustedUidLocked(); 223 throwIfShutdownLocked(); 224 throwIfNotConnectedLocked(); 225 } 226 int callingUserId = UserHandle.getCallingUserId(); 227 final long identity = Binder.clearCallingIdentity(); 228 try { 229 IBinder token = mAccessibilityManager.getWindowToken(windowId, callingUserId); 230 if (token == null) { 231 return null; 232 } 233 return mWindowManager.getWindowContentFrameStats(token); 234 } finally { 235 Binder.restoreCallingIdentity(identity); 236 } 237 } 238 239 @Override clearWindowAnimationFrameStats()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 getWindowAnimationFrameStats()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 grantRuntimePermission(String packageName, String permission, int userId)272 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 { 281 mPermissionManager.grantRuntimePermission(packageName, permission, userId); 282 } finally { 283 Binder.restoreCallingIdentity(identity); 284 } 285 } 286 287 @Override revokeRuntimePermission(String packageName, String permission, int userId)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 { 297 mPermissionManager.revokeRuntimePermission(packageName, permission, userId, null); 298 } finally { 299 Binder.restoreCallingIdentity(identity); 300 } 301 } 302 303 @Override adoptShellPermissionIdentity(int uid, @Nullable String[] permissions)304 public void adoptShellPermissionIdentity(int uid, @Nullable String[] permissions) 305 throws RemoteException { 306 synchronized (mLock) { 307 throwIfCalledByNotTrustedUidLocked(); 308 throwIfShutdownLocked(); 309 throwIfNotConnectedLocked(); 310 } 311 final long identity = Binder.clearCallingIdentity(); 312 try { 313 mActivityManager.startDelegateShellPermissionIdentity(uid, permissions); 314 } finally { 315 Binder.restoreCallingIdentity(identity); 316 } 317 } 318 319 @Override dropShellPermissionIdentity()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 334 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; Repeater(InputStream readFrom, OutputStream writeTo)338 public Repeater (InputStream readFrom, OutputStream writeTo) { 339 this.readFrom = readFrom; 340 this.writeTo = writeTo; 341 } 342 @Override run()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) { 356 Log.w(TAG, "Error while reading/writing to streams"); 357 } finally { 358 IoUtils.closeQuietly(readFrom); 359 IoUtils.closeQuietly(writeTo); 360 } 361 } 362 } 363 364 @Override executeShellCommand(final String command, final ParcelFileDescriptor sink, final ParcelFileDescriptor source)365 public void executeShellCommand(final String command, final ParcelFileDescriptor sink, 366 final ParcelFileDescriptor source) throws RemoteException { 367 synchronized (mLock) { 368 throwIfCalledByNotTrustedUidLocked(); 369 throwIfShutdownLocked(); 370 throwIfNotConnectedLocked(); 371 } 372 final java.lang.Process process; 373 374 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 406 public void run() { 407 try { 408 if (writeToProcess != null) { 409 writeToProcess.join(); 410 } 411 if (readFromProcess != null) { 412 readFromProcess.join(); 413 } 414 } catch (InterruptedException exc) { 415 Log.e(TAG, "At least one of the threads was interrupted"); 416 } 417 IoUtils.closeQuietly(sink); 418 IoUtils.closeQuietly(source); 419 process.destroy(); 420 } 421 }); 422 cleanup.start(); 423 } 424 425 @Override shutdown()426 public void shutdown() { 427 synchronized (mLock) { 428 if (isConnectedLocked()) { 429 throwIfCalledByNotTrustedUidLocked(); 430 } 431 throwIfShutdownLocked(); 432 mIsShutdown = true; 433 if (isConnectedLocked()) { 434 disconnect(); 435 } 436 } 437 } 438 registerUiTestAutomationServiceLocked(IAccessibilityServiceClient client, int flags)439 private void registerUiTestAutomationServiceLocked(IAccessibilityServiceClient client, 440 int flags) { 441 IAccessibilityManager manager = IAccessibilityManager.Stub.asInterface( 442 ServiceManager.getService(Context.ACCESSIBILITY_SERVICE)); 443 final AccessibilityServiceInfo info = new AccessibilityServiceInfo(); 444 info.eventTypes = AccessibilityEvent.TYPES_ALL_MASK; 445 info.feedbackType = AccessibilityServiceInfo.FEEDBACK_GENERIC; 446 info.flags |= AccessibilityServiceInfo.FLAG_INCLUDE_NOT_IMPORTANT_VIEWS 447 | AccessibilityServiceInfo.FLAG_REPORT_VIEW_IDS 448 | AccessibilityServiceInfo.FLAG_FORCE_DIRECT_BOOT_AWARE; 449 info.setCapabilities(AccessibilityServiceInfo.CAPABILITY_CAN_RETRIEVE_WINDOW_CONTENT 450 | AccessibilityServiceInfo.CAPABILITY_CAN_REQUEST_TOUCH_EXPLORATION 451 | AccessibilityServiceInfo.CAPABILITY_CAN_REQUEST_ENHANCED_WEB_ACCESSIBILITY 452 | AccessibilityServiceInfo.CAPABILITY_CAN_REQUEST_FILTER_KEY_EVENTS); 453 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. 456 manager.registerUiTestAutomationService(mToken, client, info, flags); 457 mClient = client; 458 } catch (RemoteException re) { 459 throw new IllegalStateException("Error while registering UiTestAutomationService.", re); 460 } 461 } 462 unregisterUiTestAutomationServiceLocked()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 storeRotationStateLocked()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. 482 mInitialFrozenRotation = mWindowManager.getDefaultDisplayRotation(); 483 } 484 } catch (RemoteException re) { 485 /* ignore */ 486 } 487 } 488 restoreRotationStateLocked()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 isConnectedLocked()505 private boolean isConnectedLocked() { 506 return mClient != null; 507 } 508 throwIfShutdownLocked()509 private void throwIfShutdownLocked() { 510 if (mIsShutdown) { 511 throw new IllegalStateException("Connection shutdown!"); 512 } 513 } 514 throwIfNotConnectedLocked()515 private void throwIfNotConnectedLocked() { 516 if (!isConnectedLocked()) { 517 throw new IllegalStateException("Not connected!"); 518 } 519 } 520 throwIfCalledByNotTrustedUidLocked()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 } 529