/**
   * This method contains the code required to stop an emulator
   *
   * @param device The device to stop
   */
  private void stopEmulator(IDevice device) {
    int devicePort = extractPortFromDevice(device);
    if (devicePort == -1) {
      getLog()
          .info(
              "Unable to retrieve port to stop emulator "
                  + DeviceHelper.getDescriptiveName(device));
    } else {
      getLog().info("Stopping emulator " + DeviceHelper.getDescriptiveName(device));

      sendEmulatorCommand(devicePort, "avd stop");
      boolean killed = sendEmulatorCommand(devicePort, "kill");
      if (!killed) {
        getLog().info("Emulator failed to stop " + DeviceHelper.getDescriptiveName(device));
      } else {
        getLog().info("Emulator stopped successfully " + DeviceHelper.getDescriptiveName(device));
      }
    }
  }
  /**
   * Performs the callback action on the devices determined by {@link
   * #shouldDoWithThisDevice(com.android.ddmlib.IDevice)}
   *
   * @param deviceCallback the action to perform on each device
   * @throws org.apache.maven.plugin.MojoExecutionException in case there is a problem
   * @throws org.apache.maven.plugin.MojoFailureException in case there is a problem
   */
  protected void doWithDevices(final DeviceCallback deviceCallback)
      throws MojoExecutionException, MojoFailureException {
    final AndroidDebugBridge androidDebugBridge = initAndroidDebugBridge();

    if (!androidDebugBridge.isConnected()) {
      throw new MojoExecutionException("Android Debug Bridge is not connected.");
    }

    waitForInitialDeviceList(androidDebugBridge);
    List<IDevice> devices = Arrays.asList(androidDebugBridge.getDevices());
    int numberOfDevices = devices.size();
    getLog().info("Found " + numberOfDevices + " devices connected with the Android Debug Bridge");
    if (devices.size() == 0) {
      throw new MojoExecutionException("No online devices attached.");
    }

    boolean shouldRunOnAllDevices = StringUtils.isBlank(device);
    if (shouldRunOnAllDevices) {
      getLog().info("android.device parameter not set, using all attached devices");
    } else {
      getLog().info("android.device parameter set to " + device);
    }

    ArrayList<DoThread> doThreads = new ArrayList<DoThread>();
    for (final IDevice idevice : devices) {
      if (shouldRunOnAllDevices) {
        String deviceType = idevice.isEmulator() ? "Emulator " : "Device ";
        getLog().info(deviceType + DeviceHelper.getDescriptiveName(idevice) + " found.");
      }
      if (shouldRunOnAllDevices || shouldDoWithThisDevice(idevice)) {
        DoThread deviceDoThread =
            new DoThread() {
              public void runDo() throws MojoFailureException, MojoExecutionException {
                deviceCallback.doWithDevice(idevice);
              }
            };
        doThreads.add(deviceDoThread);
        deviceDoThread.start();
      }
    }

    joinAllThreads(doThreads);
    throwAnyDoThreadErrors(doThreads);

    if (!shouldRunOnAllDevices && doThreads.isEmpty()) {
      throw new MojoExecutionException("No device found for android.device=" + device);
    }
  }
  /**
   * Stop the running Android Emulators.
   *
   * @throws org.apache.maven.plugin.MojoExecutionException
   */
  protected void stopAndroidEmulators() throws MojoExecutionException {
    final AndroidDebugBridge androidDebugBridge = initAndroidDebugBridge();
    if (androidDebugBridge.isConnected()) {
      List<IDevice> devices = Arrays.asList(androidDebugBridge.getDevices());
      int numberOfDevices = devices.size();
      getLog()
          .info("Found " + numberOfDevices + " devices connected with the Android Debug Bridge");

      for (IDevice device : devices) {
        if (device.isEmulator()) {
          stopEmulator(device);
        } else {
          getLog()
              .info("Skipping stop. Not an emulator. " + DeviceHelper.getDescriptiveName(device));
        }
      }
    }
  }
  /**
   * Performs the callback action on the devices determined by {@link
   * #shouldDoWithThisDevice(com.android.ddmlib.IDevice)}
   *
   * @param deviceCallback the action to perform on each device
   * @throws org.apache.maven.plugin.MojoExecutionException in case there is a problem
   * @throws org.apache.maven.plugin.MojoFailureException in case there is a problem
   */
  protected void doWithDevices(final DeviceCallback deviceCallback)
      throws MojoExecutionException, MojoFailureException {
    final AndroidDebugBridge androidDebugBridge = initAndroidDebugBridge();

    if (!androidDebugBridge.isConnected()) {
      throw new MojoExecutionException("Android Debug Bridge is not connected.");
    }

    waitForInitialDeviceList(androidDebugBridge);
    List<IDevice> devices = Arrays.asList(androidDebugBridge.getDevices());
    int numberOfDevices = devices.size();
    getLog().debug("Found " + numberOfDevices + " devices connected with the Android Debug Bridge");
    if (devices.size() == 0) {
      throw new MojoExecutionException("No online devices attached.");
    }

    int threadCount = getDeviceThreads();
    if (getDeviceThreads() == 0) {
      getLog()
          .info(
              "android.devicesThreads parameter not set, using a thread for each attached device");
      threadCount = numberOfDevices;
    } else {
      getLog().info("android.devicesThreads parameter set to " + getDeviceThreads());
    }

    boolean shouldRunOnAllDevices = getDevices().size() == 0;
    if (shouldRunOnAllDevices) {
      getLog().info("android.devices parameter not set, using all attached devices");
    } else {
      getLog().info("android.devices parameter set to " + getDevices().toString());
    }

    ArrayList<DoThread> doThreads = new ArrayList<DoThread>();
    ExecutorService executor = Executors.newFixedThreadPool(threadCount);
    for (final IDevice idevice : devices) {
      if (shouldRunOnAllDevices) {
        String deviceType = idevice.isEmulator() ? "Emulator " : "Device ";
        getLog().info(deviceType + DeviceHelper.getDescriptiveName(idevice) + " found.");
      }
      if (shouldRunOnAllDevices || shouldDoWithThisDevice(idevice)) {
        DoThread deviceDoThread =
            new DoThread() {
              public void runDo() throws MojoFailureException, MojoExecutionException {
                deviceCallback.doWithDevice(idevice);
              }
            };
        doThreads.add(deviceDoThread);
        executor.execute(deviceDoThread);
      }
    }
    executor.shutdown();
    while (!executor.isTerminated()) {
      // waiting for threads finish
    }
    throwAnyDoThreadErrors(doThreads);

    if (!shouldRunOnAllDevices && doThreads.isEmpty()) {
      throw new MojoExecutionException(
          "No device found for android.device=" + getDevices().toString());
    }
  }