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 17package android.app; 18 19import android.accessibilityservice.AccessibilityServiceInfo; 20import android.accessibilityservice.IAccessibilityServiceClient; 21import android.content.Context; 22import android.content.pm.IPackageManager; 23import android.graphics.Bitmap; 24import android.graphics.Rect; 25import android.hardware.input.InputManager; 26import android.os.Binder; 27import android.os.IBinder; 28import android.os.ParcelFileDescriptor; 29import android.os.Process; 30import android.os.RemoteException; 31import android.os.ServiceManager; 32import android.os.UserHandle; 33import android.view.IWindowManager; 34import android.view.InputEvent; 35import android.view.SurfaceControl; 36import android.view.WindowAnimationFrameStats; 37import android.view.WindowContentFrameStats; 38import android.view.accessibility.AccessibilityEvent; 39import android.view.accessibility.IAccessibilityManager; 40import android.util.Log; 41 42import libcore.io.IoUtils; 43 44import java.io.FileInputStream; 45import java.io.FileOutputStream; 46import java.io.IOException; 47import java.io.InputStream; 48import 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 */ 59public 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 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 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 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 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 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 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 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 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 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 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 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; 281 public Repeater (InputStream readFrom, OutputStream writeTo) { 282 this.readFrom = readFrom; 283 this.writeTo = writeTo; 284 } 285 @Override 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 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 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 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 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 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 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 448 private boolean isConnectedLocked() { 449 return mClient != null; 450 } 451 452 private void throwIfShutdownLocked() { 453 if (mIsShutdown) { 454 throw new IllegalStateException("Connection shutdown!"); 455 } 456 } 457 458 private void throwIfNotConnectedLocked() { 459 if (!isConnectedLocked()) { 460 throw new IllegalStateException("Not connected!"); 461 } 462 } 463 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