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