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