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