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