View Javadoc
1   /*
2    * Copyright (C) 2009-2011 Jayway AB
3    * Copyright (C) 2007-2008 JVending Masa
4    *
5    * Licensed under the Apache License, Version 2.0 (the "License");
6    * you may not use this file except in compliance with the License.
7    * You may obtain a copy of the License at
8    *
9    *      http://www.apache.org/licenses/LICENSE-2.0
10   *
11   * Unless required by applicable law or agreed to in writing, software
12   * distributed under the License is distributed on an "AS IS" BASIS,
13   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14   * See the License for the specific language governing permissions and
15   * limitations under the License.
16   */
17  package com.simpligility.maven.plugins.android;
18  
19  import com.android.builder.core.DefaultManifestParser;
20  import com.android.ddmlib.AndroidDebugBridge;
21  import com.android.ddmlib.DdmPreferences;
22  import com.android.ddmlib.IDevice;
23  import com.android.ddmlib.InstallException;
24  import com.simpligility.maven.plugins.android.common.AaptCommandBuilder;
25  import com.simpligility.maven.plugins.android.common.AndroidExtension;
26  import com.simpligility.maven.plugins.android.common.ArtifactResolverHelper;
27  import com.simpligility.maven.plugins.android.common.DependencyResolver;
28  import com.simpligility.maven.plugins.android.common.DeviceHelper;
29  import com.simpligility.maven.plugins.android.common.MavenToPlexusLogAdapter;
30  import com.simpligility.maven.plugins.android.common.NativeHelper;
31  import com.simpligility.maven.plugins.android.common.UnpackedLibHelper;
32  import com.simpligility.maven.plugins.android.config.ConfigPojo;
33  import com.simpligility.maven.plugins.android.configuration.Ndk;
34  import com.simpligility.maven.plugins.android.configuration.Sdk;
35  
36  import org.apache.commons.io.FileUtils;
37  import org.apache.commons.io.filefilter.TrueFileFilter;
38  import org.apache.commons.jxpath.JXPathContext;
39  import org.apache.commons.jxpath.JXPathNotFoundException;
40  import org.apache.commons.jxpath.xml.DocumentContainer;
41  import org.apache.commons.lang3.StringUtils;
42  import org.apache.maven.artifact.Artifact;
43  import org.apache.maven.artifact.handler.ArtifactHandler;
44  import org.apache.maven.artifact.resolver.ArtifactResolver;
45  import org.apache.maven.execution.MavenSession;
46  import org.apache.maven.model.Resource;
47  import org.apache.maven.plugin.AbstractMojo;
48  import org.apache.maven.plugin.MojoExecution;
49  import org.apache.maven.plugin.MojoExecutionException;
50  import org.apache.maven.plugin.MojoFailureException;
51  import org.apache.maven.plugins.annotations.Component;
52  import org.apache.maven.plugins.annotations.Parameter;
53  import org.apache.maven.project.MavenProject;
54  import org.apache.maven.project.MavenProjectHelper;
55  import org.apache.maven.shared.dependency.graph.DependencyGraphBuilder;
56  
57  import java.io.File;
58  import java.io.FileFilter;
59  import java.io.IOException;
60  import java.net.MalformedURLException;
61  import java.net.URL;
62  import java.util.ArrayList;
63  import java.util.Arrays;
64  import java.util.HashSet;
65  import java.util.List;
66  import java.util.Scanner;
67  import java.util.Set;
68  import java.util.concurrent.ExecutorService;
69  import java.util.concurrent.Executors;
70  import java.util.concurrent.atomic.AtomicBoolean;
71  
72  import static com.simpligility.maven.plugins.android.common.AndroidExtension.APK;
73  import com.simpligility.maven.plugins.android.configuration.Jack;
74  import static org.apache.commons.lang3.StringUtils.isBlank;
75  
76  /**
77   * Contains common fields and methods for android mojos.
78   *
79   * @author hugo.josefson@jayway.com
80   * @author Manfred Moser - manfred@simpligility.com
81   * @author William Ferguson - william.ferguson@xandar.com.au
82   * @author Malachi de AElfweald malachid@gmail.com
83   * @author Roy Clarkson - rclarkson@gopivotal.com
84   */
85  public abstract class AbstractAndroidMojo extends AbstractMojo
86  {
87  
88      public static final List<String> SUPPORTED_PACKAGING_TYPES = new ArrayList<String>();
89  
90      static
91      {
92          SUPPORTED_PACKAGING_TYPES.add( AndroidExtension.APK );
93      }
94  
95      /**
96       * Android Debug Bridge initialization timeout in milliseconds.
97       */
98      private static final long ADB_TIMEOUT_MS = 60L * 1000;
99  
100     /**
101      * The <code>ANDROID_NDK_HOME</code> environment variable name.
102      */
103     public static final String ENV_ANDROID_NDK_HOME = "ANDROID_NDK_HOME";
104 
105     /**
106      * <p>The Android NDK to use.</p>
107      * <p>Looks like this:</p>
108      * <pre>
109      * &lt;ndk&gt;
110      *     &lt;path&gt;/opt/android-ndk-r4&lt;/path&gt;
111      * &lt;/ndk&gt;
112      * </pre>
113      * <p>The <code>&lt;path&gt;</code> parameter is optional. The default is the setting of the ANDROID_NDK_HOME
114      * environment variable. The parameter can be used to override this setting with a different environment variable
115      * like this:</p>
116      * <pre>
117      * &lt;ndk&gt;
118      *     &lt;path&gt;${env.ANDROID_NDK_HOME}&lt;/path&gt;
119      * &lt;/ndk&gt;
120      * </pre>
121      * <p>or just with a hardcoded absolute path. The parameters can also be configured from command-line with parameter
122      * <code>-Dandroid.ndk.path</code>.</p>
123      */
124     @Parameter
125     @ConfigPojo( prefix = "ndk" )
126     private Ndk ndk;
127 
128     /**
129      * The maven project.
130      */
131     @Component
132     protected MavenProject project;
133 
134     /**
135      * The maven session.
136      */
137     @Component
138     protected MavenSession session;
139 
140     /**
141      */
142     @Component
143     protected MojoExecution execution;
144 
145     /**
146      * The java sources directory.
147      */
148     @Parameter( defaultValue = "${project.build.sourceDirectory}", readonly = true )
149     protected File sourceDirectory;
150 
151     /**
152      * The project build directory. Ie target.
153      */
154     @Parameter( defaultValue = "${project.build.directory}", readonly = true )
155     protected File targetDirectory;
156     
157     /**
158      * The output directory. Ie target/classes.
159      */
160     @Parameter( defaultValue = "${project.build.outputDirectory}", readonly = true )
161     protected File projectOutputDirectory;
162     
163     /**
164      * The project resources. By default a list containing src/main/resources.
165      */
166     @Parameter( defaultValue = "${project.build.resources}", readonly = true )
167     protected List<Resource> resources;
168     
169     /**
170      * The final name of the artifact.
171      */
172     @Parameter( defaultValue = "${project.build.finalName}", readonly = true )
173     protected String finalName;
174 
175     /**
176      * The Android resources (src/main/res) directory. Note that this is different from the 
177      * Maven/Java resources directory (src/main/resources) and should not be set to be the same
178      * since different processing is carried out on these folder by different plugins and tools.
179      */
180     @Parameter( defaultValue = "${project.basedir}/src/main/res" )
181     protected File resourceDirectory;
182 
183     /**
184      * The project source encoding. It will use the platform default encoding if the property is not set.
185      */
186     @Parameter( defaultValue = "${project.build.sourceEncoding}", readonly = true )
187     protected String sourceEncoding;
188     
189     /**
190      * Override default generated folder containing R.java
191      */
192     @Parameter( property = "android.genDirectory", defaultValue = "${project.build.directory}/generated-sources/r" )
193     protected File genDirectory;
194 
195     /**
196      * <p>Root folder containing native libraries to include in the application package.</p>
197      */
198     @Parameter( property = "android.nativeLibrariesDirectory", defaultValue = "${project.basedir}/src/main/libs" )
199     protected File nativeLibrariesDirectory;
200 
201     /**
202      * Folder in which the ndk libraries are collected ready for packaging.
203      */
204     @Parameter( defaultValue = "${project.build.directory}/ndk-libs", readonly = true )
205     protected File ndkOutputDirectory;
206 
207 
208     /**
209      * The android resources overlay directory. This will be overridden
210      * by resourceOverlayDirectories if present.
211      */
212     @Parameter( defaultValue = "${project.basedir}/res-overlay" )
213     protected File resourceOverlayDirectory;
214 
215     /**
216      * The android resources overlay directories. If this is specified,
217      * the {@link #resourceOverlayDirectory} parameter will be ignored.
218      */
219     @Parameter
220     protected File[] resourceOverlayDirectories;
221 
222     /**
223      * The android assets directory.
224      */
225     @Parameter( defaultValue = "${project.basedir}/src/main/assets" )
226     protected File assetsDirectory;
227 
228     /**
229      * The <code>AndroidManifest.xml</code> file.
230      */
231     @Parameter( property = "android.manifestFile", defaultValue = "${project.basedir}/src/main/AndroidManifest.xml" )
232     protected File androidManifestFile;
233 
234 
235     /**
236      * Path to which to save the result of updating/merging/processing the source <code>AndroidManifest.xml</code>
237      * file ({@link androidManifestFile}).
238      */
239     @Parameter( property = "destination.manifestFile", defaultValue = "${project.build.directory}/AndroidManifest.xml" )
240     protected File destinationManifestFile;
241 
242     /**
243      * <p>A possibly new package name for the application. This value will be passed on to the aapt
244      * parameter --rename-manifest-package. Look to aapt for more help on this. </p>
245      */
246     @Parameter( property = "android.renameManifestPackage" )
247     protected String renameManifestPackage;
248 
249     @Parameter( defaultValue = "${project.build.directory}/generated-sources/extracted-dependencies", readonly = true )
250     protected File extractedDependenciesDirectory;
251 
252     @Parameter(
253             defaultValue = "${project.build.directory}/generated-sources/extracted-dependencies/src/main/java",
254             readonly = true
255     )
256     protected File extractedDependenciesJavaSources;
257 
258     @Parameter(
259             defaultValue = "${project.build.directory}/generated-sources/extracted-dependencies/src/main/resources",
260             readonly = true
261     )
262     protected File extractedDependenciesJavaResources;
263 
264     /**
265      * The combined assets directory. This will contain both the assets found in "assets" as well as any assets
266      * contained in a apksources, apklib or aar dependencies.
267      */
268     @Parameter( defaultValue = "${project.build.directory}/generated-sources/combined-assets", readonly = true )
269     protected File combinedAssets;
270 
271     /**
272      * <p>Include jars stored in the libs folder of an apklib as dependencies.
273      * Do not delete or change name as it is used in the LifeCycleParticipant.</p>
274      * 
275      * @see ClasspathModifierLifecycleParticipant
276      */
277     @Parameter( defaultValue = "false" )
278     private boolean includeLibsJarsFromApklib;
279  
280     /**
281      * <p>Include jars stored in the libs folder of an aar as dependencies.
282      * Do not delete or change name as it is used in the LifeCycleParticipant.</p>
283      * 
284      * @see ClasspathModifierLifecycleParticipant
285      */
286     @Parameter( defaultValue = "true" )
287     private boolean includeLibsJarsFromAar;
288 
289     /**
290      * Specifies which the serial number of the device to connect to. Using the special values "usb" or
291      * "emulator" is also valid. "usb" will connect to all actual devices connected (via usb). "emulator" will
292      * connect to all emulators connected. Multiple devices will be iterated over in terms of goals to run. All
293      * device interaction goals support this so you can e.. deploy the apk to all attached emulators and devices.
294      * Goals supporting this are devices, deploy, undeploy, redeploy, pull, push and instrument.
295      */
296     @Parameter( property = "android.device" )
297     protected String device;
298 
299     /**
300      * <p>Specifies a list of serial numbers of each device you want to connect to. Using the special values "usb" or
301      * "emulator" is also valid. "usb" will connect to all actual devices connected (via usb). "emulator" will
302      * connect to all emulators connected. Multiple devices will be iterated over in terms of goals to run. All
303      * device interaction goals support this so you can e.. deploy the apk to all attached emulators and devices.
304      * Goals supporting this are devices, deploy, undeploy, redeploy, pull, push and instrument.</p>
305      * <pre>
306      * &lt;devices&gt;
307      *     &lt;device&gt;usb&lt;/device&gt;
308      *     &lt;device&gt;emulator-5554&lt;/device&gt;
309      * &lt;/devices&gt;
310      * </pre>
311      * <p>This parameter can also be configured from command-line with
312      * parameter <code>-Dandroid.devices=usb,emulator</code>.</p>
313      */
314     @Parameter( property = "android.devices" )
315     protected String[] devices;
316     
317     /**
318      * <p>Specifies the number of threads to use for deploying and testing on attached devices.
319      * 
320      * <p>This parameter can also be configured from command-line with
321      * parameter <code>-Dandroid.deviceThreads=2</code>.</p>
322      */
323     @Parameter( property = "android.deviceThreads" )
324     protected int deviceThreads;
325 
326     /**
327      * <p>External IP addresses. The connect goal of the android maven plugin  will execute an adb connect on
328      * each IP address. If you have external dervice, you should call this connect goal before any other goal :
329      * mvn clean android:connect install.</p>
330      * <p>The Maven plugin will automatically add all these IP addresses into the the devices parameter.
331      * If you want to disconnect the IP addresses after the build, you can call the disconnect goal :
332      * mvn clean android:connect install android:disconnect</p>
333      *
334      * <pre>
335      * &lt;ips&gt;
336      *     &lt;ip&gt;127.0.0.1:5556&lt;/ip&gt;
337      * &lt;/ips&gt;
338      * </pre>
339      */
340     @Parameter( property = "android.ips" )
341     protected String[] ips;
342 
343     /**
344      * A selection of configurations to be included in the APK as a comma separated list. This will limit the
345      * configurations for a certain type. For example, specifying <code>hdpi</code> will exclude all resource folders
346      * with the <code>mdpi</code> or <code>ldpi</code> modifiers, but won't affect language or orientation modifiers.
347      * For more information about this option, look in the aapt command line help.
348      */
349     @Parameter( property = "android.configurations" )
350     protected String configurations;
351 
352     /**
353      * A list of extra arguments that must be passed to aapt.
354      */
355     @Parameter( property = "android.aaptExtraArgs" )
356     protected String[] aaptExtraArgs;
357 
358     /**
359      * Activate verbose output for the aapt execution in Maven debug mode. Defaults to "false"
360      */
361     @Parameter( property = "android.aaptVerbose" )
362     protected boolean aaptVerbose;
363 
364     /**
365      * Automatically create a ProGuard configuration file that will guard Activity classes and the like that are
366      * defined in the AndroidManifest.xml. This files is then automatically used in the proguard mojo execution, 
367      * if enabled.
368      */
369     @Parameter( property = "android.proguardFile" )
370     protected File proguardFile;
371 
372     /**
373      * Decides whether the Apk should be generated or not. If set to false, dx and apkBuilder will not run. This is
374      * probably most useful for a project used to generate apk sources to be inherited into another application
375      * project.
376      */
377     @Parameter( property = "android.generateApk", defaultValue = "true" )
378     protected boolean generateApk;
379 
380     @Component
381     private ArtifactResolver artifactResolver;
382 
383     @Component
384     private ArtifactHandler artifactHandler;
385 
386     /**
387      * Generates R.java into a different package.
388      */
389     @Parameter( property = "android.customPackage" )
390     protected String customPackage;
391 
392     /**
393      * Maven ProjectHelper.
394      */
395     @Component
396     protected MavenProjectHelper projectHelper;
397 
398     /**
399      * <p>The Android SDK to use.</p>
400      * <p>Looks like this:</p>
401      * <pre>
402      * &lt;sdk&gt;
403      *     &lt;path&gt;/opt/android-sdk-linux&lt;/path&gt;
404      *     &lt;platform&gt;2.1&lt;/platform&gt;
405      * &lt;/sdk&gt;
406      * </pre>
407      * <p>The <code>&lt;platform&gt;</code> parameter is optional, and corresponds to the
408      * <code>platforms/android-*</code> directories in the Android SDK directory. Default is the latest available
409      * version, so you only need to set it if you for example want to use platform 1.5 but also have e.g. 2.2 installed.
410      * Has no effect when used on an Android SDK 1.1. The parameter can also be coded as the API level. Therefore valid
411      * values are 1.1, 1.5, 1.6, 2.0, 2.01, 2.1, 2.2 and so as well as 3, 4, 5, 6, 7, 8... 19. If a platform/api level 
412      * is not installed on the machine an error message will be produced. </p>
413      * <p>The <code>&lt;path&gt;</code> parameter is optional. The default is the setting of the ANDROID_HOME
414      * environment variable. The parameter can be used to override this setting with a different environment variable
415      * like this:</p>
416      * <pre>
417      * &lt;sdk&gt;
418      *     &lt;path&gt;${env.ANDROID_SDK}&lt;/path&gt;
419      * &lt;/sdk&gt;
420      * </pre>
421      * <p>or just with a hard-coded absolute path. The parameters can also be configured from command-line with
422      * parameters <code>-Dandroid.sdk.path</code> and <code>-Dandroid.sdk.platform</code>.</p>
423      */
424     @Parameter
425     private Sdk sdk;
426 
427     /**
428      * <p>Parameter designed to pick up <code>-Dandroid.sdk.path</code> in case there is no pom with an
429      * <code>&lt;sdk&gt;</code> configuration tag.</p>
430      * <p>Corresponds to {@link com.simpligility.maven.plugins.android.configuration.Sdk#path}.</p>
431      */
432     @Parameter( property = "android.sdk.path", readonly = true )
433     private File sdkPath;
434 
435     /**
436      * <p>Parameter designed to pick up environment variable <code>ANDROID_HOME</code> in case
437      * <code>android.sdk.path</code> is not configured.</p>
438      */
439     @Parameter( defaultValue = "${env.ANDROID_HOME}", readonly = true )
440     private String envAndroidHome;
441 
442     /**
443      * The <code>ANDROID_HOME</code> environment variable name.
444      */
445     public static final String ENV_ANDROID_HOME = "ANDROID_HOME";
446 
447     /**
448      * <p>Parameter designed to pick up <code>-Dandroid.sdk.platform</code> in case there is no pom with an
449      * <code>&lt;sdk&gt;</code> configuration tag.</p>
450      * <p>Corresponds to {@link com.simpligility.maven.plugins.android.configuration.Sdk#platform}.</p>
451      */
452     @Parameter( property = "android.sdk.platform", readonly = true )
453     private String sdkPlatform;
454 
455     /**
456      * <p>Whether to undeploy an apk from the device before deploying it.</p>
457      * 
458      * <p>Only has effect when running <code>mvn android:deploy</code> in an Android application project manually, or
459      * when running <code>mvn integration-test</code> (or <code>mvn install</code>) in a project with instrumentation
460      * tests.
461      * </p>
462      * 
463      * <p>It is useful to keep this set to <code>true</code> at all times, because if an apk with the same package was
464      * previously signed with a different keystore, and deployed to the device, deployment will fail because your
465      * keystore is different.</p>
466      */
467     @Parameter( property = "android.undeployBeforeDeploy", defaultValue = "false" )
468     protected boolean undeployBeforeDeploy;
469 
470     /**
471      * <p>Whether to attach the normal .jar file to the build, so it can be depended on by for example integration-tests
472      * which may then access {@code R.java} from this project.</p>
473      * <p>Only disable it if you know you won't need it for any integration-tests. Otherwise, leave it enabled.</p>
474      */
475     @Parameter( property = "android.attachJar", defaultValue = "true" )
476     protected boolean attachJar;
477 
478     /**
479      * <p>Whether to attach sources to the build, which can be depended on by other {@code apk} projects, for including
480      * them in their builds.</p>
481      * <p>Enabling this setting is only required if this project's source code and/or res(ources) will be included in
482      * other projects, using the Maven &lt;dependency&gt; tag.</p>
483      */
484     @Parameter( property = "android.attachSources", defaultValue = "false" )
485     protected boolean attachSources;
486 
487     /**
488      * <p>Parameter designed to pick up <code>-Dandroid.ndk.path</code> in case there is no pom with an
489      * <code>&lt;ndk&gt;</code> configuration tag.</p>
490      * <p>Corresponds to {@link com.simpligility.maven.plugins.android.configuration.Ndk#path}.</p>
491      */
492     @Parameter( property = "android.ndk.path", readonly = true )
493     private File ndkPath;
494 
495     /**
496      * Whether to create a release build (default is false / debug build). This affect BuildConfig generation 
497      * and apk generation at this stage, but should probably affect other aspects of the build.
498      */
499     @Parameter( property = "android.release", defaultValue = "false" )
500     protected boolean release;
501 
502     /**
503      * The timeout value for an adb connection in milliseconds.
504      */
505     @Parameter( property = "android.adb.connectionTimeout", defaultValue = "5000" )
506     protected int adbConnectionTimeout;
507 
508     /**
509      * Folder in which AAR library dependencies will be unpacked.
510      */
511     @Parameter( property = "unpackedLibsFolder", defaultValue = "${project.build.directory}/unpacked-libs" )
512     private File unpackedLibsFolder;
513     
514     /**
515      * Whether the plugin should show a warning if conflicting dependencies with the Android provided ones exist.
516      * 
517      * @see ClasspathModifierLifecycleParticipant
518      */
519     @Parameter( defaultValue = "false" )
520     private File disableConflictingDependenciesWarning;
521 
522     /**
523     * configure the Jack compiler
524     */    
525     @Parameter
526     private Jack jack;
527 
528     
529     private UnpackedLibHelper unpackedLibHelper;
530     private ArtifactResolverHelper artifactResolverHelper;
531     private NativeHelper nativeHelper;
532 
533     /**
534      *
535      */
536     private static final Object ADB_LOCK = new Object();
537 
538     /**
539      *
540      */
541     private static boolean adbInitialized = false;
542 
543     /**
544      * Dependency graph builder component.
545      */
546     @Component( hint = "default" )
547     protected DependencyGraphBuilder dependencyGraphBuilder;
548 
549     protected final DependencyResolver getDependencyResolver()
550     {
551         return new DependencyResolver( new MavenToPlexusLogAdapter( getLog() ), dependencyGraphBuilder );
552     }
553 
554     /**
555      * @return a {@code Set} of dependencies which may be extracted and otherwise included in other artifacts. Never
556      *         {@code null}. This excludes artifacts of the {@code EXCLUDED_DEPENDENCY_SCOPES} scopes.
557      */
558     protected Set<Artifact> getRelevantCompileArtifacts()
559     {
560         final List<Artifact> allArtifacts = project.getCompileArtifacts();
561         return getArtifactResolverHelper().getFilteredArtifacts( allArtifacts );
562     }
563 
564     /**
565      * @return a {@code Set} of direct project dependencies. Never {@code null}. This excludes artifacts of the {@code
566      *         EXCLUDED_DEPENDENCY_SCOPES} scopes.
567      */
568     protected Set<Artifact> getDirectDependencyArtifacts()
569     {
570         final Set<Artifact> allArtifacts = project.getDependencyArtifacts();
571         return getArtifactResolverHelper().getFilteredArtifacts( allArtifacts );
572     }
573 
574     /**
575      * Provides transitive dependency artifacts having types defined by {@code types} argument
576      * or all types if {@code types} argument is empty
577      *
578      * @param types artifact types to be selected
579      * @return a {@code List} of all project dependencies. Never {@code null}.
580      *         This excludes artifacts of the {@link ArtifactResolverHelper.EXCLUDE_NON_PACKAGED_SCOPES} scopes.
581      *         This should maintain dependency order to comply with library project resource precedence.
582      */
583     protected Set<Artifact> getTransitiveDependencyArtifacts( String... types )
584     {
585         return getArtifactResolverHelper().getFilteredArtifacts( project.getArtifacts(), types );
586     }
587 
588     /**
589      * Provides transitive dependency artifacts only defined types based on {@code types} argument
590      * or all types if {@code types} argument is empty
591      *
592      * @param filteredScopes    List of scopes to be removed (ie filtered out).
593      * @param types             Zero or more artifact types to be selected.
594      * @return a {@code List} of all project dependencies. Never {@code null}.
595      *         This should maintain dependency order to comply with library project resource precedence.
596      */
597     protected Set<Artifact> getTransitiveDependencyArtifacts( List<String> filteredScopes, String... types )
598     {
599         return getArtifactResolverHelper().getFilteredArtifacts( filteredScopes, project.getArtifacts(), types );
600     }
601 
602     /**
603      * Attempts to resolve an {@link Artifact} to a {@link File}.
604      *
605      * @param artifact to resolve
606      * @return a {@link File} to the resolved artifact, never <code>null</code>.
607      * @throws MojoExecutionException if the artifact could not be resolved.
608      */
609     protected File resolveArtifactToFile( Artifact artifact ) throws MojoExecutionException
610     {
611         return getArtifactResolverHelper().resolveArtifactToFile( artifact );
612     }
613 
614     /**
615      * Initialize the Android Debug Bridge and wait for it to start. Does not reinitialize it if it has
616      * already been initialized (that would through and IllegalStateException...). Synchronized sine
617      * the init call in the library is also synchronized .. just in case.
618      */
619     protected AndroidDebugBridge initAndroidDebugBridge() throws MojoExecutionException
620     {
621         synchronized ( ADB_LOCK )
622         {
623             if ( ! adbInitialized )
624             {
625                 DdmPreferences.setTimeOut( adbConnectionTimeout );
626                 AndroidDebugBridge.init( false );
627                 adbInitialized = true;
628             }
629             AndroidDebugBridge androidDebugBridge = AndroidDebugBridge
630                     .createBridge( getAndroidSdk().getAdbPath(), false );
631             waitUntilConnected( androidDebugBridge );
632             return androidDebugBridge;
633         }
634     }
635 
636     /**
637      * Run a wait loop until adb is connected or trials run out. This method seems to work more reliably then using a
638      * listener.
639      */
640     private void waitUntilConnected( AndroidDebugBridge adb )
641     {
642         int trials = 10;
643         final int connectionWaitTime = 50;
644         while ( trials > 0 )
645         {
646             try
647             {
648                 Thread.sleep( connectionWaitTime );
649             }
650             catch ( InterruptedException e )
651             {
652                 e.printStackTrace();
653             }
654             if ( adb.isConnected() )
655             {
656                 break;
657             }
658             trials--;
659         }
660     }
661 
662     /**
663      * Wait for the Android Debug Bridge to return an initial device list.
664      */
665     protected void waitForInitialDeviceList( final AndroidDebugBridge androidDebugBridge ) throws MojoExecutionException
666     {
667         if ( ! androidDebugBridge.hasInitialDeviceList() )
668         {
669             getLog().info( "Waiting for initial device list from the Android Debug Bridge" );
670             long limitTime = System.currentTimeMillis() + ADB_TIMEOUT_MS;
671             while ( ! androidDebugBridge.hasInitialDeviceList() && ( System.currentTimeMillis() < limitTime ) )
672             {
673                 try
674                 {
675                     Thread.sleep( 1000 );
676                 }
677                 catch ( InterruptedException e )
678                 {
679                     throw new MojoExecutionException(
680                             "Interrupted waiting for initial device list from Android Debug Bridge" );
681                 }
682             }
683             if ( ! androidDebugBridge.hasInitialDeviceList() )
684             {
685                 getLog().error( "Did not receive initial device list from the Android Debug Bridge." );
686             }
687         }
688     }
689 
690     /**
691      * Deploys an apk file to a connected emulator or usb device.
692      *
693      * @param apkFile the file to deploy
694      * @throws MojoExecutionException If there is a problem deploying the apk file.
695      */
696     protected void deployApk( final File apkFile ) throws MojoExecutionException, MojoFailureException
697     {
698         if ( undeployBeforeDeploy )
699         {
700             undeployApk( apkFile );
701         }
702         doWithDevices( new DeviceCallback()
703         {
704             public void doWithDevice( final IDevice device ) throws MojoExecutionException
705             {
706                 String deviceLogLinePrefix = DeviceHelper.getDeviceLogLinePrefix( device );
707                 try
708                 {
709                     device.installPackage( apkFile.getAbsolutePath(), true );
710                     getLog().info( deviceLogLinePrefix + "Successfully installed " + apkFile.getAbsolutePath() ); 
711                     getLog().debug( " to " + DeviceHelper.getDescriptiveName( device ) );
712                 }
713                 catch ( InstallException e )
714                 {
715                     throw new MojoExecutionException( deviceLogLinePrefix + "Install of " + apkFile.getAbsolutePath()
716                             + " failed.", e );
717                 }
718             }
719         } );
720     }
721 
722     /**
723      *
724      * @throws MojoExecutionException
725      * @throws MojoFailureException
726      */
727     protected void deployDependencies() throws MojoExecutionException, MojoFailureException
728     {
729         Set<Artifact> directDependentArtifacts = project.getDependencyArtifacts();
730         if ( directDependentArtifacts != null )
731         {
732             for ( Artifact artifact : directDependentArtifacts )
733             {
734                 String type = artifact.getType();
735                 if ( type.equals( APK ) )
736                 {
737                     getLog().debug( "Detected apk dependency " + artifact + ". Will resolve and deploy to device..." );
738                     final File targetApkFile = resolveArtifactToFile( artifact );
739                     getLog().debug( "Deploying " + targetApkFile + " to device..." );
740                     deployApk( targetApkFile );
741                 }
742             }
743         }
744     }
745 
746     /**
747      * Deploy the apk built with the current projects to all attached devices and emulators. 
748      * Skips other projects in a multi-module build without terminating.
749      * 
750      * @throws MojoExecutionException
751      * @throws MojoFailureException
752      */
753     protected void deployBuiltApk() throws MojoExecutionException, MojoFailureException
754     {
755         if ( project.getPackaging().equals( APK ) )
756         {
757             File apkFile = new File( targetDirectory, finalName + "." + APK );
758             deployApk( apkFile );
759         }
760         else 
761         {
762             getLog().info( "Project packaging is not apk, skipping deployment." );
763         }
764     }
765 
766 
767     /**
768      * Performs the callback action on the devices determined by
769      * {@link #shouldDoWithThisDevice(com.android.ddmlib.IDevice)}
770      *
771      * @param deviceCallback the action to perform on each device
772      * @throws org.apache.maven.plugin.MojoExecutionException
773      *          in case there is a problem
774      * @throws org.apache.maven.plugin.MojoFailureException
775      *          in case there is a problem
776      */
777     protected void doWithDevices( final DeviceCallback deviceCallback )
778             throws MojoExecutionException, MojoFailureException
779     {
780         final AndroidDebugBridge androidDebugBridge = initAndroidDebugBridge();
781 
782         if ( !androidDebugBridge.isConnected() )
783         {
784             throw new MojoExecutionException( "Android Debug Bridge is not connected." );
785         }
786 
787         waitForInitialDeviceList( androidDebugBridge );
788         List<IDevice> devices = Arrays.asList( androidDebugBridge.getDevices() );
789         int numberOfDevices = devices.size();
790         getLog().debug( "Found " + numberOfDevices + " devices connected with the Android Debug Bridge" );
791         if ( devices.size() == 0 )
792         {
793             throw new MojoExecutionException( "No online devices attached." );
794         }
795 
796         int threadCount = getDeviceThreads();
797         if ( getDeviceThreads() == 0 )
798         {
799             getLog().info( "android.devicesThreads parameter not set, using a thread for each attached device" );
800             threadCount = numberOfDevices;
801         }
802         else
803         {
804             getLog().info( "android.devicesThreads parameter set to " + getDeviceThreads() );
805         }
806 
807         boolean shouldRunOnAllDevices = getDevices().size() == 0;
808         if ( shouldRunOnAllDevices )
809         {
810             getLog().info( "android.devices parameter not set, using all attached devices" );
811         }
812         else
813         {
814             getLog().info( "android.devices parameter set to " + getDevices().toString() );
815         }
816 
817         ArrayList<DoThread> doThreads = new ArrayList<DoThread>();
818         ExecutorService executor = Executors.newFixedThreadPool( threadCount );
819         for ( final IDevice idevice : devices )
820         {
821             if ( shouldRunOnAllDevices )
822             {
823                 String deviceType = idevice.isEmulator() ? "Emulator " : "Device ";
824                 getLog().info( deviceType + DeviceHelper.getDescriptiveName( idevice ) + " found." );
825             }
826             if ( shouldRunOnAllDevices || shouldDoWithThisDevice( idevice ) )
827             {
828                 DoThread deviceDoThread = new DoThread() {
829                     public void runDo() throws MojoFailureException, MojoExecutionException
830                     {
831                         deviceCallback.doWithDevice( idevice );
832                     }
833                 };
834                 doThreads.add( deviceDoThread );
835                 executor.execute( deviceDoThread );
836             }
837         }
838         executor.shutdown();
839         while ( ! executor.isTerminated() )
840         {
841             // waiting for threads finish
842         }
843         throwAnyDoThreadErrors( doThreads );
844 
845         if ( ! shouldRunOnAllDevices && doThreads.isEmpty() )
846         {
847             throw new MojoExecutionException( "No device found for android.device=" + getDevices().toString() );
848         }
849     }
850 
851     private void throwAnyDoThreadErrors( ArrayList<DoThread> doThreads ) throws MojoExecutionException,
852             MojoFailureException
853     {
854         for ( DoThread deviceDoThread : doThreads )
855         {
856             if ( deviceDoThread.failure != null )
857             {
858                 throw deviceDoThread.failure;
859             }
860             if ( deviceDoThread.execution != null )
861             {
862                 throw deviceDoThread.execution;
863             }
864         }
865     }
866 
867     /**
868      * Determines if this {@link IDevice}(s) should be used
869      *
870      * @param idevice the device to check
871      * @return if the device should be used
872      * @throws org.apache.maven.plugin.MojoExecutionException
873      *          in case there is a problem
874      * @throws org.apache.maven.plugin.MojoFailureException
875      *          in case there is a problem
876      */
877     private boolean shouldDoWithThisDevice( IDevice idevice ) throws MojoExecutionException, MojoFailureException
878     {
879 
880         for ( String device : getDevices() )
881         {
882             // use specified device or all emulators or all devices
883             if ( "emulator".equals( device ) && idevice.isEmulator() )
884             {
885                 return true;
886             }
887 
888             if ( "usb".equals( device ) && ! idevice.isEmulator() )
889             {
890                 return true;
891             }
892 
893             if ( idevice.isEmulator() && ( device.equalsIgnoreCase( idevice.getAvdName() ) || device
894                     .equalsIgnoreCase( idevice.getSerialNumber() ) ) )
895             {
896                 return true;
897             }
898 
899             if ( ! idevice.isEmulator() && device.equals( idevice.getSerialNumber() ) )
900             {
901                 return true;
902             }
903         }
904 
905         return false;
906     }
907 
908     /**
909      * Undeploys an apk from a connected emulator or usb device. Also deletes the application's data and cache
910      * directories on the device.
911      *
912      * @param apkFile the file to undeploy
913      * @return <code>true</code> if successfully undeployed, <code>false</code> otherwise.
914      */
915     protected boolean undeployApk( File apkFile ) throws MojoExecutionException, MojoFailureException
916     {
917         final String packageName;
918         packageName = extractPackageNameFromApk( apkFile );
919         return undeployApk( packageName );
920     }
921 
922     /**
923      * Undeploys an apk, specified by package name, from a connected emulator
924      * or usb device. Also deletes the application's data and cache
925      * directories on the device.
926      *
927      * @param packageName the package name to undeploy.
928      * @return <code>true</code> if successfully undeployed, <code>false</code> otherwise.
929      */
930     protected boolean undeployApk( final String packageName ) throws MojoExecutionException, MojoFailureException
931     {
932 
933         final AtomicBoolean result = new AtomicBoolean( true ); // if no devices are present, it counts as successful
934 
935         doWithDevices( new DeviceCallback()
936         {
937             public void doWithDevice( final IDevice device ) throws MojoExecutionException
938             {
939                 String deviceLogLinePrefix = DeviceHelper.getDeviceLogLinePrefix( device );
940                 try
941                 {
942                     device.uninstallPackage( packageName );
943                     getLog().info( deviceLogLinePrefix + "Successfully uninstalled " + packageName );
944                     getLog().debug( " from " + DeviceHelper.getDescriptiveName( device ) );
945                     result.set( true );
946                 }
947                 catch ( InstallException e )
948                 {
949                     result.set( false );
950                     throw new MojoExecutionException( deviceLogLinePrefix + "Uninstall of " + packageName
951                             + " failed.", e );
952                 }
953             }
954         } );
955 
956         return result.get();
957     }
958 
959     /**
960      * Extracts the package name from an apk file.
961      *
962      * @param apkFile apk file to extract package name from.
963      * @return the package name from inside the apk file.
964      */
965     protected String extractPackageNameFromApk( File apkFile ) throws MojoExecutionException
966     {
967         CommandExecutor executor = CommandExecutor.Factory.createDefaultCommmandExecutor();
968         executor.setLogger( this.getLog() );
969         executor.setCaptureStdOut( true );
970         executor.setCaptureStdErr( true );
971 
972         AaptCommandBuilder commandBuilder = AaptCommandBuilder
973                 .dump( getLog() )
974                 .xmlTree()
975                 .setPathToApk( apkFile.getAbsolutePath() )
976                 .addAssetFile( "AndroidManifest.xml" );
977 
978         getLog().info( getAndroidSdk().getAaptPath() + " " + commandBuilder.toString() );
979         try
980         {
981             executor.executeCommand( getAndroidSdk().getAaptPath(), commandBuilder.build(), false );
982             final String xmlTree = executor.getStandardOut();
983             return extractPackageNameFromAndroidManifestXmlTree( xmlTree );
984         }
985         catch ( ExecutionException e )
986         {
987             throw new MojoExecutionException(
988                     "Error while trying to figure out package name from inside apk file " + apkFile );
989         }
990         finally
991         {
992             String errout = executor.getStandardError();
993             if ( ( errout != null ) && ( errout.trim().length() > 0 ) )
994             {
995                 getLog().error( errout );
996             }
997         }
998     }
999 
1000     /**
1001      * Extracts the package name from an XmlTree dump of AndroidManifest.xml by the <code>aapt</code> tool.
1002      *
1003      * @param aaptDumpXmlTree output from <code>aapt dump xmltree &lt;apkFile&gt; AndroidManifest.xml
1004      * @return the package name from inside the apkFile.
1005      */
1006     protected String extractPackageNameFromAndroidManifestXmlTree( String aaptDumpXmlTree )
1007     {
1008         final Scanner scanner = new Scanner( aaptDumpXmlTree );
1009         // Finds the root element named "manifest".
1010         scanner.findWithinHorizon( "^E: manifest", 0 );
1011         // Finds the manifest element's attribute named "package".
1012         scanner.findWithinHorizon( "  A: package=\"", 0 );
1013         // Extracts the package value including the trailing double quote.
1014         String packageName = scanner.next( ".*?\"" );
1015         // Removes the double quote.
1016         packageName = packageName.substring( 0, packageName.length() - 1 );
1017         return packageName;
1018     }
1019 
1020     /**
1021      * Provides package name for android artifact.
1022      *
1023      * @param artifact android artifact which package have to be extracted
1024      * @return package name
1025      * @throws MojoExecutionException if there is no AndroidManifest.xml for provided artifact
1026      *      or appears error while parsing in {@link #extractPackageNameFromAndroidManifest(File)}
1027      *
1028      * @see #extractPackageNameFromAndroidManifest(File)
1029      */
1030     protected String extractPackageNameFromAndroidArtifact( Artifact artifact ) throws MojoExecutionException
1031     {
1032         final File unpackedLibFolder = getUnpackedLibFolder( artifact );
1033         final File manifest = new File( unpackedLibFolder, "AndroidManifest.xml" );
1034         if ( !manifest.exists() )
1035         {
1036             throw new MojoExecutionException(
1037                     "AndroidManifest.xml file wasn't found in next place: " + unpackedLibFolder );
1038         }
1039         return extractPackageNameFromAndroidManifest( manifest );
1040     }
1041 
1042     protected String extractPackageNameFromAndroidManifest( File manifestFile )
1043     {
1044         return new DefaultManifestParser( manifestFile ).getPackage();
1045     }
1046 
1047     /**
1048      * @return the package name from this project's Android Manifest.
1049      */
1050     protected final String getAndroidManifestPackageName()
1051     {
1052         return extractPackageNameFromAndroidManifest( destinationManifestFile );
1053     }
1054 
1055     /**
1056      * Attempts to find the instrumentation test runner from inside the AndroidManifest.xml file.
1057      *
1058      * @param manifestFile the AndroidManifest.xml file to inspect.
1059      * @return the instrumentation test runner declared in AndroidManifest.xml, or {@code null} if it is not declared.
1060      * @throws MojoExecutionException
1061      */
1062     protected String extractInstrumentationRunnerFromAndroidManifest( File manifestFile )
1063             throws MojoExecutionException
1064     {
1065         final URL xmlURL;
1066         try
1067         {
1068             xmlURL = manifestFile.toURI().toURL();
1069         }
1070         catch ( MalformedURLException e )
1071         {
1072             throw new MojoExecutionException(
1073                     "Error while trying to figure out instrumentation runner from inside AndroidManifest.xml file "
1074                             + manifestFile, e );
1075         }
1076         final DocumentContainer documentContainer = new DocumentContainer( xmlURL );
1077         final Object instrumentationRunner;
1078         try
1079         {
1080             instrumentationRunner = JXPathContext.newContext( documentContainer )
1081                     .getValue( "manifest//instrumentation/@android:name", String.class );
1082         }
1083         catch ( JXPathNotFoundException e )
1084         {
1085             return null;
1086         }
1087         return ( String ) instrumentationRunner;
1088     }
1089 
1090     protected final boolean isInstrumentationTest() throws MojoExecutionException
1091     {
1092         return extractInstrumentationRunnerFromAndroidManifest( destinationManifestFile ) != null;
1093     }
1094 
1095     /**
1096      * <p>Returns the Android SDK to use.</p>
1097      * 
1098      * <p>Current implementation looks for System property <code>android.sdk.path</code>, then
1099      * <code>&lt;sdk&gt;&lt;path&gt;</code> configuration in pom, then environment variable <code>ANDROID_HOME</code>.
1100      * 
1101      * <p>This is where we collect all logic for how to lookup where it is, and which one to choose. The lookup is
1102      * based on available parameters. This method should be the only one you should need to look at to understand how
1103      * the Android SDK is chosen, and from where on disk.</p>
1104      *
1105      * @return the Android SDK to use.
1106      * @throws org.apache.maven.plugin.MojoExecutionException
1107      *          if no Android SDK path configuration is available at all.
1108      */
1109     protected AndroidSdk getAndroidSdk() throws MojoExecutionException
1110     {
1111         File chosenSdkPath;
1112         String chosenSdkPlatform;
1113         String buildToolsVersion = null;
1114 
1115         if ( sdk != null )
1116         {
1117             // An <sdk> tag exists in the pom.
1118             buildToolsVersion = sdk.getBuildTools();
1119 
1120             if ( sdk.getPath() != null )
1121             {
1122                 // An <sdk><path> tag is set in the pom.
1123 
1124                 chosenSdkPath = sdk.getPath();
1125             }
1126             else
1127             {
1128                 // There is no <sdk><path> tag in the pom.
1129 
1130                 if ( sdkPath != null )
1131                 {
1132                     // -Dandroid.sdk.path is set on command line, or via <properties><android.sdk.path>...
1133                     chosenSdkPath = sdkPath;
1134                 }
1135                 else
1136                 {
1137                     // No -Dandroid.sdk.path is set on command line, or via <properties><android.sdk.path>...
1138                     chosenSdkPath = new File( getAndroidHomeOrThrow() );
1139                 }
1140             }
1141 
1142             // Use <sdk><platform> from pom if it's there, otherwise try -Dandroid.sdk.platform from command line or
1143             // <properties><sdk.platform>...
1144             if ( ! isBlank( sdk.getPlatform() ) )
1145             {
1146                 chosenSdkPlatform = sdk.getPlatform();
1147             }
1148             else
1149             {
1150                 chosenSdkPlatform = sdkPlatform;
1151             }
1152         }
1153         else
1154         {
1155             // There is no <sdk> tag in the pom.
1156 
1157             if ( sdkPath != null )
1158             {
1159                 // -Dandroid.sdk.path is set on command line, or via <properties><android.sdk.path>...
1160                 chosenSdkPath = sdkPath;
1161             }
1162             else
1163             {
1164                 // No -Dandroid.sdk.path is set on command line, or via <properties><android.sdk.path>...
1165                 chosenSdkPath = new File( getAndroidHomeOrThrow() );
1166             }
1167 
1168             // Use any -Dandroid.sdk.platform from command line or <properties><sdk.platform>...
1169             chosenSdkPlatform = sdkPlatform;
1170         }
1171 
1172         return new AndroidSdk( chosenSdkPath, chosenSdkPlatform, buildToolsVersion );
1173     }
1174 
1175     protected Jack getJack() 
1176     {
1177         if ( jack == null ) 
1178         {
1179             return new Jack( super.getPluginContext() );
1180         }
1181         else 
1182         {
1183             return jack;
1184         }
1185     }
1186     
1187     private String getAndroidHomeOrThrow() throws MojoExecutionException
1188     {
1189         final String androidHome = System.getenv( ENV_ANDROID_HOME );
1190         if ( isBlank( androidHome ) )
1191         {
1192             throw new MojoExecutionException( "No Android SDK path could be found. You may configure it in the "
1193                     + "plugin configuration section in the pom file using <sdk><path>...</path></sdk> or "
1194                     + "<properties><android.sdk.path>...</android.sdk.path></properties> or on command-line "
1195                     + "using -Dandroid.sdk.path=... or by setting environment variable " + ENV_ANDROID_HOME );
1196         }
1197         return androidHome;
1198     }
1199 
1200     protected final File getUnpackedLibsDirectory()
1201     {
1202         return getUnpackedLibHelper().getUnpackedLibsFolder();
1203     }
1204 
1205     public final File getUnpackedLibFolder( Artifact artifact )
1206     {
1207         return getUnpackedLibHelper().getUnpackedLibFolder( artifact );
1208     }
1209 
1210     protected final File getUnpackedAarClassesJar( Artifact artifact )
1211     {
1212         return getUnpackedLibHelper().getUnpackedClassesJar( artifact );
1213     }
1214 
1215     protected final File getUnpackedApkLibSourceFolder( Artifact artifact )
1216     {
1217         return getUnpackedLibHelper().getUnpackedApkLibSourceFolder( artifact );
1218     }
1219 
1220     protected final File getUnpackedLibResourceFolder( Artifact artifact )
1221     {
1222         return getUnpackedLibHelper().getUnpackedLibResourceFolder( artifact );
1223     }
1224 
1225     protected final File getUnpackedLibAssetsFolder( Artifact artifact )
1226     {
1227         return getUnpackedLibHelper().getUnpackedLibAssetsFolder( artifact );
1228     }
1229 
1230     /**
1231      * @param artifact  Android dependency that is being referenced.
1232      * @return Folder where the unpacked native libraries are located.
1233      */
1234     public final File getUnpackedLibNativesFolder( Artifact artifact )
1235     {
1236         return getUnpackedLibHelper().getUnpackedLibNativesFolder( artifact );
1237     }
1238 
1239     // TODO Replace this with a non-static method (could even replace it with one of the methods above).
1240     public static File getLibraryUnpackDirectory( File unpackedApkLibsDirectory, Artifact artifact )
1241     {
1242         return new File( unpackedApkLibsDirectory.getAbsolutePath(), artifact.getArtifactId() );
1243     }
1244 
1245     /**
1246      * <p>Returns the Android NDK to use.</p>
1247      * 
1248      * <p>Current implementation looks for <code>&lt;ndk&gt;&lt;path&gt;</code> configuration in pom, then System
1249      * property <code>android.ndk.path</code>, then environment variable <code>ANDROID_NDK_HOME</code>.
1250      * 
1251      * <p>This is where we collect all logic for how to lookup where it is, and which one to choose. The lookup is
1252      * based on available parameters. This method should be the only one you should need to look at to understand how
1253      * the Android NDK is chosen, and from where on disk.</p>
1254      *
1255      * @return the Android NDK to use.
1256      * @throws org.apache.maven.plugin.MojoExecutionException
1257      *          if no Android NDK path configuration is available at all.
1258      */
1259     protected AndroidNdk getAndroidNdk() throws MojoExecutionException
1260     {
1261         File chosenNdkPath;
1262         // There is no <ndk> tag in the pom.
1263         if ( ndkPath != null )
1264         {
1265             // -Dandroid.ndk.path is set on command line, or via <properties><ndk.path>...
1266             chosenNdkPath = ndkPath;
1267         }
1268         else if ( ndk != null && ndk.getPath() != null )
1269         {
1270             chosenNdkPath = ndk.getPath();
1271         }
1272         else
1273         {
1274             // No -Dandroid.ndk.path is set on command line, or via <properties><ndk.path>...
1275             chosenNdkPath = new File( getAndroidNdkHomeOrThrow() );
1276         }
1277         return new AndroidNdk( chosenNdkPath );
1278     }
1279 
1280 
1281     private String getAndroidNdkHomeOrThrow() throws MojoExecutionException
1282     {
1283         final String androidHome = System.getenv( ENV_ANDROID_NDK_HOME );
1284         if ( isBlank( androidHome ) )
1285         {
1286             throw new MojoExecutionException( "No Android NDK path could be found. You may configure it in the pom "
1287                     + "using <ndk><path>...</path></ndk> or <properties><ndk.path>...</ndk.path></properties> or on "
1288                     + "command-line using -Dandroid.ndk.path=... or by setting environment variable "
1289                     + ENV_ANDROID_NDK_HOME );
1290         }
1291         return androidHome;
1292     }
1293 
1294     /**
1295      * @return the resource directories if defined or the resource directory.
1296      */
1297     public File[] getResourceOverlayDirectories()
1298     {
1299         File[] overlayDirectories;
1300 
1301         if ( resourceOverlayDirectories == null || resourceOverlayDirectories.length == 0 )
1302         {
1303             overlayDirectories = new File[]{ resourceOverlayDirectory };
1304         }
1305         else
1306         {
1307             overlayDirectories = resourceOverlayDirectories;
1308         }
1309 
1310         return overlayDirectories;
1311     }
1312 
1313     private Set<String> getDevices()
1314     {
1315         Set<String> list = new HashSet<String>();
1316 
1317         if ( StringUtils.isNotBlank( device ) )
1318         {
1319             list.add( device );
1320         }
1321 
1322         list.addAll( Arrays.asList( devices ) );
1323 
1324         list.addAll( Arrays.asList( ips ) );
1325 
1326         return list;
1327     }
1328     
1329     private int getDeviceThreads()
1330     {
1331         return deviceThreads;
1332     }
1333 
1334     private abstract class DoThread extends Thread
1335     {
1336         private MojoFailureException failure;
1337         private MojoExecutionException execution;
1338 
1339         public final void run()
1340         {
1341             try
1342             {
1343                 runDo();
1344             }
1345             catch ( MojoFailureException e )
1346             {
1347                 failure = e;
1348             }
1349             catch ( MojoExecutionException e )
1350             {
1351                 execution = e;
1352             }
1353         }
1354 
1355         protected abstract void runDo() throws MojoFailureException, MojoExecutionException;
1356     }
1357 
1358     /**
1359      * @return True if this project constructs an APK as opposed to an AAR or APKLIB.
1360      */
1361     protected final boolean isAPKBuild()
1362     {
1363         return getUnpackedLibHelper().isAPKBuild( project );
1364     }
1365 
1366     /**
1367      * Copies the files contained within the source folder to the target folder.
1368      * <p>
1369      * The the target folder doesn't exist it will be created.
1370      * </p>
1371      *
1372      * @param sourceFolder      Folder from which to copy the resources.
1373      * @param targetFolder      Folder to which to copy the files.
1374      * @throws MojoExecutionException if the files cannot be copied.
1375      */
1376     protected final void copyFolder( File sourceFolder, File targetFolder ) throws MojoExecutionException
1377     {
1378         copyFolder( sourceFolder, targetFolder, TrueFileFilter.TRUE );
1379     }
1380 
1381     private void copyFolder( File sourceFolder, File targetFolder, FileFilter filter ) throws MojoExecutionException
1382     {
1383         if ( !sourceFolder.exists() )
1384         {
1385             return;
1386         }
1387 
1388         try
1389         {
1390             getLog().debug( "Copying " + sourceFolder + " to " + targetFolder );
1391             if ( ! targetFolder.exists() )
1392             {
1393                 if ( ! targetFolder.mkdirs() )
1394                 {
1395                     throw new MojoExecutionException( "Could not create target directory " + targetFolder );
1396                 }
1397             }
1398             FileUtils.copyDirectory( sourceFolder, targetFolder, filter );
1399         }
1400         catch ( IOException e )
1401         {
1402             throw new MojoExecutionException( "Could not copy source folder to target folder", e );
1403         }
1404 
1405     }
1406 
1407     protected final UnpackedLibHelper getUnpackedLibHelper()
1408     {
1409         if ( unpackedLibHelper == null )
1410         {
1411             unpackedLibHelper = new UnpackedLibHelper(
1412                 getArtifactResolverHelper(),
1413                 project,
1414                 new MavenToPlexusLogAdapter( getLog() ),
1415                 unpackedLibsFolder
1416             );
1417         }
1418         return unpackedLibHelper;
1419     }
1420 
1421     protected final ArtifactResolverHelper getArtifactResolverHelper()
1422     {
1423         if ( artifactResolverHelper == null )
1424         {
1425             artifactResolverHelper = new ArtifactResolverHelper(
1426                     artifactResolver,
1427                     new MavenToPlexusLogAdapter( getLog() ),
1428                     project.getRemoteArtifactRepositories()
1429             );
1430         }
1431         return artifactResolverHelper;
1432     }
1433 
1434     protected final NativeHelper getNativeHelper()
1435     {
1436         if ( nativeHelper == null )
1437         {
1438             nativeHelper = new NativeHelper( project, dependencyGraphBuilder, getLog() );
1439         }
1440         return nativeHelper;
1441     }
1442 }