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