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