private void unregisterUiTestAutomationServiceLocked() {
   IAccessibilityManager manager =
       IAccessibilityManager.Stub.asInterface(
           ServiceManager.getService(Context.ACCESSIBILITY_SERVICE));
   try {
     // Calling out with a lock held is fine since if the system
     // process is gone the client calling in will be killed.
     manager.unregisterUiTestAutomationService(mClient);
     mClient = null;
   } catch (RemoteException re) {
     throw new IllegalStateException("Error while unregistering UiTestAutomationService", re);
   }
 }
 private void registerUiTestAutomationServiceLocked(IAccessibilityServiceClient client) {
   IAccessibilityManager manager =
       IAccessibilityManager.Stub.asInterface(
           ServiceManager.getService(Context.ACCESSIBILITY_SERVICE));
   AccessibilityServiceInfo info = new AccessibilityServiceInfo();
   info.eventTypes = AccessibilityEvent.TYPES_ALL_MASK;
   info.feedbackType = AccessibilityServiceInfo.FEEDBACK_GENERIC;
   info.flags |=
       AccessibilityServiceInfo.FLAG_INCLUDE_NOT_IMPORTANT_VIEWS
           | AccessibilityServiceInfo.FLAG_REPORT_VIEW_IDS;
   info.setCapabilities(
       AccessibilityServiceInfo.CAPABILITY_CAN_RETRIEVE_WINDOW_CONTENT
           | AccessibilityServiceInfo.CAPABILITY_CAN_REQUEST_TOUCH_EXPLORATION
           | AccessibilityServiceInfo.CAPABILITY_CAN_REQUEST_ENHANCED_WEB_ACCESSIBILITY
           | AccessibilityServiceInfo.CAPABILITY_CAN_REQUEST_FILTER_KEY_EVENTS);
   try {
     // Calling out with a lock held is fine since if the system
     // process is gone the client calling in will be killed.
     manager.registerUiTestAutomationService(mToken, client, info);
     mClient = client;
   } catch (RemoteException re) {
     throw new IllegalStateException("Error while registering UiTestAutomationService.", re);
   }
 }
/**
 * This is a remote object that is passed from the shell to an instrumentation for enabling access
 * to privileged operations which the shell can do and the instrumentation cannot. These privileged
 * operations are needed for implementing a {@link UiAutomation} that enables across application
 * testing by simulating user actions and performing screen introspection.
 *
 * @hide
 */
public final class UiAutomationConnection extends IUiAutomationConnection.Stub {

  private static final int INITIAL_FROZEN_ROTATION_UNSPECIFIED = -1;

  private final IWindowManager mWindowManager =
      IWindowManager.Stub.asInterface(ServiceManager.getService(Service.WINDOW_SERVICE));

  private final IAccessibilityManager mAccessibilityManager =
      IAccessibilityManager.Stub.asInterface(
          ServiceManager.getService(Service.ACCESSIBILITY_SERVICE));

  private final IPackageManager mPackageManager =
      IPackageManager.Stub.asInterface(ServiceManager.getService("package"));

  private final Object mLock = new Object();

  private final Binder mToken = new Binder();

  private int mInitialFrozenRotation = INITIAL_FROZEN_ROTATION_UNSPECIFIED;

  private IAccessibilityServiceClient mClient;

  private boolean mIsShutdown;

  private int mOwningUid;

  public void connect(IAccessibilityServiceClient client) {
    if (client == null) {
      throw new IllegalArgumentException("Client cannot be null!");
    }
    synchronized (mLock) {
      throwIfShutdownLocked();
      if (isConnectedLocked()) {
        throw new IllegalStateException("Already connected.");
      }
      mOwningUid = Binder.getCallingUid();
      registerUiTestAutomationServiceLocked(client);
      storeRotationStateLocked();
    }
  }

  @Override
  public void disconnect() {
    synchronized (mLock) {
      throwIfCalledByNotTrustedUidLocked();
      throwIfShutdownLocked();
      if (!isConnectedLocked()) {
        throw new IllegalStateException("Already disconnected.");
      }
      mOwningUid = -1;
      unregisterUiTestAutomationServiceLocked();
      restoreRotationStateLocked();
    }
  }

  @Override
  public boolean injectInputEvent(InputEvent event, boolean sync) {
    synchronized (mLock) {
      throwIfCalledByNotTrustedUidLocked();
      throwIfShutdownLocked();
      throwIfNotConnectedLocked();
    }
    final int mode =
        (sync)
            ? InputManager.INJECT_INPUT_EVENT_MODE_WAIT_FOR_FINISH
            : InputManager.INJECT_INPUT_EVENT_MODE_ASYNC;
    final long identity = Binder.clearCallingIdentity();
    try {
      return InputManager.getInstance().injectInputEvent(event, mode);
    } finally {
      Binder.restoreCallingIdentity(identity);
    }
  }

