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