/**
   * Creates the APK file using the command line.
   *
   * @param outputFile the output file
   * @param dexFile the dex file
   * @param zipArchive the classes folder
   * @param sourceFolders the resources
   * @param jarFiles the embedded java files
   * @param nativeFolders the native folders
   * @param signWithDebugKeyStore enables the signature of the APK using the debug key
   * @throws MojoExecutionException if the APK cannot be created.
   */
  private void doAPKWithCommand(
      File outputFile,
      File dexFile,
      File zipArchive,
      ArrayList<File> sourceFolders,
      ArrayList<File> jarFiles,
      ArrayList<File> nativeFolders,
      boolean signWithDebugKeyStore)
      throws MojoExecutionException {
    CommandExecutor executor = CommandExecutor.Factory.createDefaultCommmandExecutor();
    executor.setLogger(this.getLog());

    List<String> commands = new ArrayList<String>();
    commands.add(outputFile.getAbsolutePath());

    if (!signWithDebugKeyStore) {
      commands.add("-u");
    }

    commands.add("-z");
    commands.add(
        new File(project.getBuild().getDirectory(), project.getBuild().getFinalName() + ".ap_")
            .getAbsolutePath());
    commands.add("-f");
    commands.add(new File(project.getBuild().getDirectory(), "classes.dex").getAbsolutePath());
    commands.add("-rf");
    commands.add(new File(project.getBuild().getDirectory(), "classes").getAbsolutePath());

    if (nativeFolders != null && !nativeFolders.isEmpty()) {
      for (File lib : nativeFolders) {
        commands.add("-nf");
        commands.add(lib.getAbsolutePath());
      }
    }

    for (Artifact artifact : getRelevantCompileArtifacts()) {
      commands.add("-rj");
      commands.add(artifact.getFile().getAbsolutePath());
    }

    getLog().info(getAndroidSdk().getPathForTool("apkbuilder") + " " + commands.toString());
    try {
      executor.executeCommand(
          getAndroidSdk().getPathForTool("apkbuilder"), commands, project.getBasedir(), false);
    } catch (ExecutionException e) {
      throw new MojoExecutionException("", e);
    }
  }
  private void invokeNDKStripper(File file) throws MojoExecutionException {
    try {
      getLog().debug("Detected shared library artifact, will now strip it");
      // Execute the strip command
      final CommandExecutor stripCommandExecutor =
          CommandExecutor.Factory.createDefaultCommmandExecutor();
      stripCommandExecutor.setErrorListener(
          new CommandExecutor.ErrorListener() {
            public boolean isError(String error) {
              getLog().error("Error while stripping binary: " + error);
              return true;
            }
          });
      stripCommandExecutor.setLogger(getLog());

      stripCommandExecutor.executeCommand(
          resolveNdkStripper(file).getAbsolutePath(), Arrays.asList(file.getAbsolutePath()));
    } catch (ExecutionException e) {
      getLog().error("Error while attempting to strip shared library", e);
      throw new MojoExecutionException("Error while attempting to strip shared library");
    }
  }
  /**
   * Given a map of source directories to list of AIDL (relative) filenames within each, runs the
   * AIDL compiler for each, such that all source directories are available to the AIDL compiler.
   *
   * @param files Map of source directory File instances to the relative paths to all AIDL files
   *     within
   * @throws MojoExecutionException If the AIDL compiler fails
   */
  private void generateAidlFiles(
      Map<File /*sourceDirectory*/, String[] /*relativeAidlFileNames*/> files)
      throws MojoExecutionException {
    List<String> protoCommands = new ArrayList<String>();
    protoCommands.add("-p" + getAndroidSdk().getPathForFrameworkAidl());

    genDirectoryAidl.mkdirs();
    getLog().info("Adding AIDL gen folder to compile classpath: " + genDirectoryAidl);
    project.addCompileSourceRoot(genDirectoryAidl.getPath());
    Set<File> sourceDirs = files.keySet();
    for (File sourceDir : sourceDirs) {
      protoCommands.add("-I" + sourceDir);
    }
    for (File sourceDir : sourceDirs) {
      for (String relativeAidlFileName : files.get(sourceDir)) {
        File targetDirectory =
            new File(genDirectoryAidl, new File(relativeAidlFileName).getParent());
        targetDirectory.mkdirs();

        final String shortAidlFileName = new File(relativeAidlFileName).getName();
        final String shortJavaFileName =
            shortAidlFileName.substring(0, shortAidlFileName.lastIndexOf(".")) + ".java";
        final File aidlFileInSourceDirectory = new File(sourceDir, relativeAidlFileName);

        List<String> commands = new ArrayList<String>(protoCommands);
        commands.add(aidlFileInSourceDirectory.getAbsolutePath());
        commands.add(new File(targetDirectory, shortJavaFileName).getAbsolutePath());
        try {
          CommandExecutor executor = CommandExecutor.Factory.createDefaultCommmandExecutor();
          executor.setLogger(this.getLog());
          executor.setCaptureStdOut(true);
          executor.executeCommand(
              getAndroidSdk().getAidlPath(), commands, project.getBasedir(), false);
        } catch (ExecutionException e) {
          throw new MojoExecutionException("", e);
        }
      }
    }
  }
  /**
   * @throws MojoExecutionException
   * @throws MojoFailureException
   */
  public void execute() throws MojoExecutionException, MojoFailureException {
    try {
      // Validate the NDK
      final File ndkBuildFile = new File(getAndroidNdk().getNdkBuildPath());
      NativeHelper.validateNDKVersion(ndkBuildFile.getParentFile());

      // Validate the makefile - if our packaging type is so (for example) and there are
      // dependencies on .a files (or shared files for that matter) the makefile should include
      // the include of our Android Maven plugin generated makefile.
      validateMakefile(project, makefile);

      String[] ndkArchitectures =
          getAndroidNdk()
              .getNdkArchitectures(
                  ndkClassifier, ndkArchitecture, applicationMakefile, project.getBasedir());
      for (String ndkArchitecture : ndkArchitectures) {
        Preparation preparation = new Preparation().invoke(ndkArchitecture);
        boolean libsDirectoryExists = preparation.isLibsDirectoryExists();
        File directoryToRemove = preparation.getDirectoryToRemove();

        // Start setting up the command line to be executed
        final CommandExecutor executor = CommandExecutor.Factory.createDefaultCommmandExecutor();
        // Add an error listener to the build - this allows the build to conditionally fail
        // depending on a) the output of the build b) whether or not build errors (output on stderr)
        // should be
        // ignored and c) whether the pattern matches or not
        executor.setErrorListener(getNdkErrorListener());

        final Set<Artifact> nativeLibraryArtifacts = findNativeLibraryDependencies();

        // If there are any static libraries the code needs to link to, include those in the make
        // file
        final Set<Artifact> resolveNativeLibraryArtifacts =
            AetherHelper.resolveArtifacts(
                nativeLibraryArtifacts, repoSystem, repoSession, projectRepos);
        if (getLog().isDebugEnabled()) {
          getLog()
              .debug(
                  "resolveArtifacts found "
                      + resolveNativeLibraryArtifacts.size()
                      + ": "
                      + resolveNativeLibraryArtifacts.toString());
        }

        final File makefileDir = new File(project.getBuild().getDirectory(), NDK_MAKFILE_DIRECTORY);
        makefileDir.mkdirs();
        final File androidMavenMakefile = new File(makefileDir, "android_maven_plugin_makefile.mk");

        // set the ndk build directory
        if (ndkBuildDirectory == null) {
          ndkBuildDirectory = project.getBasedir().getAbsolutePath();
        }

        final MakefileHelper makefileHelper =
            new MakefileHelper(
                getLog(), repoSystem, repoSession, projectRepos, unpackedApkLibsDirectory);
        final MakefileHelper.MakefileHolder makefileHolder =
            makefileHelper.createMakefileFromArtifacts(
                new File(ndkBuildDirectory),
                resolveNativeLibraryArtifacts,
                ndkArchitecture,
                useHeaderArchives);
        IOUtil.copy(makefileHolder.getMakeFile(), new FileOutputStream(androidMavenMakefile));

        // Add the path to the generated makefile - this is picked up by the build (by an include
        // from the user)
        executor.addEnvironment(
            "ANDROID_MAVEN_PLUGIN_MAKEFILE", androidMavenMakefile.getAbsolutePath());

        setupNativeLibraryEnvironment(
            makefileHelper, executor, resolveNativeLibraryArtifacts, ndkArchitecture);

        // Adds the location of the Makefile capturer file - this file will after the build include
        // things like header files, flags etc.  It is processed after the build to retrieve the
        // headers
        // and also capture flags etc ...
        final File makefileCaptureFile =
            File.createTempFile("android_maven_plugin_makefile_captures", ".tmp");
        makefileCaptureFile.deleteOnExit();
        executor.addEnvironment(
            MakefileHelper.MAKEFILE_CAPTURE_FILE, makefileCaptureFile.getAbsolutePath());

        // Add any defined system properties
        if (systemProperties != null && !systemProperties.isEmpty()) {
          for (Map.Entry<String, String> entry : systemProperties.entrySet()) {
            executor.addEnvironment(entry.getKey(), entry.getValue());
          }
        }
        executor.setLogger(this.getLog());
        // Setup the command line for the make
        final List<String> commands = new ArrayList<String>();
        // Setup the build directory (defaults to the current directory) but may be different
        // depending
        // on user configuration
        commands.add("-C");
        commands.add(ndkBuildDirectory);

        // If the build should use a custom makefile or not - some validation is done to ensure
        // this exists and all
        if (makefile != null) {
          File makeFile = new File(project.getBasedir(), makefile);
          if (!makeFile.exists()) {
            getLog().error("Specified makefile " + makeFile + " does not exist");
            throw new MojoExecutionException("Specified makefile " + makeFile + " does not exist");
          }
          commands.add("-f");
          commands.add(makefile);
        }

        configureApplicationMakefile(commands);
        configureMaxJobs(commands);
        configureNdkToolchain(commands);

        // Anything else on the command line the user wants to add - simply splice it up and
        // add it one by one to the command line
        if (ndkBuildAdditionalCommandline != null) {
          String[] additionalCommands = ndkBuildAdditionalCommandline.split(" ");
          for (final String command : additionalCommands) {
            commands.add(command);
          }
        }
        // If a build target is specified, tag that onto the command line as the
        // very last of the parameters
        if (target != null) {
          commands.add(target);
        } else /*if ( "a".equals( project.getPackaging() ) )*/ {
          commands.add(project.getArtifactId());
        }

        final String ndkBuildPath = resolveNdkBuildExecutable();
        getLog().info(ndkBuildPath + " " + commands.toString());

        executor.executeCommand(ndkBuildPath, commands, project.getBasedir(), true);

        cleanUp(
            preparation.getNativeLibDirectory(),
            ndkArchitecture,
            libsDirectoryExists,
            directoryToRemove,
            makefileHolder,
            makefileCaptureFile);
      }
    } catch (MojoExecutionException e) {
      getLog().error("Error during build: " + e.getMessage(), e);
      throw e;
    } catch (Exception e) {
      getLog().error("Error while executing: " + e.getMessage());
      throw new MojoExecutionException(e.getMessage(), e);
    }
  }
  /**
   * Executes aapt to generate the R class for the given apklib.
   *
   * @param apklibArtifact apklib for which to generate the R class.
   * @throws MojoExecutionException if it fails.
   */
  private void generateRForApkLibDependency(Artifact apklibArtifact) throws MojoExecutionException {
    final File unpackDir = getUnpackedLibFolder(apklibArtifact);
    getLog()
        .debug(
            "Generating incomplete R file for apklib: "
                + apklibArtifact.getGroupId()
                + ":"
                + apklibArtifact.getArtifactId());
    final File apklibManifest = new File(unpackDir, "AndroidManifest.xml");
    final File apklibResDir = new File(unpackDir, "res");

    List<File> dependenciesResDirectories = new ArrayList<File>();
    final Set<Artifact> apklibDeps =
        getDependencyResolver().getLibraryDependenciesFor(project, apklibArtifact);
    getLog().debug("apklib=" + apklibArtifact + "  dependencies=" + apklibDeps);
    for (Artifact dependency : apklibDeps) {
      // Add in the resources that are dependencies of the apklib.
      final String extension = dependency.getType();
      final File dependencyResDir = getUnpackedLibResourceFolder(dependency);
      if ((extension.equals(APKLIB) || extension.equals(AAR)) && dependencyResDir.exists()) {
        dependenciesResDirectories.add(dependencyResDir);
      }
    }

    // Create combinedAssets for this apklib dependency - can't have multiple -A args
    final File apklibCombAssets = new File(getUnpackedLibFolder(apklibArtifact), "combined-assets");
    for (Artifact dependency : apklibDeps) {
      // Accumulate assets for dependencies of the apklib (if they exist).
      final String extension = dependency.getType();
      final File dependencyAssetsDir = getUnpackedLibAssetsFolder(dependency);
      if ((extension.equals(APKLIB) || extension.equals(AAR))) {
        copyFolder(dependencyAssetsDir, apklibCombAssets);
      }
    }
    // Overlay the apklib dependency assets (if they exist)
    final File apkLibAssetsDir = getUnpackedLibAssetsFolder(apklibArtifact);
    copyFolder(apkLibAssetsDir, apklibCombAssets);

    final CommandExecutor executor = CommandExecutor.Factory.createDefaultCommmandExecutor();
    executor.setLogger(getLog());

    AaptCommandBuilder commandBuilder =
        new AaptCommandBuilder()
            .packageResources()
            .makeResourcesNonConstant()
            .makePackageDirectories()
            .setWhereToOutputResourceConstants(genDirectory.getAbsolutePath())
            .generateRIntoPackage(extractPackageNameFromAndroidManifest(apklibManifest))
            .setPathToAndroidManifest(apklibManifest.getAbsolutePath())
            .addResourceDirectoryIfExists(apklibResDir)
            .addResourceDirectoriesIfExists(dependenciesResDirectories)
            .autoAddOverlay()
            .addRawAssetsDirectoryIfExists(apklibCombAssets)
            .addExistingPackageToBaseIncludeSet(getAndroidSdk().getAndroidJar().getAbsolutePath())
            .addConfigurations(configurations)
            .addExtraArguments(aaptExtraArgs)
            .setVerbose(aaptVerbose)
            // We need to generate R.txt for all projects as it needs to be consumed when generating
            // R class.
            // It also needs to be consumed when packaging aar.
            .generateRTextFile(unpackDir.getAbsolutePath());

    getLog().debug(getAndroidSdk().getAaptPath() + " " + commandBuilder.toString());
    try {
      executor.setCaptureStdOut(true);
      List<String> commands = commandBuilder.build();
      executor.executeCommand(getAndroidSdk().getAaptPath(), commands, project.getBasedir(), false);
    } catch (ExecutionException e) {
      throw new MojoExecutionException("", e);
    }
  }
  private void generateR() throws MojoExecutionException {
    getLog().info("Generating R file for " + project.getArtifact());

    genDirectory.mkdirs();

    final File[] overlayDirectories = getResourceOverlayDirectories();
    getLog().debug("Resource overlay folders : " + Arrays.asList(overlayDirectories));

    final CommandExecutor executor = CommandExecutor.Factory.createDefaultCommmandExecutor();
    executor.setLogger(this.getLog());

    final List<String> commands = new ArrayList<String>();
    commands.add("package");

    commands.add("-f");
    commands.add("--no-crunch");

    // inputs
    commands.add("-I");
    commands.add(getAndroidSdk().getAndroidJar().getAbsolutePath());

    commands.add("-M");
    commands.add(androidManifestFile.getAbsolutePath());

    // NB AndroidBuilder only adds a single folder - presumably it contains a merge of all
    // resources.
    for (File resOverlayDir : overlayDirectories) {
      if (resOverlayDir != null && resOverlayDir.exists()) {
        getLog().debug("Adding resource overlay folder : " + resOverlayDir);
        commands.add("-S");
        commands.add(resOverlayDir.getAbsolutePath());
      }
    }
    if (resourceDirectory.exists()) {
      getLog().debug("Adding resource folder : " + resourceDirectory);
      commands.add("-S");
      commands.add(resourceDirectory.getAbsolutePath());
    }

    // Need to include any AAR or APKLIB dependencies when generating R because if any local
    // resources directly reference dependent resources then R generation will crash.
    addLibraryResourceFolders(commands);

    // NB aapt only accepts a single assets parameter - combinedAssets is a merge of all assets
    if (combinedAssets.exists()) {
      getLog().debug("Adding assets folder : " + combinedAssets);
      commands.add("-A");
      commands.add(combinedAssets.getAbsolutePath());
    }

    // outputs
    commands.add("-m");
    commands.add("-J");
    commands.add(genDirectory.getAbsolutePath());

    // Write the output to an optional location
    // Used by AndroidBuilder but not by us.
    // final File optionalOutputLocation = some file;
    // getLog().debug( "Using default package : " + optionalOutputLocation );
    // commands.add( "-F" );
    // commands.add( optionalOutputLocation.getAbsolutePath() );

    // If a proguard file is defined then output Proguard options to it.
    if (proguardFile != null) {
      final File parentFolder = proguardFile.getParentFile();
      if (parentFolder != null) {
        parentFolder.mkdirs();
      }
      getLog().debug("Adding proguard file : " + proguardFile);
      commands.add("-G");
      commands.add(proguardFile.getAbsolutePath());
    }

    if (StringUtils.isNotBlank(customPackage)) {
      getLog().debug("Adding custom-package : " + customPackage);
      commands.add("--custom-package");
      commands.add(customPackage);
    }

    if (AAR.equals(project.getArtifact().getType())) {
      getLog().debug("Adding non-constant-id");
      commands.add("--non-constant-id");
    }

    for (String aaptExtraArg : aaptExtraArgs) {
      getLog().debug("Adding aapt arg : " + aaptExtraArg);
      commands.add(aaptExtraArg);
    }

    if (StringUtils.isNotBlank(configurations)) {
      // Should be comma separated list of locales etc.
      getLog().debug("Adding resource configurations : " + configurations);
      commands.add("-c");
      commands.add(configurations);
    }

    if (aaptVerbose) {
      commands.add("-v");
    }

    // We need to generate R.txt for all projects as it needs to be consumed when generating R
    // class.
    // It also needs to be consumed when packaging aar.
    commands.add("--output-text-symbols");
    commands.add(targetDirectory.getAbsolutePath());

    // Allows us to supply multiple -S arguments.
    commands.add("--auto-add-overlay");

    getLog().debug(getAndroidSdk().getAaptPath() + " " + commands.toString());
    try {
      targetDirectory.mkdirs();
      executor.setCaptureStdOut(true);
      executor.executeCommand(getAndroidSdk().getAaptPath(), commands, project.getBasedir(), false);
    } catch (ExecutionException e) {
      throw new MojoExecutionException("", e);
    }

    ResourceClassGenerator resourceGenerator =
        new ResourceClassGenerator(this, targetDirectory, genDirectory);

    generateCorrectRJavaForApklibDependencies(resourceGenerator);
    generateCorrectRJavaForAarDependencies(resourceGenerator);

    getLog().info("Adding R gen folder to compile classpath: " + genDirectory);
    project.addCompileSourceRoot(genDirectory.getAbsolutePath());
  }
  private void executeProguard() throws MojoExecutionException {

    // we should make this configurable, users may want to use a newer (or diff) version of proguard
    String proguardJar = getAndroidSdk().getPathForTool("proguard/lib/proguard.jar");

    File proguardDir = new File(project.getBuild().getDirectory(), "proguard");
    if (!proguardDir.exists() && !proguardDir.mkdir()) {
      throw new MojoExecutionException("Cannot create proguard output directory");
    } else if (proguardDir.exists() && !proguardDir.isDirectory()) {
      throw new MojoExecutionException("Non-directory exists at " + proguardDir.getAbsolutePath());
    }

    CommandExecutor executor = CommandExecutor.Factory.createDefaultCommmandExecutor();
    executor.setLogger(this.getLog());
    List<String> commands = new ArrayList<String>();

    if (jvmArguments != null) {
      for (String jvmArgument : jvmArguments) {
        // preserve backward compatibility allowing argument with or without dash (e.g. Xmx512m as
        // well as
        // -Xmx512m should work) (see
        // http://code.google.com/p/maven-android-plugin/issues/detail?id=153)
        if (!jvmArgument.startsWith("-")) {
          jvmArgument = "-" + jvmArgument;
        }
        commands.add(jvmArgument);
      }
    }
    commands.add("-jar");
    commands.add(proguardJar);

    commands.add("@" + proguard.getConfig());

    for (File file : getInputFiles()) {
      // don't add android packaging files, these are not input to proguard
      if (!AndroidExtension.isAndroidPackaging(FileUtils.extension(file.getAbsolutePath()))) {
        commands.add("-injars");
        commands.add(file.getAbsolutePath());
      }
    }

    commands.add("-outjars");
    commands.add(project.getBuild().getDirectory() + File.separator + PROGUARD_OBFUSCATED_JAR);

    commands.add("-libraryjars");
    commands.add(getAndroidSdk().getAndroidJar().getAbsolutePath());

    commands.add("-dump");
    commands.add(proguardDir + File.separator + "dump.txt");
    commands.add("-printseeds");
    commands.add(proguardDir + File.separator + "seeds.txt");
    commands.add("-printusage");
    commands.add(proguardDir + File.separator + "usage.txt");
    commands.add("-printmapping");
    commands.add(proguardDir + File.separator + "mapping.txt");

    final String javaExecutable = getJavaExecutable().getAbsolutePath();
    getLog().info(javaExecutable + " " + commands.toString());
    try {
      executor.executeCommand(javaExecutable, commands, project.getBasedir(), false);
    } catch (ExecutionException e) {
      throw new MojoExecutionException("", e);
    }
  }
  /**
   * Generates an intermediate apk file (actually .ap_) containing the resources and assets.
   *
   * @throws MojoExecutionException
   */
  private void generateIntermediateApk() throws MojoExecutionException {
    CommandExecutor executor = CommandExecutor.Factory.createDefaultCommmandExecutor();
    executor.setLogger(this.getLog());
    File[] overlayDirectories = getResourceOverlayDirectories();

    File androidJar = getAndroidSdk().getAndroidJar();
    File outputFile =
        new File(project.getBuild().getDirectory(), project.getBuild().getFinalName() + ".ap_");

    List<String> commands = new ArrayList<String>();
    commands.add("package");
    commands.add("-f");
    commands.add("-M");
    commands.add(androidManifestFile.getAbsolutePath());
    for (File resOverlayDir : overlayDirectories) {
      if (resOverlayDir != null && resOverlayDir.exists()) {
        commands.add("-S");
        commands.add(resOverlayDir.getAbsolutePath());
      }
    }
    if (resourceDirectory.exists()) {
      commands.add("-S");
      commands.add(resourceDirectory.getAbsolutePath());
    }
    for (Artifact libraryArtifact : getTransitiveDependencyArtifacts(APKLIB, AAR)) {
      final File libraryResDir = getUnpackedLibResourceFolder(libraryArtifact);
      if (libraryResDir.exists()) {
        commands.add("-S");
        commands.add(libraryResDir.getAbsolutePath());
      }
    }
    commands.add("--auto-add-overlay");

    // NB aapt only accepts a single assets parameter - combinedAssets is a merge of all assets
    if (combinedAssets.exists()) {
      getLog().debug("Adding assets folder : " + combinedAssets);
      commands.add("-A");
      commands.add(combinedAssets.getAbsolutePath());
    }

    if (StringUtils.isNotBlank(renameManifestPackage)) {
      commands.add("--rename-manifest-package");
      commands.add(renameManifestPackage);
    }

    if (StringUtils.isNotBlank(renameInstrumentationTargetPackage)) {
      commands.add("--rename-instrumentation-target-package");
      commands.add(renameInstrumentationTargetPackage);
    }

    commands.add("-I");
    commands.add(androidJar.getAbsolutePath());
    commands.add("-F");
    commands.add(outputFile.getAbsolutePath());
    if (StringUtils.isNotBlank(configurations)) {
      commands.add("-c");
      commands.add(configurations);
    }

    for (String aaptExtraArg : aaptExtraArgs) {
      commands.add(aaptExtraArg);
    }

    if (aaptVerbose) {
      commands.add("-v");
    }

    if (!release) {
      getLog().info("Generating debug apk.");
      commands.add("--debug-mode");
    } else {
      getLog().info("Generating release apk.");
    }

    getLog().debug(getAndroidSdk().getAaptPath() + " " + commands.toString());
    try {
      executor.setCaptureStdOut(true);
      executor.executeCommand(getAndroidSdk().getAaptPath(), commands, project.getBasedir(), false);
    } catch (ExecutionException e) {
      throw new MojoExecutionException("", e);
    }
  }
  protected void instrument() throws MojoExecutionException, MojoFailureException {
    if (instrumentationPackage == null) {
      instrumentationPackage = extractPackageNameFromAndroidManifest(androidManifestFile);
    }

    if (instrumentationRunner == null) {
      instrumentationRunner = extractInstrumentationRunnerFromAndroidManifest(androidManifestFile);
    }

    final CommandExecutor executor = CommandExecutor.Factory.createDefaultCommmandExecutor();
    executor.setLogger(this.getLog());

    doWithDevices(
        new DeviceCallback() {
          public void doWithDevice(final IDevice device)
              throws MojoExecutionException, MojoFailureException {
            List<String> commands = new ArrayList<String>();

            addDeviceParameter(commands, device);

            commands.add("shell");
            commands.add("am");
            commands.add("instrument");
            commands.add("-w");

            // only run Tests in specific package
            String testPackages = buildTestPackagesString();
            // only run Tests in specific class
            String testClasses = buildTestClassesString();
            boolean tcExists = StringUtils.isNotBlank(testClasses);
            boolean tpExists = StringUtils.isNotBlank(testPackages);

            if (tcExists && tpExists) {
              // if both testPackages and testClasses are specified --> ERROR
              throw new MojoFailureException(
                  "testPackages and testClasses are mutual exclusive. They cannot be specified at the same time. "
                      + "Please specify either testPackages or testClasses! For details, see http://developer.android.com/guide/developing/testing/testing_otheride.html");
            }

            if (tpExists) {
              commands.add("-e");
              commands.add("package");
              commands.add(testPackages);

              getLog().info("Running tests for specified test packages: " + testPackages);
            }

            if (tcExists) {
              commands.add("-e");
              commands.add("class");
              commands.add(testClasses);

              getLog().info("Running tests for specified test classes/methods: " + testClasses);
            }
            // ---- stop mkessel extensions

            commands.add(instrumentationPackage + "/" + instrumentationRunner);

            getLog().info(getAndroidSdk().getAdbPath() + " " + commands.toString());
            try {
              executor.executeCommand(
                  getAndroidSdk().getAdbPath(), commands, project.getBasedir(), true);
              final String standardOut = executor.getStandardOut();
              final String standardError = executor.getStandardError();
              getLog().debug(standardOut);
              getLog().debug(standardError);
              // Fail when tests on device fail. adb does not exit with errorcode!=0 or even print
              // to stderr, so we have to parse stdout.
              if (standardOut == null || !standardOut.matches(".*?OK \\([0-9]+ tests?\\)\\s*")) {
                throw new MojoFailureException("Tests failed on device.");
              }
            } catch (ExecutionException e) {
              getLog().error(executor.getStandardOut());
              getLog().error(executor.getStandardError());
              throw new MojoFailureException("Tests failed on device.");
            }
          }
        });
  }
  /**
   * Generates an intermediate apk file (actually .ap_) containing the resources and assets.
   *
   * @throws MojoExecutionException
   */
  private void generateIntermediateAp_() throws MojoExecutionException {

    CommandExecutor executor = CommandExecutor.Factory.createDefaultCommmandExecutor();
    executor.setLogger(this.getLog());
    File[] overlayDirectories;

    if (resourceOverlayDirectories == null || resourceOverlayDirectories.length == 0) {
      overlayDirectories = new File[] {resourceOverlayDirectory};
    } else {
      overlayDirectories = resourceOverlayDirectories;
    }

    if (!combinedRes.exists()) {
      if (!combinedRes.mkdirs()) {
        throw new MojoExecutionException(
            "Could not create directory for combined resources at "
                + combinedRes.getAbsolutePath());
      }
    }
    if (extractedDependenciesRes.exists()) {
      try {
        getLog().info("Copying dependency resource files to combined resource directory.");
        org.apache.commons.io.FileUtils.copyDirectory(extractedDependenciesRes, combinedRes);
      } catch (IOException e) {
        throw new MojoExecutionException("", e);
      }
    }
    if (resourceDirectory.exists()) {
      try {
        getLog().info("Copying local resource files to combined resource directory.");
        org.apache.commons.io.FileUtils.copyDirectory(
            resourceDirectory,
            combinedRes,
            new FileFilter() {

              /**
               * Excludes files matching one of the common file to exclude. The default excludes
               * pattern are the ones from
               * {org.codehaus.plexus.util.AbstractScanner#DEFAULTEXCLUDES}
               *
               * @see java.io.FileFilter#accept(java.io.File)
               */
              public boolean accept(File file) {
                for (String pattern : AbstractScanner.DEFAULTEXCLUDES) {
                  if (AbstractScanner.match(pattern, file.getAbsolutePath())) {
                    getLog()
                        .debug(
                            "Excluding "
                                + file.getName()
                                + " from resource copy : matching "
                                + pattern);
                    return false;
                  }
                }
                return true;
              }
            });
      } catch (IOException e) {
        throw new MojoExecutionException("", e);
      }
    }

    // Must combine assets.
    // The aapt tools does not support several -A arguments.
    // We copy the assets from extracted dependencies first, and then the local assets.
    // This allows redefining the assets in the current project
    if (extractedDependenciesAssets.exists()) {
      try {
        getLog().info("Copying dependency assets files to combined assets directory.");
        org.apache.commons.io.FileUtils.copyDirectory(
            extractedDependenciesAssets,
            combinedAssets,
            new FileFilter() {
              /**
               * Excludes files matching one of the common file to exclude. The default excludes
               * pattern are the ones from
               * {org.codehaus.plexus.util.AbstractScanner#DEFAULTEXCLUDES}
               *
               * @see java.io.FileFilter#accept(java.io.File)
               */
              public boolean accept(File file) {
                for (String pattern : AbstractScanner.DEFAULTEXCLUDES) {
                  if (AbstractScanner.match(pattern, file.getAbsolutePath())) {
                    getLog()
                        .debug(
                            "Excluding "
                                + file.getName()
                                + " from asset copy : matching "
                                + pattern);
                    return false;
                  }
                }

                return true;
              }
            });
      } catch (IOException e) {
        throw new MojoExecutionException("", e);
      }
    }

    if (assetsDirectory.exists()) {
      try {
        getLog().info("Copying local assets files to combined assets directory.");
        org.apache.commons.io.FileUtils.copyDirectory(
            assetsDirectory,
            combinedAssets,
            new FileFilter() {
              /**
               * Excludes files matching one of the common file to exclude. The default excludes
               * pattern are the ones from
               * {org.codehaus.plexus.util.AbstractScanner#DEFAULTEXCLUDES}
               *
               * @see java.io.FileFilter#accept(java.io.File)
               */
              public boolean accept(File file) {
                for (String pattern : AbstractScanner.DEFAULTEXCLUDES) {
                  if (AbstractScanner.match(pattern, file.getAbsolutePath())) {
                    getLog()
                        .debug(
                            "Excluding "
                                + file.getName()
                                + " from asset copy : matching "
                                + pattern);
                    return false;
                  }
                }

                return true;
              }
            });
      } catch (IOException e) {
        throw new MojoExecutionException("", e);
      }
    }

    File androidJar = getAndroidSdk().getAndroidJar();
    File outputFile =
        new File(project.getBuild().getDirectory(), project.getBuild().getFinalName() + ".ap_");

    List<String> commands = new ArrayList<String>();
    commands.add("package");
    commands.add("-f");
    commands.add("-M");
    commands.add(androidManifestFile.getAbsolutePath());
    for (File resOverlayDir : overlayDirectories) {
      if (resOverlayDir != null && resOverlayDir.exists()) {
        commands.add("-S");
        commands.add(resOverlayDir.getAbsolutePath());
      }
    }
    if (combinedRes.exists()) {
      commands.add("-S");
      commands.add(combinedRes.getAbsolutePath());
    }

    // Use the combined assets.
    // Indeed, aapt does not support several -A arguments.
    if (combinedAssets.exists()) {
      commands.add("-A");
      commands.add(combinedAssets.getAbsolutePath());
    }

    commands.add("-I");
    commands.add(androidJar.getAbsolutePath());
    commands.add("-F");
    commands.add(outputFile.getAbsolutePath());
    if (StringUtils.isNotBlank(configurations)) {
      commands.add("-c");
      commands.add(configurations);
    }
    getLog().info(getAndroidSdk().getPathForTool("aapt") + " " + commands.toString());
    try {
      executor.executeCommand(
          getAndroidSdk().getPathForTool("aapt"), commands, project.getBasedir(), false);
    } catch (ExecutionException e) {
      throw new MojoExecutionException("", e);
    }
  }