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