  @Override
  public boolean setRotation(int rotation) {
    synchronized (mLock) {
      throwIfCalledByNotTrustedUidLocked();
      throwIfShutdownLocked();
      throwIfNotConnectedLocked();
    }
    final long identity = Binder.clearCallingIdentity();
    try {
      if (rotation == UiAutomation.ROTATION_UNFREEZE) {
        mWindowManager.thawRotation();
      } else {
        mWindowManager.freezeRotation(rotation);
      }
      return true;
    } catch (RemoteException re) {
      /* ignore */
    } finally {
      Binder.restoreCallingIdentity(identity);
    }
    return false;
  }

  @Override
  public Bitmap takeScreenshot(int width, int height) {
    synchronized (mLock) {
      throwIfCalledByNotTrustedUidLocked();
      throwIfShutdownLocked();
      throwIfNotConnectedLocked();
    }
    final long identity = Binder.clearCallingIdentity();
    try {
      return SurfaceControl.screenshot(width, height);
    } finally {
      Binder.restoreCallingIdentity(identity);
    }
  }

  @Override
  public boolean clearWindowContentFrameStats(int windowId) throws RemoteException {
    synchronized (mLock) {
      throwIfCalledByNotTrustedUidLocked();
      throwIfShutdownLocked();
      throwIfNotConnectedLocked();
    }
    final long identity = Binder.clearCallingIdentity();
    try {
      IBinder token = mAccessibilityManager.getWindowToken(windowId);
      if (token == null) {
        return false;
      }
      return mWindowManager.clearWindowContentFrameStats(token);
    } finally {
      Binder.restoreCallingIdentity(identity);
    }
  }

  @Override
  public WindowContentFrameStats getWindowContentFrameStats(int windowId) throws RemoteException {
    synchronized (mLock) {
      throwIfCalledByNotTrustedUidLocked();
      throwIfShutdownLocked();
      throwIfNotConnectedLocked();
    }
    final long identity = Binder.clearCallingIdentity();
    try {
      IBinder token = mAccessibilityManager.getWindowToken(windowId);
      if (token == null) {
        return null;
      }
      return mWindowManager.getWindowContentFrameStats(token);
    } finally {
      Binder.restoreCallingIdentity(identity);
    }
  }

  @Override
  public void clearWindowAnimationFrameStats() {
    synchronized (mLock) {
      throwIfCalledByNotTrustedUidLocked();
      throwIfShutdownLocked();
      throwIfNotConnectedLocked();
    }
    final long identity = Binder.clearCallingIdentity();
    try {
      SurfaceControl.clearAnimationFrameStats();
    } finally {
      Binder.restoreCallingIdentity(identity);
    }
  }

  @Override
  public WindowAnimationFrameStats getWindowAnimationFrameStats() {
    synchronized (mLock) {
      throwIfCalledByNotTrustedUidLocked();
      throwIfShutdownLocked();
      throwIfNotConnectedLocked();
    }
    final long identity = Binder.clearCallingIdentity();
    try {
      WindowAnimationFrameStats stats = new WindowAnimationFrameStats();
      SurfaceControl.getAnimationFrameStats(stats);
      return stats;
    } finally {
      Binder.restoreCallingIdentity(identity);
    }
  }

  @Override
  public void grantRuntimePermission(String packageName, String permission, int userId)
      throws RemoteException {
    synchronized (mLock) {
      throwIfCalledByNotTrustedUidLocked();
      throwIfShutdownLocked();
      throwIfNotConnectedLocked();
    }
    final long identity = Binder.clearCallingIdentity();
    try {
      mPackageManager.grantRuntimePermission(packageName, permission, userId);
    } finally {
      Binder.restoreCallingIdentity(identity);
    }
  }

  @Override
  public void revokeRuntimePermission(String packageName, String permission, int userId)
      throws RemoteException {
    synchronized (mLock) {
      throwIfCalledByNotTrustedUidLocked();
      throwIfShutdownLocked();
      throwIfNotConnectedLocked();
    }
    final long identity = Binder.clearCallingIdentity();
    try {
      mPackageManager.revokeRuntimePermission(packageName, permission, userId);
    } finally {
      Binder.restoreCallingIdentity(identity);
    }
  }

