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