blob: 2c1ee8e136247559b38b3b1f3a0247707a32e320 [file] [log] [blame]
Svetoslav Ganov80943d82013-01-02 10:25:37 -08001/*
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;
Svet Ganov52153f42015-08-11 08:59:12 -070022import android.content.pm.IPackageManager;
Svetoslav Ganov80943d82013-01-02 10:25:37 -080023import android.graphics.Bitmap;
24import android.hardware.input.InputManager;
25import android.os.Binder;
Svetoslav1376d602014-03-13 11:17:26 -070026import android.os.IBinder;
Svetoslav121e0c02014-05-08 18:51:25 -070027import android.os.ParcelFileDescriptor;
Svetoslav Ganov80943d82013-01-02 10:25:37 -080028import android.os.Process;
29import android.os.RemoteException;
30import android.os.ServiceManager;
Fyodor Kupolove2239c92016-01-12 16:46:13 -080031import android.os.UserHandle;
Svetoslav Ganov80943d82013-01-02 10:25:37 -080032import android.view.IWindowManager;
33import android.view.InputEvent;
Mathias Agopian3866f0d2013-02-11 22:08:48 -080034import android.view.SurfaceControl;
Svetoslav1376d602014-03-13 11:17:26 -070035import android.view.WindowAnimationFrameStats;
36import android.view.WindowContentFrameStats;
Svetoslav Ganov80943d82013-01-02 10:25:37 -080037import android.view.accessibility.AccessibilityEvent;
38import android.view.accessibility.IAccessibilityManager;
Jeff Sharkeyb5e89c62016-04-01 23:20:31 -060039
Svetoslav121e0c02014-05-08 18:51:25 -070040import libcore.io.IoUtils;
41
42import java.io.FileOutputStream;
43import java.io.IOException;
44import java.io.InputStream;
45import java.io.OutputStream;
Svetoslav Ganov80943d82013-01-02 10:25:37 -080046
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 */
56public 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
Svetoslav121e0c02014-05-08 18:51:25 -070063 private final IAccessibilityManager mAccessibilityManager = IAccessibilityManager.Stub
64 .asInterface(ServiceManager.getService(Service.ACCESSIBILITY_SERVICE));
Svetoslav1376d602014-03-13 11:17:26 -070065
Svet Ganov52153f42015-08-11 08:59:12 -070066 private final IPackageManager mPackageManager = IPackageManager.Stub
67 .asInterface(ServiceManager.getService("package"));
68
Svetoslav Ganov80943d82013-01-02 10:25:37 -080069 private final Object mLock = new Object();
70
Svetoslav3c55e5c2013-02-27 18:24:28 -080071 private final Binder mToken = new Binder();
72
Svetoslav Ganov80943d82013-01-02 10:25:37 -080073 private int mInitialFrozenRotation = INITIAL_FROZEN_ROTATION_UNSPECIFIED;
74
75 private IAccessibilityServiceClient mClient;
76
77 private boolean mIsShutdown;
78
79 private int mOwningUid;
80
Jeff Sharkeyb5e89c62016-04-01 23:20:31 -060081 @Override
Phil Weaver1dd87222016-01-26 17:15:15 -080082 public void connect(IAccessibilityServiceClient client, int flags) {
Svetoslav Ganov80943d82013-01-02 10:25:37 -080083 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();
Phil Weaver1dd87222016-01-26 17:15:15 -080092 registerUiTestAutomationServiceLocked(client, flags);
Svetoslav Ganov80943d82013-01-02 10:25:37 -080093 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 {
Mathias Agopian3866f0d2013-02-11 22:08:48 -0800160 return SurfaceControl.screenshot(width, height);
Svetoslav Ganov80943d82013-01-02 10:25:37 -0800161 } finally {
162 Binder.restoreCallingIdentity(identity);
163 }
164 }
165
166 @Override
Svetoslav1376d602014-03-13 11:17:26 -0700167 public boolean clearWindowContentFrameStats(int windowId) throws RemoteException {
168 synchronized (mLock) {
169 throwIfCalledByNotTrustedUidLocked();
170 throwIfShutdownLocked();
171 throwIfNotConnectedLocked();
172 }
Fyodor Kupolove2239c92016-01-12 16:46:13 -0800173 int callingUserId = UserHandle.getCallingUserId();
Svetoslav1376d602014-03-13 11:17:26 -0700174 final long identity = Binder.clearCallingIdentity();
175 try {
Fyodor Kupolove2239c92016-01-12 16:46:13 -0800176 IBinder token = mAccessibilityManager.getWindowToken(windowId, callingUserId);
Svetoslav1376d602014-03-13 11:17:26 -0700177 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 }
Fyodor Kupolove2239c92016-01-12 16:46:13 -0800193 int callingUserId = UserHandle.getCallingUserId();
Svetoslav1376d602014-03-13 11:17:26 -0700194 final long identity = Binder.clearCallingIdentity();
195 try {
Fyodor Kupolove2239c92016-01-12 16:46:13 -0800196 IBinder token = mAccessibilityManager.getWindowToken(windowId, callingUserId);
Svetoslav1376d602014-03-13 11:17:26 -0700197 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
Svet Ganov52153f42015-08-11 08:59:12 -0700239 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
Guang Zhu14e26012015-03-18 20:54:46 -0700271 public void executeShellCommand(final String command, final ParcelFileDescriptor sink)
Svetoslav121e0c02014-05-08 18:51:25 -0700272 throws RemoteException {
273 synchronized (mLock) {
274 throwIfCalledByNotTrustedUidLocked();
275 throwIfShutdownLocked();
276 throwIfNotConnectedLocked();
277 }
278
Guang Zhu14e26012015-03-18 20:54:46 -0700279 Thread streamReader = new Thread() {
280 public void run() {
281 InputStream in = null;
282 OutputStream out = null;
Guang Zhu843b9922015-06-07 16:16:15 -0700283 java.lang.Process process = null;
Svetoslav121e0c02014-05-08 18:51:25 -0700284
Guang Zhu14e26012015-03-18 20:54:46 -0700285 try {
Guang Zhu843b9922015-06-07 16:16:15 -0700286 process = Runtime.getRuntime().exec(command);
Svetoslav121e0c02014-05-08 18:51:25 -0700287
Guang Zhu14e26012015-03-18 20:54:46 -0700288 in = process.getInputStream();
289 out = new FileOutputStream(sink.getFileDescriptor());
Svetoslav121e0c02014-05-08 18:51:25 -0700290
Guang Zhu14e26012015-03-18 20:54:46 -0700291 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 {
Guang Zhu843b9922015-06-07 16:16:15 -0700302 if (process != null) {
303 process.destroy();
304 }
Guang Zhu14e26012015-03-18 20:54:46 -0700305 IoUtils.closeQuietly(out);
306 IoUtils.closeQuietly(sink);
Svetoslav121e0c02014-05-08 18:51:25 -0700307 }
Guang Zhu14e26012015-03-18 20:54:46 -0700308 };
309 };
310 streamReader.start();
Svetoslav121e0c02014-05-08 18:51:25 -0700311 }
312
313 @Override
Svetoslav Ganov80943d82013-01-02 10:25:37 -0800314 public void shutdown() {
315 synchronized (mLock) {
Svetoslav9663fd092013-10-31 16:28:20 -0700316 if (isConnectedLocked()) {
317 throwIfCalledByNotTrustedUidLocked();
318 }
Svetoslav Ganov80943d82013-01-02 10:25:37 -0800319 throwIfShutdownLocked();
320 mIsShutdown = true;
321 if (isConnectedLocked()) {
322 disconnect();
323 }
324 }
325 }
326
Phil Weaver1dd87222016-01-26 17:15:15 -0800327 private void registerUiTestAutomationServiceLocked(IAccessibilityServiceClient client,
328 int flags) {
Svetoslav Ganov80943d82013-01-02 10:25:37 -0800329 IAccessibilityManager manager = IAccessibilityManager.Stub.asInterface(
330 ServiceManager.getService(Context.ACCESSIBILITY_SERVICE));
Jeff Sharkeyb5e89c62016-04-01 23:20:31 -0600331 final AccessibilityServiceInfo info = new AccessibilityServiceInfo();
Svetoslav Ganov80943d82013-01-02 10:25:37 -0800332 info.eventTypes = AccessibilityEvent.TYPES_ALL_MASK;
333 info.feedbackType = AccessibilityServiceInfo.FEEDBACK_GENERIC;
334 info.flags |= AccessibilityServiceInfo.FLAG_INCLUDE_NOT_IMPORTANT_VIEWS
Jeff Sharkeyb5e89c62016-04-01 23:20:31 -0600335 | AccessibilityServiceInfo.FLAG_REPORT_VIEW_IDS
336 | AccessibilityServiceInfo.FLAG_FORCE_DIRECT_BOOT_AWARE;
Svetoslav11adf6d2013-04-24 14:51:29 -0700337 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);
Svetoslav Ganov80943d82013-01-02 10:25:37 -0800341 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.
Phil Weaver1dd87222016-01-26 17:15:15 -0800344 manager.registerUiTestAutomationService(mToken, client, info, flags);
Svetoslav Ganov80943d82013-01-02 10:25:37 -0800345 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}