  @Override
  public void executeShellCommand(final String command, final ParcelFileDescriptor sink)
      throws RemoteException {
    synchronized (mLock) {
      throwIfCalledByNotTrustedUidLocked();
      throwIfShutdownLocked();
      throwIfNotConnectedLocked();
    }

    Thread streamReader =
        new Thread() {
          public void run() {
            InputStream in = null;
            OutputStream out = null;
            java.lang.Process process = null;

            try {
              process = Runtime.getRuntime().exec(command);

              in = process.getInputStream();
              out = new FileOutputStream(sink.getFileDescriptor());

              final byte[] buffer = new byte[8192];
              while (true) {
                final int readByteCount = in.read(buffer);
                if (readByteCount < 0) {
                  break;
                }
                out.write(buffer, 0, readByteCount);
              }
            } catch (IOException ioe) {
              throw new RuntimeException("Error running shell command", ioe);
            } finally {
              if (process != null) {
                process.destroy();
              }
              IoUtils.closeQuietly(out);
              IoUtils.closeQuietly(sink);
            }
          };
        };
    streamReader.start();
  }

  @Override
  public void shutdown() {
    synchronized (mLock) {
      if (isConnectedLocked()) {
        throwIfCalledByNotTrustedUidLocked();
      }
      throwIfShutdownLocked();
      mIsShutdown = true;
      if (isConnectedLocked()) {
        disconnect();
      }
    }
  }

  private void registerUiTestAutomationServiceLocked(IAccessibilityServiceClient client) {
    IAccessibilityManager manager =
        IAccessibilityManager.Stub.asInterface(
            ServiceManager.getService(Context.ACCESSIBILITY_SERVICE));
    AccessibilityServiceInfo info = new AccessibilityServiceInfo();
    info.eventTypes = AccessibilityEvent.TYPES_ALL_MASK;
    info.feedbackType = AccessibilityServiceInfo.FEEDBACK_GENERIC;
    info.flags |=
        AccessibilityServiceInfo.FLAG_INCLUDE_NOT_IMPORTANT_VIEWS
            | AccessibilityServiceInfo.FLAG_REPORT_VIEW_IDS;
    info.setCapabilities(
        AccessibilityServiceInfo.CAPABILITY_CAN_RETRIEVE_WINDOW_CONTENT
            | AccessibilityServiceInfo.CAPABILITY_CAN_REQUEST_TOUCH_EXPLORATION
            | AccessibilityServiceInfo.CAPABILITY_CAN_REQUEST_ENHANCED_WEB_ACCESSIBILITY
            | AccessibilityServiceInfo.CAPABILITY_CAN_REQUEST_FILTER_KEY_EVENTS);
    try {
      // Calling out with a lock held is fine since if the system
      // process is gone the client calling in will be killed.
      manager.registerUiTestAutomationService(mToken, client, info);
      mClient = client;
    } catch (RemoteException re) {
      throw new IllegalStateException("Error while registering UiTestAutomationService.", re);
    }
  }

  private void unregisterUiTestAutomationServiceLocked() {
    IAccessibilityManager manager =
        IAccessibilityManager.Stub.asInterface(
            ServiceManager.getService(Context.ACCESSIBILITY_SERVICE));
    try {
      // Calling out with a lock held is fine since if the system
      // process is gone the client calling in will be killed.
      manager.unregisterUiTestAutomationService(mClient);
      mClient = null;
    } catch (RemoteException re) {
      throw new IllegalStateException("Error while unregistering UiTestAutomationService", re);
    }
  }

  private void storeRotationStateLocked() {
    try {
      if (mWindowManager.isRotationFrozen()) {
        // Calling out with a lock held is fine since if the system
        // process is gone the client calling in will be killed.
        mInitialFrozenRotation = mWindowManager.getRotation();
      }
    } catch (RemoteException re) {
      /* ignore */
    }
  }

  private void restoreRotationStateLocked() {
    try {
      if (mInitialFrozenRotation != INITIAL_FROZEN_ROTATION_UNSPECIFIED) {
        // Calling out with a lock held is fine since if the system
        // process is gone the client calling in will be killed.
        mWindowManager.freezeRotation(mInitialFrozenRotation);
      } else {
        // Calling out with a lock held is fine since if the system
        // process is gone the client calling in will be killed.
        mWindowManager.thawRotation();
      }
    } catch (RemoteException re) {
      /* ignore */
    }
  }

  private boolean isConnectedLocked() {
    return mClient != null;
  }

  private void throwIfShutdownLocked() {
    if (mIsShutdown) {
      throw new IllegalStateException("Connection shutdown!");
    }
  }

  private void throwIfNotConnectedLocked() {
    if (!isConnectedLocked()) {
      throw new IllegalStateException("Not connected!");
    }
  }

  private void throwIfCalledByNotTrustedUidLocked() {
    final int callingUid = Binder.getCallingUid();
    if (callingUid != mOwningUid && mOwningUid != Process.SYSTEM_UID && callingUid != 0 /*root*/) {
      throw new SecurityException("Calling from not trusted UID!");
    }
  }
}