blob: 276f774a8f4efd7e15d227280e58907ff17c886e [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;
Svetoslav121e0c02014-05-08 18:51:25 -070039import libcore.io.IoUtils;
40
41import java.io.FileOutputStream;
42import java.io.IOException;
43import java.io.InputStream;
44import java.io.OutputStream;
Svetoslav Ganov80943d82013-01-02 10:25:37 -080045
46/**
47 * This is a remote object that is passed from the shell to an instrumentation
48 * for enabling access to privileged operations which the shell can do and the
49 * instrumentation cannot. These privileged operations are needed for implementing
50 * a {@link UiAutomation} that enables across application testing by simulating
51 * user actions and performing screen introspection.
52 *
53 * @hide
54 */
55public final class UiAutomationConnection extends IUiAutomationConnection.Stub {
56
57 private static final int INITIAL_FROZEN_ROTATION_UNSPECIFIED = -1;
58
59 private final IWindowManager mWindowManager = IWindowManager.Stub.asInterface(
60 ServiceManager.getService(Service.WINDOW_SERVICE));
61
Svetoslav121e0c02014-05-08 18:51:25 -070062 private final IAccessibilityManager mAccessibilityManager = IAccessibilityManager.Stub
63 .asInterface(ServiceManager.getService(Service.ACCESSIBILITY_SERVICE));
Svetoslav1376d602014-03-13 11:17:26 -070064
Svet Ganov52153f42015-08-11 08:59:12 -070065 private final IPackageManager mPackageManager = IPackageManager.Stub
66 .asInterface(ServiceManager.getService("package"));
67
Svetoslav Ganov80943d82013-01-02 10:25:37 -080068 private final Object mLock = new Object();
69
Svetoslav3c55e5c2013-02-27 18:24:28 -080070 private final Binder mToken = new Binder();
71
Svetoslav Ganov80943d82013-01-02 10:25:37 -080072 private int mInitialFrozenRotation = INITIAL_FROZEN_ROTATION_UNSPECIFIED;
73
74 private IAccessibilityServiceClient mClient;
75
76 private boolean mIsShutdown;
77
78 private int mOwningUid;
79
Phil Weaver1dd87222016-01-26 17:15:15 -080080 public void connect(IAccessibilityServiceClient client, int flags) {
Svetoslav Ganov80943d82013-01-02 10:25:37 -080081 if (client == null) {
82 throw new IllegalArgumentException("Client cannot be null!");
83 }
84 synchronized (mLock) {
85 throwIfShutdownLocked();
86 if (isConnectedLocked()) {
87 throw new IllegalStateException("Already connected.");
88 }
89 mOwningUid = Binder.getCallingUid();
Phil Weaver1dd87222016-01-26 17:15:15 -080090 registerUiTestAutomationServiceLocked(client, flags);
Svetoslav Ganov80943d82013-01-02 10:25:37 -080091 storeRotationStateLocked();
92 }
93 }
94
95 @Override
96 public void disconnect() {
97 synchronized (mLock) {
98 throwIfCalledByNotTrustedUidLocked();
99 throwIfShutdownLocked();
100 if (!isConnectedLocked()) {
101 throw new IllegalStateException("Already disconnected.");
102 }
103 mOwningUid = -1;
104 unregisterUiTestAutomationServiceLocked();
105 restoreRotationStateLocked();
106 }
107 }
108
109 @Override
110 public boolean injectInputEvent(InputEvent event, boolean sync) {
111 synchronized (mLock) {
112 throwIfCalledByNotTrustedUidLocked();
113 throwIfShutdownLocked();
114 throwIfNotConnectedLocked();
115 }
116 final int mode = (sync) ? InputManager.INJECT_INPUT_EVENT_MODE_WAIT_FOR_FINISH
117 : InputManager.INJECT_INPUT_EVENT_MODE_ASYNC;
118 final long identity = Binder.clearCallingIdentity();
119 try {
120 return InputManager.getInstance().injectInputEvent(event, mode);
121 } finally {
122 Binder.restoreCallingIdentity(identity);
123 }
124 }
125
126 @Override
127 public boolean setRotation(int rotation) {
128 synchronized (mLock) {
129 throwIfCalledByNotTrustedUidLocked();
130 throwIfShutdownLocked();
131 throwIfNotConnectedLocked();
132 }
133 final long identity = Binder.clearCallingIdentity();
134 try {
135 if (rotation == UiAutomation.ROTATION_UNFREEZE) {
136 mWindowManager.thawRotation();
137 } else {
138 mWindowManager.freezeRotation(rotation);
139 }
140 return true;
141 } catch (RemoteException re) {
142 /* ignore */
143 } finally {
144 Binder.restoreCallingIdentity(identity);
145 }
146 return false;
147 }
148
149 @Override
150 public Bitmap takeScreenshot(int width, int height) {
151 synchronized (mLock) {
152 throwIfCalledByNotTrustedUidLocked();
153 throwIfShutdownLocked();
154 throwIfNotConnectedLocked();
155 }
156 final long identity = Binder.clearCallingIdentity();
157 try {
Mathias Agopian3866f0d2013-02-11 22:08:48 -0800158 return SurfaceControl.screenshot(width, height);
Svetoslav Ganov80943d82013-01-02 10:25:37 -0800159 } finally {
160 Binder.restoreCallingIdentity(identity);
161 }
162 }
163
164 @Override
Svetoslav1376d602014-03-13 11:17:26 -0700165 public boolean clearWindowContentFrameStats(int windowId) throws RemoteException {
166 synchronized (mLock) {
167 throwIfCalledByNotTrustedUidLocked();
168 throwIfShutdownLocked();
169 throwIfNotConnectedLocked();
170 }
Fyodor Kupolove2239c92016-01-12 16:46:13 -0800171 int callingUserId = UserHandle.getCallingUserId();
Svetoslav1376d602014-03-13 11:17:26 -0700172 final long identity = Binder.clearCallingIdentity();
173 try {
Fyodor Kupolove2239c92016-01-12 16:46:13 -0800174 IBinder token = mAccessibilityManager.getWindowToken(windowId, callingUserId);
Svetoslav1376d602014-03-13 11:17:26 -0700175 if (token == null) {
176 return false;
177 }
178 return mWindowManager.clearWindowContentFrameStats(token);
179 } finally {
180 Binder.restoreCallingIdentity(identity);
181 }
182 }
183
184 @Override
185 public WindowContentFrameStats getWindowContentFrameStats(int windowId) throws RemoteException {
186 synchronized (mLock) {
187 throwIfCalledByNotTrustedUidLocked();
188 throwIfShutdownLocked();
189 throwIfNotConnectedLocked();
190 }
Fyodor Kupolove2239c92016-01-12 16:46:13 -0800191 int callingUserId = UserHandle.getCallingUserId();
Svetoslav1376d602014-03-13 11:17:26 -0700192 final long identity = Binder.clearCallingIdentity();
193 try {
Fyodor Kupolove2239c92016-01-12 16:46:13 -0800194 IBinder token = mAccessibilityManager.getWindowToken(windowId, callingUserId);
Svetoslav1376d602014-03-13 11:17:26 -0700195 if (token == null) {
196 return null;
197 }
198 return mWindowManager.getWindowContentFrameStats(token);
199 } finally {
200 Binder.restoreCallingIdentity(identity);
201 }
202 }
203
204 @Override
205 public void clearWindowAnimationFrameStats() {
206 synchronized (mLock) {
207 throwIfCalledByNotTrustedUidLocked();
208 throwIfShutdownLocked();
209 throwIfNotConnectedLocked();
210 }
211 final long identity = Binder.clearCallingIdentity();
212 try {
213 SurfaceControl.clearAnimationFrameStats();
214 } finally {
215 Binder.restoreCallingIdentity(identity);
216 }
217 }
218
219 @Override
220 public WindowAnimationFrameStats getWindowAnimationFrameStats() {
221 synchronized (mLock) {
222 throwIfCalledByNotTrustedUidLocked();
223 throwIfShutdownLocked();
224 throwIfNotConnectedLocked();
225 }
226 final long identity = Binder.clearCallingIdentity();
227 try {
228 WindowAnimationFrameStats stats = new WindowAnimationFrameStats();
229 SurfaceControl.getAnimationFrameStats(stats);
230 return stats;
231 } finally {
232 Binder.restoreCallingIdentity(identity);
233 }
234 }
235
236 @Override
Svet Ganov52153f42015-08-11 08:59:12 -0700237 public void grantRuntimePermission(String packageName, String permission, int userId)
238 throws RemoteException {
239 synchronized (mLock) {
240 throwIfCalledByNotTrustedUidLocked();
241 throwIfShutdownLocked();
242 throwIfNotConnectedLocked();
243 }
244 final long identity = Binder.clearCallingIdentity();
245 try {
246 mPackageManager.grantRuntimePermission(packageName, permission, userId);
247 } finally {
248 Binder.restoreCallingIdentity(identity);
249 }
250 }
251
252 @Override
253 public void revokeRuntimePermission(String packageName, String permission, int userId)
254 throws RemoteException {
255 synchronized (mLock) {
256 throwIfCalledByNotTrustedUidLocked();
257 throwIfShutdownLocked();
258 throwIfNotConnectedLocked();
259 }
260 final long identity = Binder.clearCallingIdentity();
261 try {
262 mPackageManager.revokeRuntimePermission(packageName, permission, userId);
263 } finally {
264 Binder.restoreCallingIdentity(identity);
265 }
266 }
267
268 @Override
Guang Zhu14e26012015-03-18 20:54:46 -0700269 public void executeShellCommand(final String command, final ParcelFileDescriptor sink)
Svetoslav121e0c02014-05-08 18:51:25 -0700270 throws RemoteException {
271 synchronized (mLock) {
272 throwIfCalledByNotTrustedUidLocked();
273 throwIfShutdownLocked();
274 throwIfNotConnectedLocked();
275 }
276
Guang Zhu14e26012015-03-18 20:54:46 -0700277 Thread streamReader = new Thread() {
278 public void run() {
279 InputStream in = null;
280 OutputStream out = null;
Guang Zhu843b9922015-06-07 16:16:15 -0700281 java.lang.Process process = null;
Svetoslav121e0c02014-05-08 18:51:25 -0700282
Guang Zhu14e26012015-03-18 20:54:46 -0700283 try {
Guang Zhu843b9922015-06-07 16:16:15 -0700284 process = Runtime.getRuntime().exec(command);
Svetoslav121e0c02014-05-08 18:51:25 -0700285
Guang Zhu14e26012015-03-18 20:54:46 -0700286 in = process.getInputStream();
287 out = new FileOutputStream(sink.getFileDescriptor());
Svetoslav121e0c02014-05-08 18:51:25 -0700288
Guang Zhu14e26012015-03-18 20:54:46 -0700289 final byte[] buffer = new byte[8192];
290 while (true) {
291 final int readByteCount = in.read(buffer);
292 if (readByteCount < 0) {
293 break;
294 }
295 out.write(buffer, 0, readByteCount);
296 }
297 } catch (IOException ioe) {
298 throw new RuntimeException("Error running shell command", ioe);
299 } finally {
Guang Zhu843b9922015-06-07 16:16:15 -0700300 if (process != null) {
301 process.destroy();
302 }
Guang Zhu14e26012015-03-18 20:54:46 -0700303 IoUtils.closeQuietly(out);
304 IoUtils.closeQuietly(sink);
Svetoslav121e0c02014-05-08 18:51:25 -0700305 }
Guang Zhu14e26012015-03-18 20:54:46 -0700306 };
307 };
308 streamReader.start();
Svetoslav121e0c02014-05-08 18:51:25 -0700309 }
310
311 @Override
Svetoslav Ganov80943d82013-01-02 10:25:37 -0800312 public void shutdown() {
313 synchronized (mLock) {
Svetoslav9663fd092013-10-31 16:28:20 -0700314 if (isConnectedLocked()) {
315 throwIfCalledByNotTrustedUidLocked();
316 }
Svetoslav Ganov80943d82013-01-02 10:25:37 -0800317 throwIfShutdownLocked();
318 mIsShutdown = true;
319 if (isConnectedLocked()) {
320 disconnect();
321 }
322 }
323 }
324
Phil Weaver1dd87222016-01-26 17:15:15 -0800325 private void registerUiTestAutomationServiceLocked(IAccessibilityServiceClient client,
326 int flags) {
Svetoslav Ganov80943d82013-01-02 10:25:37 -0800327 IAccessibilityManager manager = IAccessibilityManager.Stub.asInterface(
328 ServiceManager.getService(Context.ACCESSIBILITY_SERVICE));
Svetoslav11adf6d2013-04-24 14:51:29 -0700329 AccessibilityServiceInfo info = new AccessibilityServiceInfo();
Svetoslav Ganov80943d82013-01-02 10:25:37 -0800330 info.eventTypes = AccessibilityEvent.TYPES_ALL_MASK;
331 info.feedbackType = AccessibilityServiceInfo.FEEDBACK_GENERIC;
332 info.flags |= AccessibilityServiceInfo.FLAG_INCLUDE_NOT_IMPORTANT_VIEWS
333 | AccessibilityServiceInfo.FLAG_REPORT_VIEW_IDS;
Svetoslav11adf6d2013-04-24 14:51:29 -0700334 info.setCapabilities(AccessibilityServiceInfo.CAPABILITY_CAN_RETRIEVE_WINDOW_CONTENT
335 | AccessibilityServiceInfo.CAPABILITY_CAN_REQUEST_TOUCH_EXPLORATION
336 | AccessibilityServiceInfo.CAPABILITY_CAN_REQUEST_ENHANCED_WEB_ACCESSIBILITY
337 | AccessibilityServiceInfo.CAPABILITY_CAN_REQUEST_FILTER_KEY_EVENTS);
Svetoslav Ganov80943d82013-01-02 10:25:37 -0800338 try {
339 // Calling out with a lock held is fine since if the system
340 // process is gone the client calling in will be killed.
Phil Weaver1dd87222016-01-26 17:15:15 -0800341 manager.registerUiTestAutomationService(mToken, client, info, flags);
Svetoslav Ganov80943d82013-01-02 10:25:37 -0800342 mClient = client;
343 } catch (RemoteException re) {
344 throw new IllegalStateException("Error while registering UiTestAutomationService.", re);
345 }
346 }
347
348 private void unregisterUiTestAutomationServiceLocked() {
349 IAccessibilityManager manager = IAccessibilityManager.Stub.asInterface(
350 ServiceManager.getService(Context.ACCESSIBILITY_SERVICE));
351 try {
352 // Calling out with a lock held is fine since if the system
353 // process is gone the client calling in will be killed.
354 manager.unregisterUiTestAutomationService(mClient);
355 mClient = null;
356 } catch (RemoteException re) {
357 throw new IllegalStateException("Error while unregistering UiTestAutomationService",
358 re);
359 }
360 }
361
362 private void storeRotationStateLocked() {
363 try {
364 if (mWindowManager.isRotationFrozen()) {
365 // Calling out with a lock held is fine since if the system
366 // process is gone the client calling in will be killed.
367 mInitialFrozenRotation = mWindowManager.getRotation();
368 }
369 } catch (RemoteException re) {
370 /* ignore */
371 }
372 }
373
374 private void restoreRotationStateLocked() {
375 try {
376 if (mInitialFrozenRotation != INITIAL_FROZEN_ROTATION_UNSPECIFIED) {
377 // Calling out with a lock held is fine since if the system
378 // process is gone the client calling in will be killed.
379 mWindowManager.freezeRotation(mInitialFrozenRotation);
380 } else {
381 // Calling out with a lock held is fine since if the system
382 // process is gone the client calling in will be killed.
383 mWindowManager.thawRotation();
384 }
385 } catch (RemoteException re) {
386 /* ignore */
387 }
388 }
389
390 private boolean isConnectedLocked() {
391 return mClient != null;
392 }
393
394 private void throwIfShutdownLocked() {
395 if (mIsShutdown) {
396 throw new IllegalStateException("Connection shutdown!");
397 }
398 }
399
400 private void throwIfNotConnectedLocked() {
401 if (!isConnectedLocked()) {
402 throw new IllegalStateException("Not connected!");
403 }
404 }
405
406 private void throwIfCalledByNotTrustedUidLocked() {
407 final int callingUid = Binder.getCallingUid();
408 if (callingUid != mOwningUid && mOwningUid != Process.SYSTEM_UID
409 && callingUid != 0 /*root*/) {
410 throw new SecurityException("Calling from not trusted UID!");
411 }
412 }
413}