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