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