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.hardware.input.InputManager;
25 import android.os.Binder;
26 import android.os.IBinder;
27 import android.os.ParcelFileDescriptor;
28 import android.os.Process;
29 import android.os.RemoteException;
30 import android.os.ServiceManager;
31 import android.os.UserHandle;
32 import android.view.IWindowManager;
33 import android.view.InputEvent;
34 import android.view.SurfaceControl;
35 import android.view.WindowAnimationFrameStats;
36 import android.view.WindowContentFrameStats;
37 import android.view.accessibility.AccessibilityEvent;
38 import android.view.accessibility.IAccessibilityManager;
39 
40 import libcore.io.IoUtils;
41 
42 import java.io.FileOutputStream;
43 import java.io.IOException;
44 import java.io.InputStream;
45 import java.io.OutputStream;
46 
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  */
56 public 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 
63     private final IAccessibilityManager mAccessibilityManager = IAccessibilityManager.Stub
64             .asInterface(ServiceManager.getService(Service.ACCESSIBILITY_SERVICE));
65 
66     private final IPackageManager mPackageManager = IPackageManager.Stub
67             .asInterface(ServiceManager.getService("package"));
68 
69     private final Object mLock = new Object();
70 
71     private final Binder mToken = new Binder();
72 
73     private int mInitialFrozenRotation = INITIAL_FROZEN_ROTATION_UNSPECIFIED;
74 
75     private IAccessibilityServiceClient mClient;
76 
77     private boolean mIsShutdown;
78 
79     private int mOwningUid;
80 
81     @Override
connect(IAccessibilityServiceClient client, int flags)82     public void connect(IAccessibilityServiceClient client, int flags) {
83         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();
92             registerUiTestAutomationServiceLocked(client, flags);
93             storeRotationStateLocked();
94         }
95     }
96 
97     @Override
disconnect()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
injectInputEvent(InputEvent event, boolean sync)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
setRotation(int rotation)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
takeScreenshot(int width, int height)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 {
160             return SurfaceControl.screenshot(width, height);
161         } finally {
162             Binder.restoreCallingIdentity(identity);
163         }
164     }
165 
166     @Override
clearWindowContentFrameStats(int windowId)167     public boolean clearWindowContentFrameStats(int windowId) throws RemoteException {
168         synchronized (mLock) {
169             throwIfCalledByNotTrustedUidLocked();
170             throwIfShutdownLocked();
171             throwIfNotConnectedLocked();
172         }
173         int callingUserId = UserHandle.getCallingUserId();
174         final long identity = Binder.clearCallingIdentity();
175         try {
176             IBinder token = mAccessibilityManager.getWindowToken(windowId, callingUserId);
177             if (token == null) {
178                 return false;
179             }
180             return mWindowManager.clearWindowContentFrameStats(token);
181         } finally {
182             Binder.restoreCallingIdentity(identity);
183         }
184     }
185 
186     @Override
getWindowContentFrameStats(int windowId)187     public WindowContentFrameStats getWindowContentFrameStats(int windowId) throws RemoteException {
188         synchronized (mLock) {
189             throwIfCalledByNotTrustedUidLocked();
190             throwIfShutdownLocked();
191             throwIfNotConnectedLocked();
192         }
193         int callingUserId = UserHandle.getCallingUserId();
194         final long identity = Binder.clearCallingIdentity();
195         try {
196             IBinder token = mAccessibilityManager.getWindowToken(windowId, callingUserId);
197             if (token == null) {
198                 return null;
199             }
200             return mWindowManager.getWindowContentFrameStats(token);
201         } finally {
202             Binder.restoreCallingIdentity(identity);
203         }
204     }
205 
206     @Override
clearWindowAnimationFrameStats()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
getWindowAnimationFrameStats()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
grantRuntimePermission(String packageName, String permission, int userId)239     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
revokeRuntimePermission(String packageName, String permission, int userId)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
executeShellCommand(final String command, final ParcelFileDescriptor sink)271     public void executeShellCommand(final String command, final ParcelFileDescriptor sink)
272             throws RemoteException {
273         synchronized (mLock) {
274             throwIfCalledByNotTrustedUidLocked();
275             throwIfShutdownLocked();
276             throwIfNotConnectedLocked();
277         }
278 
279         Thread streamReader = new Thread() {
280             public void run() {
281                 InputStream in = null;
282                 OutputStream out = null;
283                 java.lang.Process process = null;
284 
285                 try {
286                     process = Runtime.getRuntime().exec(command);
287 
288                     in = process.getInputStream();
289                     out = new FileOutputStream(sink.getFileDescriptor());
290 
291                     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 {
302                     if (process != null) {
303                         process.destroy();
304                     }
305                     IoUtils.closeQuietly(out);
306                     IoUtils.closeQuietly(sink);
307                 }
308             };
309         };
310         streamReader.start();
311     }
312 
313     @Override
shutdown()314     public void shutdown() {
315         synchronized (mLock) {
316             if (isConnectedLocked()) {
317                 throwIfCalledByNotTrustedUidLocked();
318             }
319             throwIfShutdownLocked();
320             mIsShutdown = true;
321             if (isConnectedLocked()) {
322                 disconnect();
323             }
324         }
325     }
326 
registerUiTestAutomationServiceLocked(IAccessibilityServiceClient client, int flags)327     private void registerUiTestAutomationServiceLocked(IAccessibilityServiceClient client,
328             int flags) {
329         IAccessibilityManager manager = IAccessibilityManager.Stub.asInterface(
330                 ServiceManager.getService(Context.ACCESSIBILITY_SERVICE));
331         final AccessibilityServiceInfo info = new AccessibilityServiceInfo();
332         info.eventTypes = AccessibilityEvent.TYPES_ALL_MASK;
333         info.feedbackType = AccessibilityServiceInfo.FEEDBACK_GENERIC;
334         info.flags |= AccessibilityServiceInfo.FLAG_INCLUDE_NOT_IMPORTANT_VIEWS
335                 | AccessibilityServiceInfo.FLAG_REPORT_VIEW_IDS
336                 | AccessibilityServiceInfo.FLAG_FORCE_DIRECT_BOOT_AWARE;
337         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);
341         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.
344             manager.registerUiTestAutomationService(mToken, client, info, flags);
345             mClient = client;
346         } catch (RemoteException re) {
347             throw new IllegalStateException("Error while registering UiTestAutomationService.", re);
348         }
349     }
350 
unregisterUiTestAutomationServiceLocked()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 
storeRotationStateLocked()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.getDefaultDisplayRotation();
371             }
372         } catch (RemoteException re) {
373             /* ignore */
374         }
375     }
376 
restoreRotationStateLocked()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 
isConnectedLocked()393     private boolean isConnectedLocked() {
394         return mClient != null;
395     }
396 
throwIfShutdownLocked()397     private void throwIfShutdownLocked() {
398         if (mIsShutdown) {
399             throw new IllegalStateException("Connection shutdown!");
400         }
401     }
402 
throwIfNotConnectedLocked()403     private void throwIfNotConnectedLocked() {
404         if (!isConnectedLocked()) {
405             throw new IllegalStateException("Not connected!");
406         }
407     }
408 
throwIfCalledByNotTrustedUidLocked()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 }
417