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.hardware.input.InputManager;
     25 import android.os.Binder;
     26 import android.os.IBinder;
     27 import android.os.ParcelFileDescriptor;
     28 import android.os.Process;
     29 import android.os.RemoteException;
     30 import android.os.ServiceManager;
     31 import android.os.UserHandle;
     32 import android.view.IWindowManager;
     33 import android.view.InputEvent;
     34 import android.view.SurfaceControl;
     35 import android.view.WindowAnimationFrameStats;
     36 import android.view.WindowContentFrameStats;
     37 import android.view.accessibility.AccessibilityEvent;
     38 import android.view.accessibility.IAccessibilityManager;
     39 
     40 import libcore.io.IoUtils;
     41 
     42 import java.io.FileOutputStream;
     43 import java.io.IOException;
     44 import java.io.InputStream;
     45 import java.io.OutputStream;
     46 
     47 /**
     48  * This is a remote object that is passed from the shell to an instrumentation
     49  * for enabling access to privileged operations which the shell can do and the
     50  * instrumentation cannot. These privileged operations are needed for implementing
     51  * a {@link UiAutomation} that enables across application testing by simulating
     52  * user actions and performing screen introspection.
     53  *
     54  * @hide
     55  */
     56 public final class UiAutomationConnection extends IUiAutomationConnection.Stub {
     57 
     58     private static final int INITIAL_FROZEN_ROTATION_UNSPECIFIED = -1;
     59 
     60     private final IWindowManager mWindowManager = IWindowManager.Stub.asInterface(
     61             ServiceManager.getService(Service.WINDOW_SERVICE));
     62 
     63     private final IAccessibilityManager mAccessibilityManager = IAccessibilityManager.Stub
     64             .asInterface(ServiceManager.getService(Service.ACCESSIBILITY_SERVICE));
     65 
     66     private final IPackageManager mPackageManager = IPackageManager.Stub
     67             .asInterface(ServiceManager.getService("package"));
     68 
     69     private final Object mLock = new Object();
     70 
     71     private final Binder mToken = new Binder();
     72 
     73     private int mInitialFrozenRotation = INITIAL_FROZEN_ROTATION_UNSPECIFIED;
     74 
     75     private IAccessibilityServiceClient mClient;
     76 
     77     private boolean mIsShutdown;
     78 
     79     private int mOwningUid;
     80 
     81     @Override
     82     public void connect(IAccessibilityServiceClient client, int flags) {
     83         if (client == null) {
     84             throw new IllegalArgumentException("Client cannot be null!");
     85         }
     86         synchronized (mLock) {
     87             throwIfShutdownLocked();
     88             if (isConnectedLocked()) {
     89                 throw new IllegalStateException("Already connected.");
     90             }
     91             mOwningUid = Binder.getCallingUid();
     92             registerUiTestAutomationServiceLocked(client, flags);
     93             storeRotationStateLocked();
     94         }
     95     }
     96 
     97     @Override
     98     public void disconnect() {
     99         synchronized (mLock) {
    100             throwIfCalledByNotTrustedUidLocked();
    101             throwIfShutdownLocked();
    102             if (!isConnectedLocked()) {
    103                 throw new IllegalStateException("Already disconnected.");
    104             }
    105             mOwningUid = -1;
    106             unregisterUiTestAutomationServiceLocked();
    107             restoreRotationStateLocked();
    108         }
    109     }
    110 
    111     @Override
    112     public boolean injectInputEvent(InputEvent event, boolean sync) {
    113         synchronized (mLock) {
    114             throwIfCalledByNotTrustedUidLocked();
    115             throwIfShutdownLocked();
    116             throwIfNotConnectedLocked();
    117         }
    118         final int mode = (sync) ? InputManager.INJECT_INPUT_EVENT_MODE_WAIT_FOR_FINISH
    119                 : InputManager.INJECT_INPUT_EVENT_MODE_ASYNC;
    120         final long identity = Binder.clearCallingIdentity();
    121         try {
    122             return InputManager.getInstance().injectInputEvent(event, mode);
    123         } finally {
    124             Binder.restoreCallingIdentity(identity);
    125         }
    126     }
    127 
    128     @Override
    129     public boolean setRotation(int rotation) {
    130         synchronized (mLock) {
    131             throwIfCalledByNotTrustedUidLocked();
    132             throwIfShutdownLocked();
    133             throwIfNotConnectedLocked();
    134         }
    135         final long identity = Binder.clearCallingIdentity();
    136         try {
    137             if (rotation == UiAutomation.ROTATION_UNFREEZE) {
    138                 mWindowManager.thawRotation();
    139             } else {
    140                 mWindowManager.freezeRotation(rotation);
    141             }
    142             return true;
    143         } catch (RemoteException re) {
    144             /* ignore */
    145         } finally {
    146             Binder.restoreCallingIdentity(identity);
    147         }
    148         return false;
    149     }
    150 
    151     @Override
    152     public Bitmap takeScreenshot(int width, int height) {
    153         synchronized (mLock) {
    154             throwIfCalledByNotTrustedUidLocked();
    155             throwIfShutdownLocked();
    156             throwIfNotConnectedLocked();
    157         }
    158         final long identity = Binder.clearCallingIdentity();
    159         try {
    160             return SurfaceControl.screenshot(width, height);
    161         } finally {
    162             Binder.restoreCallingIdentity(identity);
    163         }
    164     }
    165 
    166     @Override
    167     public boolean clearWindowContentFrameStats(int windowId) throws RemoteException {
    168         synchronized (mLock) {
    169             throwIfCalledByNotTrustedUidLocked();
    170             throwIfShutdownLocked();
    171             throwIfNotConnectedLocked();
    172         }
    173         int callingUserId = UserHandle.getCallingUserId();
    174         final long identity = Binder.clearCallingIdentity();
    175         try {
    176             IBinder token = mAccessibilityManager.getWindowToken(windowId, callingUserId);
    177             if (token == null) {
    178                 return false;
    179             }
    180             return mWindowManager.clearWindowContentFrameStats(token);
    181         } finally {
    182             Binder.restoreCallingIdentity(identity);
    183         }
    184     }
    185 
    186     @Override
    187     public WindowContentFrameStats getWindowContentFrameStats(int windowId) throws RemoteException {
    188         synchronized (mLock) {
    189             throwIfCalledByNotTrustedUidLocked();
    190             throwIfShutdownLocked();
    191             throwIfNotConnectedLocked();
    192         }
    193         int callingUserId = UserHandle.getCallingUserId();
    194         final long identity = Binder.clearCallingIdentity();
    195         try {
    196             IBinder token = mAccessibilityManager.getWindowToken(windowId, callingUserId);
    197             if (token == null) {
    198                 return null;
    199             }
    200             return mWindowManager.getWindowContentFrameStats(token);
    201         } finally {
    202             Binder.restoreCallingIdentity(identity);
    203         }
    204     }
    205 
    206     @Override
    207     public void clearWindowAnimationFrameStats() {
    208         synchronized (mLock) {
    209             throwIfCalledByNotTrustedUidLocked();
    210             throwIfShutdownLocked();
    211             throwIfNotConnectedLocked();
    212         }
    213         final long identity = Binder.clearCallingIdentity();
    214         try {
    215             SurfaceControl.clearAnimationFrameStats();
    216         } finally {
    217             Binder.restoreCallingIdentity(identity);
    218         }
    219     }
    220 
    221     @Override
    222     public WindowAnimationFrameStats getWindowAnimationFrameStats() {
    223         synchronized (mLock) {
    224             throwIfCalledByNotTrustedUidLocked();
    225             throwIfShutdownLocked();
    226             throwIfNotConnectedLocked();
    227         }
    228         final long identity = Binder.clearCallingIdentity();
    229         try {
    230             WindowAnimationFrameStats stats = new WindowAnimationFrameStats();
    231             SurfaceControl.getAnimationFrameStats(stats);
    232             return stats;
    233         } finally {
    234             Binder.restoreCallingIdentity(identity);
    235         }
    236     }
    237 
    238     @Override
    239     public void grantRuntimePermission(String packageName, String permission, int userId)
    240             throws RemoteException {
    241         synchronized (mLock) {
    242             throwIfCalledByNotTrustedUidLocked();
    243             throwIfShutdownLocked();
    244             throwIfNotConnectedLocked();
    245         }
    246         final long identity = Binder.clearCallingIdentity();
    247         try {
    248             mPackageManager.grantRuntimePermission(packageName, permission, userId);
    249         } finally {
    250             Binder.restoreCallingIdentity(identity);
    251         }
    252     }
    253 
    254     @Override
    255     public void revokeRuntimePermission(String packageName, String permission, int userId)
    256             throws RemoteException {
    257         synchronized (mLock) {
    258             throwIfCalledByNotTrustedUidLocked();
    259             throwIfShutdownLocked();
    260             throwIfNotConnectedLocked();
    261         }
    262         final long identity = Binder.clearCallingIdentity();
    263         try {
    264             mPackageManager.revokeRuntimePermission(packageName, permission, userId);
    265         } finally {
    266             Binder.restoreCallingIdentity(identity);
    267         }
    268     }
    269 
    270     @Override
    271     public void executeShellCommand(final String command, final ParcelFileDescriptor sink)
    272             throws RemoteException {
    273         synchronized (mLock) {
    274             throwIfCalledByNotTrustedUidLocked();
    275             throwIfShutdownLocked();
    276             throwIfNotConnectedLocked();
    277         }
    278 
    279         Thread streamReader = new Thread() {
    280             public void run() {
    281                 InputStream in = null;
    282                 OutputStream out = null;
    283                 java.lang.Process process = null;
    284 
    285                 try {
    286                     process = Runtime.getRuntime().exec(command);
    287 
    288                     in = process.getInputStream();
    289                     out = new FileOutputStream(sink.getFileDescriptor());
    290 
    291                     final byte[] buffer = new byte[8192];
    292                     while (true) {
    293                         final int readByteCount = in.read(buffer);
    294                         if (readByteCount < 0) {
    295                             break;
    296                         }
    297                         out.write(buffer, 0, readByteCount);
    298                     }
    299                 } catch (IOException ioe) {
    300                     throw new RuntimeException("Error running shell command", ioe);
    301                 } finally {
    302                     if (process != null) {
    303                         process.destroy();
    304                     }
    305                     IoUtils.closeQuietly(out);
    306                     IoUtils.closeQuietly(sink);
    307                 }
    308             };
    309         };
    310         streamReader.start();
    311     }
    312 
    313     @Override
    314     public void shutdown() {
    315         synchronized (mLock) {
    316             if (isConnectedLocked()) {
    317                 throwIfCalledByNotTrustedUidLocked();
    318             }
    319             throwIfShutdownLocked();
    320             mIsShutdown = true;
    321             if (isConnectedLocked()) {
    322                 disconnect();
    323             }
    324         }
    325     }
    326 
    327     private void registerUiTestAutomationServiceLocked(IAccessibilityServiceClient client,
    328             int flags) {
    329         IAccessibilityManager manager = IAccessibilityManager.Stub.asInterface(
    330                 ServiceManager.getService(Context.ACCESSIBILITY_SERVICE));
    331         final AccessibilityServiceInfo info = new AccessibilityServiceInfo();
    332         info.eventTypes = AccessibilityEvent.TYPES_ALL_MASK;
    333         info.feedbackType = AccessibilityServiceInfo.FEEDBACK_GENERIC;
    334         info.flags |= AccessibilityServiceInfo.FLAG_INCLUDE_NOT_IMPORTANT_VIEWS
    335                 | AccessibilityServiceInfo.FLAG_REPORT_VIEW_IDS
    336                 | AccessibilityServiceInfo.FLAG_FORCE_DIRECT_BOOT_AWARE;
    337         info.setCapabilities(AccessibilityServiceInfo.CAPABILITY_CAN_RETRIEVE_WINDOW_CONTENT
    338                 | AccessibilityServiceInfo.CAPABILITY_CAN_REQUEST_TOUCH_EXPLORATION
    339                 | AccessibilityServiceInfo.CAPABILITY_CAN_REQUEST_ENHANCED_WEB_ACCESSIBILITY
    340                 | AccessibilityServiceInfo.CAPABILITY_CAN_REQUEST_FILTER_KEY_EVENTS);
    341         try {
    342             // Calling out with a lock held is fine since if the system
    343             // process is gone the client calling in will be killed.
    344             manager.registerUiTestAutomationService(mToken, client, info, flags);
    345             mClient = client;
    346         } catch (RemoteException re) {
    347             throw new IllegalStateException("Error while registering UiTestAutomationService.", re);
    348         }
    349     }
    350 
    351     private void unregisterUiTestAutomationServiceLocked() {
    352         IAccessibilityManager manager = IAccessibilityManager.Stub.asInterface(
    353               ServiceManager.getService(Context.ACCESSIBILITY_SERVICE));
    354         try {
    355             // Calling out with a lock held is fine since if the system
    356             // process is gone the client calling in will be killed.
    357             manager.unregisterUiTestAutomationService(mClient);
    358             mClient = null;
    359         } catch (RemoteException re) {
    360             throw new IllegalStateException("Error while unregistering UiTestAutomationService",
    361                     re);
    362         }
    363     }
    364 
    365     private void storeRotationStateLocked() {
    366         try {
    367             if (mWindowManager.isRotationFrozen()) {
    368                 // Calling out with a lock held is fine since if the system
    369                 // process is gone the client calling in will be killed.
    370                 mInitialFrozenRotation = mWindowManager.getRotation();
    371             }
    372         } catch (RemoteException re) {
    373             /* ignore */
    374         }
    375     }
    376 
    377     private void restoreRotationStateLocked() {
    378         try {
    379             if (mInitialFrozenRotation != INITIAL_FROZEN_ROTATION_UNSPECIFIED) {
    380                 // Calling out with a lock held is fine since if the system
    381                 // process is gone the client calling in will be killed.
    382                 mWindowManager.freezeRotation(mInitialFrozenRotation);
    383             } else {
    384                 // Calling out with a lock held is fine since if the system
    385                 // process is gone the client calling in will be killed.
    386                 mWindowManager.thawRotation();
    387             }
    388         } catch (RemoteException re) {
    389             /* ignore */
    390         }
    391     }
    392 
    393     private boolean isConnectedLocked() {
    394         return mClient != null;
    395     }
    396 
    397     private void throwIfShutdownLocked() {
    398         if (mIsShutdown) {
    399             throw new IllegalStateException("Connection shutdown!");
    400         }
    401     }
    402 
    403     private void throwIfNotConnectedLocked() {
    404         if (!isConnectedLocked()) {
    405             throw new IllegalStateException("Not connected!");
    406         }
    407     }
    408 
    409     private void throwIfCalledByNotTrustedUidLocked() {
    410         final int callingUid = Binder.getCallingUid();
    411         if (callingUid != mOwningUid && mOwningUid != Process.SYSTEM_UID
    412                 && callingUid != 0 /*root*/) {
    413             throw new SecurityException("Calling from not trusted UID!");
    414         }
    415     }
    416 }
    417