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.Process; 26 import android.os.RemoteException; 27 import android.os.ServiceManager; 28 import android.view.IWindowManager; 29 import android.view.InputEvent; 30 import android.view.SurfaceControl; 31 import android.view.accessibility.AccessibilityEvent; 32 import android.view.accessibility.IAccessibilityManager; 33 34 /** 35 * This is a remote object that is passed from the shell to an instrumentation 36 * for enabling access to privileged operations which the shell can do and the 37 * instrumentation cannot. These privileged operations are needed for implementing 38 * a {@link UiAutomation} that enables across application testing by simulating 39 * user actions and performing screen introspection. 40 * 41 * @hide 42 */ 43 public final class UiAutomationConnection extends IUiAutomationConnection.Stub { 44 45 private static final int INITIAL_FROZEN_ROTATION_UNSPECIFIED = -1; 46 47 private final IWindowManager mWindowManager = IWindowManager.Stub.asInterface( 48 ServiceManager.getService(Service.WINDOW_SERVICE)); 49 50 private final Object mLock = new Object(); 51 52 private final Binder mToken = new Binder(); 53 54 private int mInitialFrozenRotation = INITIAL_FROZEN_ROTATION_UNSPECIFIED; 55 56 private IAccessibilityServiceClient mClient; 57 58 private boolean mIsShutdown; 59 60 private int mOwningUid; 61 62 public void connect(IAccessibilityServiceClient client) { 63 if (client == null) { 64 throw new IllegalArgumentException("Client cannot be null!"); 65 } 66 synchronized (mLock) { 67 throwIfShutdownLocked(); 68 if (isConnectedLocked()) { 69 throw new IllegalStateException("Already connected."); 70 } 71 mOwningUid = Binder.getCallingUid(); 72 registerUiTestAutomationServiceLocked(client); 73 storeRotationStateLocked(); 74 } 75 } 76 77 @Override 78 public void disconnect() { 79 synchronized (mLock) { 80 throwIfCalledByNotTrustedUidLocked(); 81 throwIfShutdownLocked(); 82 if (!isConnectedLocked()) { 83 throw new IllegalStateException("Already disconnected."); 84 } 85 mOwningUid = -1; 86 unregisterUiTestAutomationServiceLocked(); 87 restoreRotationStateLocked(); 88 } 89 } 90 91 @Override 92 public boolean injectInputEvent(InputEvent event, boolean sync) { 93 synchronized (mLock) { 94 throwIfCalledByNotTrustedUidLocked(); 95 throwIfShutdownLocked(); 96 throwIfNotConnectedLocked(); 97 } 98 final int mode = (sync) ? InputManager.INJECT_INPUT_EVENT_MODE_WAIT_FOR_FINISH 99 : InputManager.INJECT_INPUT_EVENT_MODE_ASYNC; 100 final long identity = Binder.clearCallingIdentity(); 101 try { 102 return InputManager.getInstance().injectInputEvent(event, mode); 103 } finally { 104 Binder.restoreCallingIdentity(identity); 105 } 106 } 107 108 @Override 109 public boolean setRotation(int rotation) { 110 synchronized (mLock) { 111 throwIfCalledByNotTrustedUidLocked(); 112 throwIfShutdownLocked(); 113 throwIfNotConnectedLocked(); 114 } 115 final long identity = Binder.clearCallingIdentity(); 116 try { 117 if (rotation == UiAutomation.ROTATION_UNFREEZE) { 118 mWindowManager.thawRotation(); 119 } else { 120 mWindowManager.freezeRotation(rotation); 121 } 122 return true; 123 } catch (RemoteException re) { 124 /* ignore */ 125 } finally { 126 Binder.restoreCallingIdentity(identity); 127 } 128 return false; 129 } 130 131 @Override 132 public Bitmap takeScreenshot(int width, int height) { 133 synchronized (mLock) { 134 throwIfCalledByNotTrustedUidLocked(); 135 throwIfShutdownLocked(); 136 throwIfNotConnectedLocked(); 137 } 138 final long identity = Binder.clearCallingIdentity(); 139 try { 140 return SurfaceControl.screenshot(width, height); 141 } finally { 142 Binder.restoreCallingIdentity(identity); 143 } 144 } 145 146 @Override 147 public void shutdown() { 148 synchronized (mLock) { 149 if (isConnectedLocked()) { 150 throwIfCalledByNotTrustedUidLocked(); 151 } 152 throwIfShutdownLocked(); 153 mIsShutdown = true; 154 if (isConnectedLocked()) { 155 disconnect(); 156 } 157 } 158 } 159 160 private void registerUiTestAutomationServiceLocked(IAccessibilityServiceClient client) { 161 IAccessibilityManager manager = IAccessibilityManager.Stub.asInterface( 162 ServiceManager.getService(Context.ACCESSIBILITY_SERVICE)); 163 AccessibilityServiceInfo info = new AccessibilityServiceInfo(); 164 info.eventTypes = AccessibilityEvent.TYPES_ALL_MASK; 165 info.feedbackType = AccessibilityServiceInfo.FEEDBACK_GENERIC; 166 info.flags |= AccessibilityServiceInfo.FLAG_INCLUDE_NOT_IMPORTANT_VIEWS 167 | AccessibilityServiceInfo.FLAG_REPORT_VIEW_IDS; 168 info.setCapabilities(AccessibilityServiceInfo.CAPABILITY_CAN_RETRIEVE_WINDOW_CONTENT 169 | AccessibilityServiceInfo.CAPABILITY_CAN_REQUEST_TOUCH_EXPLORATION 170 | AccessibilityServiceInfo.CAPABILITY_CAN_REQUEST_ENHANCED_WEB_ACCESSIBILITY 171 | AccessibilityServiceInfo.CAPABILITY_CAN_REQUEST_FILTER_KEY_EVENTS); 172 try { 173 // Calling out with a lock held is fine since if the system 174 // process is gone the client calling in will be killed. 175 manager.registerUiTestAutomationService(mToken, client, info); 176 mClient = client; 177 } catch (RemoteException re) { 178 throw new IllegalStateException("Error while registering UiTestAutomationService.", re); 179 } 180 } 181 182 private void unregisterUiTestAutomationServiceLocked() { 183 IAccessibilityManager manager = IAccessibilityManager.Stub.asInterface( 184 ServiceManager.getService(Context.ACCESSIBILITY_SERVICE)); 185 try { 186 // Calling out with a lock held is fine since if the system 187 // process is gone the client calling in will be killed. 188 manager.unregisterUiTestAutomationService(mClient); 189 mClient = null; 190 } catch (RemoteException re) { 191 throw new IllegalStateException("Error while unregistering UiTestAutomationService", 192 re); 193 } 194 } 195 196 private void storeRotationStateLocked() { 197 try { 198 if (mWindowManager.isRotationFrozen()) { 199 // Calling out with a lock held is fine since if the system 200 // process is gone the client calling in will be killed. 201 mInitialFrozenRotation = mWindowManager.getRotation(); 202 } 203 } catch (RemoteException re) { 204 /* ignore */ 205 } 206 } 207 208 private void restoreRotationStateLocked() { 209 try { 210 if (mInitialFrozenRotation != INITIAL_FROZEN_ROTATION_UNSPECIFIED) { 211 // Calling out with a lock held is fine since if the system 212 // process is gone the client calling in will be killed. 213 mWindowManager.freezeRotation(mInitialFrozenRotation); 214 } else { 215 // Calling out with a lock held is fine since if the system 216 // process is gone the client calling in will be killed. 217 mWindowManager.thawRotation(); 218 } 219 } catch (RemoteException re) { 220 /* ignore */ 221 } 222 } 223 224 private boolean isConnectedLocked() { 225 return mClient != null; 226 } 227 228 private void throwIfShutdownLocked() { 229 if (mIsShutdown) { 230 throw new IllegalStateException("Connection shutdown!"); 231 } 232 } 233 234 private void throwIfNotConnectedLocked() { 235 if (!isConnectedLocked()) { 236 throw new IllegalStateException("Not connected!"); 237 } 238 } 239 240 private void throwIfCalledByNotTrustedUidLocked() { 241 final int callingUid = Binder.getCallingUid(); 242 if (callingUid != mOwningUid && mOwningUid != Process.SYSTEM_UID 243 && callingUid != 0 /*root*/) { 244 throw new SecurityException("Calling from not trusted UID!"); 245 } 246 } 247 } 248