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.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
     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
    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
    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
    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
    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
    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
    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
    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
    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
    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
    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
    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
    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
    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;
    333         public Repeater (InputStream readFrom, OutputStream writeTo) {
    334             this.readFrom = readFrom;
    335             this.writeTo = writeTo;
    336         }
    337         @Override
    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
    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
    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 
    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 
    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 
    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 
    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 
    500     private boolean isConnectedLocked() {
    501         return mClient != null;
    502     }
    503 
    504     private void throwIfShutdownLocked() {
    505         if (mIsShutdown) {
    506             throw new IllegalStateException("Connection shutdown!");
    507         }
    508     }
    509 
    510     private void throwIfNotConnectedLocked() {
    511         if (!isConnectedLocked()) {
    512             throw new IllegalStateException("Not connected!");
    513         }
    514     }
    515 
    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