View Javadoc
1   /*
2    * Copyright (C) 2009 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.phase01generatesources;
18  
19  import com.simpligility.maven.plugins.android.AbstractAndroidMojo;
20  import com.simpligility.maven.plugins.android.CommandExecutor;
21  import com.simpligility.maven.plugins.android.ExecutionException;
22  import com.simpligility.maven.plugins.android.common.AaptCommandBuilder;
23  import com.simpligility.maven.plugins.android.common.AaptCommandBuilder.AaptPackageCommandBuilder;
24  import com.simpligility.maven.plugins.android.common.DependencyResolver;
25  import com.simpligility.maven.plugins.android.common.FileRetriever;
26  import com.simpligility.maven.plugins.android.configuration.BuildConfigConstant;
27  import org.apache.commons.io.FileUtils;
28  import org.apache.commons.io.IOUtils;
29  import org.apache.commons.lang3.StringUtils;
30  import org.apache.maven.artifact.Artifact;
31  import org.apache.maven.artifact.DependencyResolutionRequiredException;
32  import org.apache.maven.plugin.MojoExecutionException;
33  import org.apache.maven.plugin.MojoFailureException;
34  import org.apache.maven.plugins.annotations.Component;
35  import org.apache.maven.plugins.annotations.LifecyclePhase;
36  import org.apache.maven.plugins.annotations.Mojo;
37  import org.apache.maven.plugins.annotations.Parameter;
38  import org.apache.maven.plugins.annotations.ResolutionScope;
39  import org.apache.maven.repository.RepositorySystem;
40  import org.codehaus.plexus.archiver.ArchiverException;
41  import org.codehaus.plexus.archiver.UnArchiver;
42  import org.codehaus.plexus.archiver.zip.ZipUnArchiver;
43  import org.codehaus.plexus.logging.Logger;
44  import org.codehaus.plexus.logging.console.ConsoleLogger;
45  import org.w3c.dom.Document;
46  
47  import javax.xml.parsers.DocumentBuilder;
48  import javax.xml.parsers.DocumentBuilderFactory;
49  import javax.xml.transform.OutputKeys;
50  import javax.xml.transform.Result;
51  import javax.xml.transform.Source;
52  import javax.xml.transform.Transformer;
53  import javax.xml.transform.TransformerFactory;
54  import javax.xml.transform.dom.DOMSource;
55  import javax.xml.transform.stream.StreamResult;
56  import java.io.File;
57  import java.io.FileWriter;
58  import java.io.IOException;
59  import java.util.ArrayList;
60  import java.util.Arrays;
61  import java.util.Collection;
62  import java.util.HashMap;
63  import java.util.HashSet;
64  import java.util.List;
65  import java.util.Map;
66  import java.util.Set;
67  import java.util.jar.JarEntry;
68  import java.util.jar.JarFile;
69  
70  import static com.simpligility.maven.plugins.android.common.AndroidExtension.AAR;
71  import static com.simpligility.maven.plugins.android.common.AndroidExtension.APK;
72  import static com.simpligility.maven.plugins.android.common.AndroidExtension.APKLIB;
73  import static com.simpligility.maven.plugins.android.common.AndroidExtension.APKSOURCES;
74  
75  /**
76   * Generates <code>R.java</code> based on resources specified by the <code>resources</code> configuration parameter.
77   * Generates java files based on aidl files.
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   */
84  @Mojo(
85          name = "generate-sources",
86          defaultPhase = LifecyclePhase.GENERATE_SOURCES,
87          requiresDependencyResolution = ResolutionScope.COMPILE
88  )
89  public class GenerateSourcesMojo extends AbstractAndroidMojo
90  {
91  
92      /**
93       * <p>Whether to produce a warning if there is an aar dependency that has an apklib artifact in its dependency
94       * tree. The combination of aar library including or depending on an apklib has been deprecated and may not be
95       * supported by future plugin versions. Traversing the dependency graph is done for all project dependencies
96       * present in build classpath.</p>
97       * 
98       * <p>It is recommended to keep this set to <code>true</code> to catch possible issues as soon as possible.</p>
99       */
100     @Parameter( defaultValue = "true" )
101     protected boolean warnOnApklibDependencies;
102 
103     /**
104      * Whether to fail the build if one of the dependencies and/or the project duplicate a layout file.
105      * <p>
106      * Such a scenario generally means that the build will fail with a compilation error due to missing resource files.
107      * This is because any Ids contained in the duplicate layout files can never be generated by aapt, as appt
108      * only picks up the first resource file it finds in its path.
109      */
110     @Parameter( defaultValue = "true" )
111     private boolean failOnConflictingLayouts;
112 
113     /**
114      * Whether to fail the build if one of the dependencies and/or the project have similar package in the
115      * AndroidManifest.
116      * <p>
117      * Such scenario generally means that the build will fail with a compilation error due to
118      * missing resources in the R file generated.
119      */
120     @Parameter( defaultValue = "true" )
121     private boolean failOnDuplicatePackages;
122 
123     /**
124      * Override default generated folder containing aidl classes
125      */
126     @Parameter(
127             property = "android.genDirectoryAidl",
128             defaultValue = "${project.build.directory}/generated-sources/aidl"
129     )
130     protected File genDirectoryAidl;
131 
132     /**
133      * The directory containing the aidl files.
134      */
135     @Parameter( property = "android.aidlSourceDirectory", defaultValue = "${project.basedir}/src/main/aidl" )
136     protected File aidlSourceDirectory;
137 
138     /**
139      * <p>Parameter designed to generate custom BuildConfig constants
140      */
141     @Parameter( property = "android.buildConfigConstants" )
142     protected BuildConfigConstant[] buildConfigConstants;
143 
144     /**
145      */
146     @Component
147     private RepositorySystem repositorySystem;
148 
149     /**
150      * Pre AMP-4 AndroidManifest file.
151      */
152     @Parameter( readonly = true, defaultValue = "${project.basedir}/AndroidManifest.xml" )
153     private File androidManifestFilePre4;
154 
155     /**
156      * Pre AMP-4 android resources folder.
157      */
158     @Parameter( readonly = true, defaultValue = "${project.basedir}/res" )
159     private File resourceDirectoryPre4;
160 
161     /**
162      * Pre AMP-4 android assets directory.
163      */
164     @Parameter( readonly = true, defaultValue = "${project.basedir}/assets" )
165     private File assetsDirectoryPre4;
166 
167     /**
168      * Pre AMP-4 native libraries folder.
169      */
170     @Parameter( readonly = true, defaultValue = "${project.basedir}/libs" )
171     private File nativeLibrariesDirectoryPre4;
172 
173     /**
174      * If any non-standard files/folders exist and have NOT been explicitly configured then fail the build.
175      */
176     @Parameter( defaultValue = "true" )
177     private boolean failOnNonStandardStructure;
178 
179     /**
180      * Which dependency scopes should not be included when unpacking dependencies
181      */
182     protected static final List<String> EXCLUDED_DEPENDENCY_SCOPES_FOR_EXTRACTION = Arrays.asList(
183             Artifact.SCOPE_SYSTEM, Artifact.SCOPE_IMPORT
184     );
185 
186     /**
187      * Generates the sources.
188      *
189      * @throws MojoExecutionException if it fails.
190      * @throws MojoFailureException   if it fails.
191      */
192     public void execute() throws MojoExecutionException, MojoFailureException
193     {
194         // If the current POM isn't an Android-related POM, then don't do
195         // anything. This helps work with multi-module projects.
196         if ( !isCurrentProjectAndroid() )
197         {
198             return;
199         }
200 
201         validateStandardLocations();
202 
203         try
204         {
205             targetDirectory.mkdirs();
206             copyManifest();
207 
208             // Check for a deprecated AAR > APKLIB artifact combination
209             if ( warnOnApklibDependencies )
210             {
211                 checkForApklibDependencies();
212             }
213 
214             // TODO Do we really want to continue supporting APKSOURCES? How long has it been deprecated
215             extractSourceDependencies();
216 
217             // Extract the apklib and aar dependencies into unpacked-libs so that they can be referenced in the build.
218             extractLibraryDependencies();
219 
220             // Copy project assets to combinedAssets so that aapt has a single assets folder to load.
221             copyFolder( assetsDirectory, combinedAssets );
222 
223             final String[] relativeAidlFileNames1 = findRelativeAidlFileNames( aidlSourceDirectory );
224             final String[] relativeAidlFileNames2 = findRelativeAidlFileNames( extractedDependenciesJavaSources );
225             final Map<String, String[]> relativeApklibAidlFileNames = new HashMap<String, String[]>();
226 
227             if ( !isInstrumentationTest() )
228             {
229                 // Only add transitive APKLIB deps if we are building an APK and not an instrumentation test apk.
230                 for ( Artifact artifact : getTransitiveDependencyArtifacts( APKLIB ) )
231                 {
232                     final File libSourceFolder = getUnpackedApkLibSourceFolder( artifact );
233                     final String[] apklibAidlFiles = findRelativeAidlFileNames( libSourceFolder );
234                     relativeApklibAidlFileNames.put( artifact.getId(), apklibAidlFiles );
235                 }
236             }
237 
238             checkPackagesForDuplicates();
239             checkForConflictingLayouts();
240             generateR();
241             generateBuildConfig();
242 
243             // When compiling AIDL for this project,
244             // make sure we compile AIDL for dependencies as well.
245             // This is so project A, which depends on project B, can
246             // use AIDL info from project B in its own AIDL
247             final Map<File, String[]> files = new HashMap<File, String[]>();
248             files.put( aidlSourceDirectory, relativeAidlFileNames1 );
249             files.put( extractedDependenciesJavaSources, relativeAidlFileNames2 );
250 
251             if ( !isInstrumentationTest() )
252             {
253                 // Only add transitive APKLIB deps if we are building an APK and not an instrumentation test apk.
254                 for ( Artifact artifact : getTransitiveDependencyArtifacts( APKLIB ) )
255                 {
256                     final File unpackedLibSourceFolder = getUnpackedApkLibSourceFolder( artifact );
257                     files.put( unpackedLibSourceFolder, relativeApklibAidlFileNames.get( artifact.getId() ) );
258                 }
259             }
260             generateAidlFiles( files );
261         }
262         catch ( MojoExecutionException e )
263         {
264             getLog().error( "Error when generating sources.", e );
265             throw e;
266         }
267     }
268 
269     /**
270      * Performs some level of validation on the configured files and folders in light of the change
271      * to AndroidStudio/Gradle style folder structures instead of the original Ant/Eclipse structures.
272      * <p>
273      * Essentially we will be noisy if we see an artifact that looks like before
274      * Android Maven Plugin 4 and is not explicitly configured.
275      */
276     private void validateStandardLocations() throws MojoExecutionException
277     {
278         boolean hasNonStandardStructure = false;
279         if ( androidManifestFilePre4.exists() && !androidManifestFilePre4.equals( androidManifestFile ) )
280         {
281             getLog().warn( "Non-standard location of AndroidManifest.xml file found, but not configured:\n "
282                     + androidManifestFilePre4 + "\nMove to the standard location src/main/AndroidManifest.xml\n"
283                     + "Or configure androidManifestFile. \n" );
284             hasNonStandardStructure = true;
285         }
286         if ( resourceDirectoryPre4.exists() && !resourceDirectoryPre4.equals( resourceDirectory ) )
287         {
288             getLog().warn( "Non-standard location of Android res folder found, but not configured:\n "
289                     + resourceDirectoryPre4 + "\nMove to the standard location src/main/res/\n"
290                     + "Or configure resourceDirectory. \n" );
291             hasNonStandardStructure = true;
292         }
293         if ( assetsDirectoryPre4.exists() && !assetsDirectoryPre4.equals( assetsDirectory ) )
294         {
295             getLog().warn( "Non-standard location assets folder found, but not configured:\n "
296                     + assetsDirectoryPre4 + "\nMove to the standard location src/main/assets/\n"
297                     + "Or configure assetsDirectory. \n" );
298             hasNonStandardStructure = true;
299         }
300         if ( nativeLibrariesDirectoryPre4.exists() && !nativeLibrariesDirectoryPre4.equals( nativeLibrariesDirectory ) )
301         {
302             getLog().warn( "Non-standard location native libs folder found, but not configured:\n "
303                     + nativeLibrariesDirectoryPre4 + "\nMove to the standard location src/main/libs/\n"
304                     + "Or configure nativeLibrariesDirectory. \n" );
305             hasNonStandardStructure = true;
306         }
307 
308         if ( hasNonStandardStructure && failOnNonStandardStructure )
309         {
310             throw new MojoExecutionException(
311                     "\n\nFound files or folders in non-standard locations in the project!\n"
312                             + "....This might be a side-effect of a migration to Android Maven Plugin 4+.\n"
313                             + "....Please observe the warnings for specific files and folders above.\n"
314                             + "....Ideally you should restructure your project.\n"
315                             + "....Alternatively add explicit configuration overrides for files or folders.\n"
316                             + "....Finally you could set failOnNonStandardStructure to false, potentially "
317                             + "resulting in other failures.\n\n\n"
318             );
319         }
320     }
321 
322     /**
323      * Copy the AndroidManifest.xml from androidManifestFile to destinationManifestFile
324      *
325      * @throws MojoExecutionException
326      */
327     protected void copyManifest() throws MojoExecutionException
328     {
329         getLog().debug( "copyManifest: " + androidManifestFile + " -> " + destinationManifestFile );
330         if ( androidManifestFile == null )
331         {
332             getLog().debug( "Manifest copying disabled. Using default manifest only" );
333             return;
334         }
335 
336         try
337         {
338             DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
339             DocumentBuilder db = dbf.newDocumentBuilder();
340             Document doc = db.parse( androidManifestFile );
341             Source source = new DOMSource( doc );
342 
343             TransformerFactory xfactory = TransformerFactory.newInstance();
344             Transformer xformer = xfactory.newTransformer();
345             xformer.setOutputProperty( OutputKeys.OMIT_XML_DECLARATION, "yes" );
346 
347             FileWriter writer = null;
348             try
349             {
350                 destinationManifestFile.getParentFile().mkdirs();
351 
352                 writer = new FileWriter( destinationManifestFile, false );
353                 if ( doc.getXmlEncoding() != null && doc.getXmlVersion() != null )
354                 {
355                     String xmldecl = String.format( "<?xml version=\"%s\" encoding=\"%s\"?>%n",
356                             doc.getXmlVersion(), doc.getXmlEncoding() );
357 
358                     writer.write( xmldecl );
359                 }
360                 Result result = new StreamResult( writer );
361                 xformer.transform( source, result );
362                 getLog().info( "Manifest copied from " + androidManifestFile + " to " + destinationManifestFile );
363             }
364             finally
365             {
366                 IOUtils.closeQuietly( writer );
367             }
368         }
369         catch ( Exception e )
370         {
371             getLog().error( "Error during copyManifest" );
372             throw new MojoExecutionException( "Error during copyManifest", e );
373         }
374     }
375 
376     /**
377      * Extract the source dependencies.
378      *
379      * @throws MojoExecutionException if it fails.
380      */
381     protected void extractSourceDependencies() throws MojoExecutionException
382     {
383         for ( Artifact artifact : getDirectDependencyArtifacts() )
384         {
385             String type = artifact.getType();
386             if ( type.equals( APKSOURCES ) )
387             {
388                 getLog().debug( "Detected apksources dependency " + artifact + " with file " + artifact.getFile()
389                         + ". Will resolve and extract..." );
390 
391                 final File apksourcesFile = resolveArtifactToFile( artifact );
392                 getLog().debug( "Extracting " + apksourcesFile + "..." );
393                 extractApksources( apksourcesFile );
394             }
395         }
396 
397         if ( extractedDependenciesJavaResources.exists() )
398         {
399             projectHelper.addResource( project, extractedDependenciesJavaResources.getAbsolutePath(), null, null );
400             project.addCompileSourceRoot( extractedDependenciesJavaSources.getAbsolutePath() );
401         }
402     }
403 
404     /**
405      * @deprecated Support <code>APKSOURCES</code> artifacts has been deprecated. Use APKLIB instead.
406      */
407     @Deprecated
408     private void extractApksources( File apksourcesFile ) throws MojoExecutionException
409     {
410         if ( apksourcesFile.isDirectory() )
411         {
412             getLog().warn( "The apksources artifact points to '" + apksourcesFile
413                     + "' which is a directory; skipping unpacking it." );
414             return;
415         }
416         final UnArchiver unArchiver = new ZipUnArchiver( apksourcesFile )
417         {
418             @Override
419             protected Logger getLogger()
420             {
421                 return new ConsoleLogger( Logger.LEVEL_DEBUG, "dependencies-unarchiver" );
422             }
423         };
424         extractedDependenciesDirectory.mkdirs();
425         unArchiver.setDestDirectory( extractedDependenciesDirectory );
426         try
427         {
428             unArchiver.extract();
429         }
430         catch ( ArchiverException e )
431         {
432             throw new MojoExecutionException( "ArchiverException while extracting " + apksourcesFile.getAbsolutePath()
433                     + ". Message: " + e.getLocalizedMessage(), e );
434         }
435     }
436 
437     private void extractLibraryDependencies() throws MojoExecutionException
438     {
439         final Collection<Artifact> artifacts = getTransitiveDependencyArtifacts(
440                 EXCLUDED_DEPENDENCY_SCOPES_FOR_EXTRACTION );
441 
442         getLog().info( "Extracting libs" );
443 
444         // The only library dependencies we want included in an instrumentation test are APK and AARs.
445         // Any APKLIB classes have already been compiled into the APK.
446         final boolean instrumentationTest = isInstrumentationTest();
447 
448         for ( Artifact artifact : artifacts )
449         {
450             final String type = artifact.getType();
451             if ( type.equals( APKLIB ) && !instrumentationTest )
452             {
453                 getLog().info( "Extracting apklib " + artifact.getArtifactId() + "..." );
454                 extractApklib( artifact );
455             }
456             else if ( type.equals( AAR ) )
457             {
458                 getLog().info( "Extracting aar " + artifact.getArtifactId() + "..." );
459                 extractAarLib( artifact );
460             }
461             else if ( type.equals( APK ) )
462             {
463                 getLog().info( "Extracting apk " + artifact.getArtifactId() + "..." );
464                 extractApkClassesJar( artifact );
465             }
466             else
467             {
468                 getLog().debug( "Not extracting " + artifact.getArtifactId() + "..." );
469             }
470         }
471     }
472 
473     /**
474      * Extracts ApkLib and adds the assets and apklib sources and resources to the build.
475      */
476     private void extractApklib( Artifact apklibArtifact ) throws MojoExecutionException
477     {
478         getUnpackedLibHelper().extractApklib( apklibArtifact );
479 
480         // Copy the assets to the the combinedAssets folder.
481         // Add the apklib source and resource to the compile.
482         // NB apklib sources are added to compileSourceRoot because we may need to compile against them.
483         //    This means the apklib classes will be compiled into target/classes and packaged with this build.
484         copyFolder( getUnpackedLibAssetsFolder( apklibArtifact ), combinedAssets );
485 
486         final File apklibSourceFolder = getUnpackedApkLibSourceFolder( apklibArtifact );
487         final List<String> resourceExclusions = Arrays.asList( "**/*.java", "**/*.aidl" );
488         projectHelper.addResource( project, apklibSourceFolder.getAbsolutePath(), null, resourceExclusions );
489         project.addCompileSourceRoot( apklibSourceFolder.getAbsolutePath() );
490     }
491 
492     /**
493      * Extracts AarLib and if this is an APK build then adds the assets and resources to the build.
494      */
495     private void extractAarLib( Artifact aarArtifact ) throws MojoExecutionException
496     {
497         getUnpackedLibHelper().extractAarLib( aarArtifact );
498 
499         // Copy the assets to the the combinedAssets folder, but only if an APK build.
500         // Ie we only want to package assets that we own.
501         // Assets should only live within their owners or the final APK.
502         if ( isAPKBuild() )
503         {
504             copyFolder( getUnpackedLibAssetsFolder( aarArtifact ), combinedAssets );
505         }
506 
507         // Aar lib resources should only be included if we are building an apk.
508         // So we need to extract them into a folder that we then add to the resource classpath.
509         if ( isAPKBuild() )
510         {
511             // (If we are including SYSTEM_SCOPE artifacts when considering resources for APK packaging)
512             // then adding the AAR resources to the project would have them added twice.
513             getLog().debug( "Not adding AAR resources to resource classpath : " + aarArtifact );
514         }
515     }
516 
517     /**
518      * Copies a dependent APK jar over the top of the placeholder created for it in AarMavenLifeCycleParticipant.
519      * <p>
520      * This is necessary because we need the classes of the APK added to the compile classpath.
521      * NB APK dependencies are uncommon as they should really only be used in a project that tests an apk.
522      *
523      * @param artifact APK dependency for this project whose classes will be copied over.
524      */
525     private void extractApkClassesJar( Artifact artifact ) throws MojoExecutionException
526     {
527         final File apkClassesJar = getUnpackedLibHelper().getJarFileForApk( artifact );
528         final File unpackedClassesJar = getUnpackedLibHelper().getUnpackedClassesJar( artifact );
529         try
530         {
531             FileUtils.copyFile( apkClassesJar, unpackedClassesJar );
532         }
533         catch ( IOException e )
534         {
535             throw new MojoExecutionException(
536                     "Could not copy APK classes jar " + apkClassesJar + " to " + unpackedClassesJar, e );
537         }
538         getLog().debug( "Copied APK classes jar into compile classpath : " + unpackedClassesJar );
539 
540     }
541 
542     /**
543      * Traverses the list of project dependencies looking for &quot;AAR depends on APKLIB&quot; artifact combination
544      * that has been deprecated. For each occurrence of an AAR artifact with APKLIB direct or transitive dependency,
545      * produces a warning message to inform the user. Future plugin versions may default to skipping or not handling
546      * unsupported artifacts during build lifecycle.
547      *
548      * @throws MojoExecutionException
549      */
550     private void checkForApklibDependencies() throws MojoExecutionException
551     {
552         final boolean isAarBuild = project.getPackaging().equals( AAR );
553 
554         final DependencyResolver dependencyResolver = getDependencyResolver();
555 
556         final Set<Artifact> allArtifacts = project.getArtifacts();
557         Set<Artifact> dependencyArtifacts = getArtifactResolverHelper().getFilteredArtifacts( allArtifacts );
558 
559         boolean foundApklib = false;
560 
561         for ( Artifact artifact : dependencyArtifacts )
562         {
563             final String type = artifact.getType();
564             if ( type.equals( APKLIB ) && isAarBuild )
565             {
566                 getLog().warn( "Detected APKLIB transitive dependency: " + artifact.getId() );
567                 foundApklib = true;
568             }
569             else if ( type.equals( AAR ) )
570             {
571                 final Set<Artifact> dependencies = dependencyResolver
572                         .getLibraryDependenciesFor( session, repositorySystem, artifact );
573                 for ( Artifact dependency : dependencies )
574                 {
575                     if ( dependency.getType().equals( APKLIB ) )
576                     {
577                         getLog().warn( "Detected " + artifact.getId() + " that depends on APKLIB: "
578                                 + dependency.getId() );
579                         foundApklib = true;
580                     }
581                 }
582             }
583         }
584 
585         if ( foundApklib )
586         {
587             getLog().warn( "AAR libraries should not depend or include APKLIB artifacts.\n"
588                     + "APKLIBs have been deprecated and the combination of the two may yield unexpected results.\n"
589                     + "Check the problematic AAR libraries for newer versions that use AAR packaging." );
590         }
591     }
592 
593     /**
594      * Checks packages in AndroidManifest.xml file of project and all dependent libraries for duplicates.
595      * <p>Generate warning if duplicates presents.
596      * <p>(in case of packages similarity R.java and BuildConfig files will be overridden)
597      *
598      * @throws MojoExecutionException
599      */
600     private void checkPackagesForDuplicates() throws MojoExecutionException
601     {
602         Set<Artifact> dependencyArtifacts = getTransitiveDependencyArtifacts( AAR, APKLIB );
603 
604         if ( dependencyArtifacts.isEmpty() )
605         {
606             //if no AAR or APKLIB dependencies presents than only one package presents
607             return;
608         }
609 
610         Map<String, Set<Artifact>> packageCompareMap = getPackageCompareMap( dependencyArtifacts );
611 
612         List<String> duplicatesMessageList = new ArrayList<String>();
613         for ( Map.Entry<String, Set<Artifact>> entry : packageCompareMap.entrySet() )
614         {
615             Set<Artifact> artifacts = entry.getValue();
616             if ( artifacts != null && artifacts.size() > 1 )
617             {
618                 StringBuilder messageBuilder = new StringBuilder();
619                 for ( Artifact item : artifacts )
620                 {
621                     messageBuilder
622                             .append( messageBuilder.length() > 0 ? ", " : "    [" )
623                             .append( item.getArtifactId() );
624                 }
625                 messageBuilder
626                         .append( "] have similar package='" )
627                         .append( entry.getKey() )
628                         .append( "'" );
629                 duplicatesMessageList.add( messageBuilder.toString() );
630             }
631         }
632         if ( !duplicatesMessageList.isEmpty() )
633         {
634             List<String> messageList = new ArrayList<String>();
635             messageList.add( "" );
636             messageList.add( "Duplicate packages detected in AndroidManifest.xml files" );
637             messageList.add( "" );
638             messageList.add( "Such scenario generally means that the build will fail with a compilation error due to"
639                     + " missing resources in R file." );
640             messageList.add( "You should consider renaming some of the duplicate packages listed below"
641                     + " to avoid the conflict." );
642             messageList.add( "" );
643             messageList.add( "Conflicting artifacts:" );
644             messageList.addAll( duplicatesMessageList );
645             messageList.add( "" );
646 
647             if ( failOnDuplicatePackages )
648             {
649                 StringBuilder builder = new StringBuilder();
650                 for ( String line : messageList )
651                 {
652                     builder.append( line );
653                     builder.append( "\n" );
654                 }
655                 builder.append( "\n" );
656                 builder.append( "You can downgrade the failure to a warning " );
657                 builder.append( "by setting the 'failOnDuplicatePackages' plugin property to false." );
658                 throw new MojoExecutionException( builder.toString() );
659             }
660             for ( String messageLine : messageList )
661             {
662                 getLog().warn( messageLine );
663             }
664         }
665     }
666 
667     /**
668      * Looks for dependencies that have a layout file with the same name as either one of the project layout files
669      * or one of the other dependencies. If such a duplicate occurs then it will fail the build or generate a warning
670      * depending upon the value of the <strong>failOnConflictingLayouts</strong> build parameter.
671      *
672      * @throws MojoExecutionException
673      */
674     private void checkForConflictingLayouts() throws MojoExecutionException
675     {
676         final ConflictingLayoutDetector detector = new ConflictingLayoutDetector();
677 
678         // Add layout files for this project
679         final FileRetriever retriever = new FileRetriever( "layout*/*.xml" );
680         detector.addLayoutFiles( getAndroidManifestPackageName(), retriever.getFileNames( resourceDirectory ) );
681 
682         // Add layout files for all dependencies.
683         for ( final Artifact dependency : getTransitiveDependencyArtifacts( AAR, APKLIB ) )
684         {
685             final String packageName = extractPackageNameFromAndroidArtifact( dependency );
686             final String[] layoutFiles = retriever.getFileNames( getUnpackedLibResourceFolder( dependency ) );
687             detector.addLayoutFiles( packageName, layoutFiles );
688         }
689 
690         final Collection<ConflictingLayout> conflictingLayouts = detector.getConflictingLayouts();
691         getLog().debug( "checkConflictingLayouts - conflicts : " + conflictingLayouts );
692         if ( !conflictingLayouts.isEmpty() )
693         {
694             final List<String> sb = new ArrayList<String>();
695             sb.add( "" );
696             sb.add( "" );
697             sb.add( "Duplicate layout files have been detected across more than one Android package." );
698             sb.add( "" );
699             sb.add( "Such a scenario generally means that the build will fail with a compilation error due to" );
700             sb.add( "missing resource files. You should consider renaming some of the layout files listed below" );
701             sb.add( "to avoid the conflict." );
702             sb.add( "" );
703             sb.add( "However, if you believe you know better, then you can downgrade the failure to a warning" );
704             sb.add( "by setting the failOnConflictingLayouts plugin property to false." );
705             sb.add( "But you really don't want to do that." );
706             sb.add( "" );
707             sb.add( "Conflicting Layouts:" );
708             for ( final ConflictingLayout layout : conflictingLayouts )
709             {
710                 sb.add( "    " + layout.getLayoutFileName() + "  packages=" + layout.getPackageNames().toString() );
711             }
712             sb.add( "" );
713 
714             if ( failOnConflictingLayouts )
715             {
716                 final StringBuilder builder = new StringBuilder();
717                 for ( final String line : sb )
718                 {
719                     builder.append( line );
720                     builder.append( "\n" );
721                 }
722                 throw new MojoExecutionException( builder.toString() );
723             }
724 
725             for ( final String line : sb )
726             {
727                 getLog().warn( line );
728             }
729         }
730     }
731 
732     /**
733      * Provides map with all provided dependencies or project itself grouped by package name
734      *
735      * @param dependencyArtifacts artifacts that should be grouped by package name
736      * @return map of with package names(String) and sets of artifacts (Set<Artifact>)
737      * that have similar package names
738      * @throws MojoExecutionException
739      */
740     Map<String, Set<Artifact>> getPackageCompareMap( Set<Artifact> dependencyArtifacts ) throws MojoExecutionException
741     {
742         if ( dependencyArtifacts == null )
743         {
744             throw new IllegalArgumentException( "dependencies must be initialized" );
745         }
746 
747         Map<String, Set<Artifact>> packageCompareMap = new HashMap<String, Set<Artifact>>();
748 
749         Set<Artifact> artifactSet = new HashSet<Artifact>();
750         artifactSet.add( project.getArtifact() );
751         packageCompareMap.put( getAndroidManifestPackageName(), artifactSet );
752 
753         for ( Artifact artifact : dependencyArtifacts )
754         {
755             String libPackage = extractPackageNameFromAndroidArtifact( artifact );
756 
757             Set<Artifact> artifacts = packageCompareMap.get( libPackage );
758             if ( artifacts == null )
759             {
760                 artifacts = new HashSet<Artifact>();
761                 packageCompareMap.put( libPackage, artifacts );
762             }
763             artifacts.add( artifact );
764         }
765         return packageCompareMap;
766     }
767 
768     private void generateR() throws MojoExecutionException
769     {
770         getLog().info( "Generating R file for " + project.getArtifact() );
771 
772         genDirectory.mkdirs();
773 
774         final AaptPackageCommandBuilder commandBuilder = AaptCommandBuilder
775                 .packageResources( getLog() )
776                 .makePackageDirectories()
777                 .setResourceConstantsFolder( genDirectory )
778                 .forceOverwriteExistingFiles()
779                 .disablePngCrunching()
780                 .generateRIntoPackage( customPackage )
781                 .setPathToAndroidManifest( destinationManifestFile )
782                 .addResourceDirectoriesIfExists( getResourceOverlayDirectories() )
783                 .addResourceDirectoryIfExists( resourceDirectory )
784                 // Need to include any AAR or APKLIB dependencies when generating R because if any local
785                 // resources directly reference dependent resources then R generation will crash.
786                 .addResourceDirectoriesIfExists( getLibraryResourceFolders() )
787                 .autoAddOverlay()
788                 .addRawAssetsDirectoryIfExists( combinedAssets )
789                 .addExistingPackageToBaseIncludeSet( getAndroidSdk().getAndroidJar() )
790                 .addConfigurations( configurations )
791                 .setVerbose( aaptVerbose )
792                 // We need to generate R.txt for all projects as it needs to be consumed when generating R class.
793                 // It also needs to be consumed when packaging aar.
794                 .generateRTextFile( targetDirectory )
795                 // If a proguard file is defined then output Proguard options to it.
796                 .setProguardOptionsOutputFile( proguardFile )
797                 .makeResourcesNonConstant( AAR.equals( project.getArtifact().getType() ) )
798                 .addExtraArguments( aaptExtraArgs );
799 
800         getLog().debug( getAndroidSdk().getAaptPath() + " " + commandBuilder.toString() );
801         try
802         {
803             final CommandExecutor executor = CommandExecutor.Factory.createDefaultCommmandExecutor();
804             executor.setLogger( getLog() );
805             executor.setCaptureStdOut( true );
806             final List<String> commands = commandBuilder.build();
807             executor.executeCommand( getAndroidSdk().getAaptPath(), commands, project.getBasedir(), false );
808         }
809         catch ( ExecutionException e )
810         {
811             throw new MojoExecutionException( "", e );
812         }
813 
814         final ClassLoader compileClassLoader = getCompileClassLoader();
815         final ResourceClassGenerator resGenerator = new ResourceClassGenerator(
816                 this,
817                 targetDirectory,
818                 genDirectory,
819                 compileClassLoader
820         );
821         generateCorrectRJavaForApklibDependencies( resGenerator );
822         generateCorrectRJavaForAarDependencies( resGenerator );
823 
824         getLog().info( "Adding R gen folder to compile classpath: " + genDirectory );
825         project.addCompileSourceRoot( genDirectory.getAbsolutePath() );
826     }
827 
828     /**
829      * @return ClassLoader containing the compile paths.
830      */
831     private ClassLoader getCompileClassLoader()
832     {
833         try
834         {
835             final List<String> runtimeClasspathElements = project.getCompileClasspathElements();
836             final ClassLoaderFactory factory = new ClassLoaderFactory( runtimeClasspathElements );
837             return factory.create();
838         }
839         catch ( DependencyResolutionRequiredException e )
840         {
841             throw new IllegalStateException( "Mojo should have resolved dependencies", e );
842         }
843     }
844 
845     /**
846      * Generate correct R.java for apklibs dependencies of a current project
847      *
848      * @throws MojoExecutionException
849      */
850     private void generateCorrectRJavaForApklibDependencies( ResourceClassGenerator resourceGenerator )
851             throws MojoExecutionException
852     {
853         getLog().debug( "" );
854         getLog().debug( "#generateCorrectRJavaFoApklibDeps" );
855 
856         // Generate R.java for apklibs
857         // Compatibility with Apklib which isn't present in AndroidBuilder
858         getLog().debug( "Generating Rs for apklib deps of project " + project.getArtifact() );
859         final Set<Artifact> apklibDependencies = getTransitiveDependencyArtifacts( APKLIB );
860         for ( final Artifact artifact : apklibDependencies )
861         {
862             getLog().debug( "Generating apklib R.java for " + artifact.getArtifactId() + "..." );
863             generateRForApkLibDependency( artifact );
864         }
865 
866         // Generate corrected R.java for APKLIB dependencies, but only if this is an APK build.
867         if ( !apklibDependencies.isEmpty() && APK.equals( project.getArtifact().getType() ) )
868         {
869             // Generate R.java for each APKLIB based on R.txt
870             getLog().debug( "" );
871             getLog().debug( "Rewriting R files for APKLIB dependencies : " + apklibDependencies );
872             resourceGenerator.generateLibraryRs( apklibDependencies );
873         }
874     }
875 
876     /**
877      * Generate correct R.java for aar dependencies of a current project
878      *
879      * @throws MojoExecutionException if it could not generate the R java for one of the libraries.
880      */
881     private void generateCorrectRJavaForAarDependencies( ResourceClassGenerator resourceGenerator )
882             throws MojoExecutionException
883     {
884         // Generate corrected R.java for AAR dependencies.
885         final Set<Artifact> aarLibraries = getTransitiveDependencyArtifacts( AAR );
886         if ( !aarLibraries.isEmpty() )
887         {
888             // Generate R.java for each AAR based on R.txt
889             getLog().debug( "Generating R file for AAR dependencies" );
890             resourceGenerator.generateLibraryRs( aarLibraries );
891         }
892     }
893 
894     private List<File> getLibraryResourceFolders()
895     {
896         final List<File> resourceFolders = new ArrayList<File>();
897         for ( Artifact artifact : getTransitiveDependencyArtifacts( AAR, APKLIB ) )
898         {
899             getLog().debug( "Considering dep artifact : " + artifact );
900             final File resourceFolder = getUnpackedLibResourceFolder( artifact );
901             if ( resourceFolder.exists() )
902             {
903                 getLog().debug( "Adding apklib or aar resource folder : " + resourceFolder );
904                 resourceFolders.add( resourceFolder );
905             }
906         }
907         return resourceFolders;
908     }
909 
910     /**
911      * Executes aapt to generate the R class for the given apklib.
912      *
913      * @param apklibArtifact apklib for which to generate the R class.
914      * @throws MojoExecutionException if it fails.
915      */
916     private void generateRForApkLibDependency( Artifact apklibArtifact ) throws MojoExecutionException
917     {
918         final File unpackDir = getUnpackedLibFolder( apklibArtifact );
919         getLog().debug( "Generating incomplete R file for apklib: " + apklibArtifact.getGroupId()
920                 + ":" + apklibArtifact.getArtifactId() );
921         final File apklibManifest = new File( unpackDir, "AndroidManifest.xml" );
922         final File apklibResDir = new File( unpackDir, "res" );
923 
924         List<File> dependenciesResDirectories = new ArrayList<File>();
925         final Set<Artifact> apklibDeps = getDependencyResolver()
926                 .getLibraryDependenciesFor( this.session, this.repositorySystem, apklibArtifact );
927         getLog().debug( "apklib=" + apklibArtifact + "  dependencies=" + apklibDeps );
928         for ( Artifact dependency : apklibDeps )
929         {
930             // Add in the resources that are dependencies of the apklib.
931             final String extension = dependency.getType();
932             final File dependencyResDir = getUnpackedLibResourceFolder( dependency );
933             if ( ( extension.equals( APKLIB ) || extension.equals( AAR ) ) && dependencyResDir.exists() )
934             {
935                 dependenciesResDirectories.add( dependencyResDir );
936             }
937         }
938 
939         // Create combinedAssets for this apklib dependency - can't have multiple -A args
940         final File apklibCombAssets = new File( getUnpackedLibFolder( apklibArtifact ), "combined-assets" );
941         for ( Artifact dependency : apklibDeps )
942         {
943             // Accumulate assets for dependencies of the apklib (if they exist).
944             final String extension = dependency.getType();
945             final File dependencyAssetsDir = getUnpackedLibAssetsFolder( dependency );
946             if ( ( extension.equals( APKLIB ) || extension.equals( AAR ) ) )
947             {
948                 copyFolder( dependencyAssetsDir, apklibCombAssets );
949             }
950         }
951         // Overlay the apklib dependency assets (if they exist)
952         final File apkLibAssetsDir = getUnpackedLibAssetsFolder( apklibArtifact );
953         copyFolder( apkLibAssetsDir, apklibCombAssets );
954 
955         final CommandExecutor executor = CommandExecutor.Factory.createDefaultCommmandExecutor();
956         executor.setLogger( getLog() );
957 
958         final AaptCommandBuilder commandBuilder = AaptCommandBuilder
959                 .packageResources( getLog() )
960                 .makeResourcesNonConstant()
961                 .makePackageDirectories()
962                 .setResourceConstantsFolder( genDirectory )
963                 .generateRIntoPackage( extractPackageNameFromAndroidManifest( apklibManifest ) )
964                 .setPathToAndroidManifest( apklibManifest )
965                 .addResourceDirectoryIfExists( apklibResDir )
966                 .addResourceDirectoriesIfExists( dependenciesResDirectories )
967                 .autoAddOverlay()
968                 .addRawAssetsDirectoryIfExists( apklibCombAssets )
969                 .addExistingPackageToBaseIncludeSet( getAndroidSdk().getAndroidJar() )
970                 .addConfigurations( configurations )
971                 .setVerbose( aaptVerbose )
972                 .addExtraArguments( aaptExtraArgs )
973                 // We need to generate R.txt for all projects as it needs to be consumed when generating R class.
974                 // It also needs to be consumed when packaging aar.
975                 .generateRTextFile( unpackDir );
976 
977         getLog().debug( getAndroidSdk().getAaptPath() + " " + commandBuilder.toString() );
978         try
979         {
980             executor.setCaptureStdOut( true );
981             final List<String> commands = commandBuilder.build();
982             executor.executeCommand( getAndroidSdk().getAaptPath(), commands, project.getBasedir(), false );
983         }
984         catch ( ExecutionException e )
985         {
986             throw new MojoExecutionException( "", e );
987         }
988     }
989 
990     private void generateBuildConfig() throws MojoExecutionException
991     {
992         getLog().debug( "Generating BuildConfig file" );
993 
994         // Create the BuildConfig for our package.
995         String packageName = extractPackageNameFromAndroidManifest( destinationManifestFile );
996         if ( StringUtils.isNotBlank( customPackage ) )
997         {
998             packageName = customPackage;
999         }
1000         generateBuildConfigForPackage( packageName );
1001 
1002         // Skip BuildConfig generation for dependencies if this is an AAR project
1003         if ( project.getPackaging().equals( AAR ) )
1004         {
1005             return;
1006         }
1007 
1008         // Generate the BuildConfig for any APKLIB and some AAR dependencies.
1009         // Need to generate for AAR, because some old AARs like ActionBarSherlock do not have BuildConfig (or R)
1010         for ( Artifact artifact : getTransitiveDependencyArtifacts( APKLIB, AAR ) )
1011         {
1012             if ( skipBuildConfigGeneration( artifact ) )
1013             {
1014                 getLog().info( "Skip BuildConfig.java generation for "
1015                         + artifact.getGroupId() + " " + artifact.getArtifactId() );
1016                 continue;
1017             }
1018 
1019             final String depPackageName = extractPackageNameFromAndroidArtifact( artifact );
1020 
1021             generateBuildConfigForPackage( depPackageName );
1022         }
1023     }
1024 
1025     private boolean skipBuildConfigGeneration( Artifact artifact ) throws MojoExecutionException
1026     {
1027         if ( artifact.getType().equals( AAR ) )
1028         {
1029             String depPackageName = extractPackageNameFromAndroidArtifact( artifact );
1030 
1031             if ( isBuildConfigPresent( artifact, depPackageName ) )
1032             {
1033                 return true;
1034             }
1035 
1036             Set<Artifact> transitiveDep = getArtifactResolverHelper()
1037                     .getFilteredArtifacts( project.getArtifacts(), AAR );
1038 
1039             for ( Artifact transitiveArtifact : transitiveDep )
1040             {
1041                 if ( isBuildConfigPresent( transitiveArtifact, depPackageName ) )
1042                 {
1043                     return true;
1044                 }
1045             }
1046         }
1047         return false;
1048     }
1049 
1050     /**
1051      * Check if given artifact includes a matching BuildConfig class
1052      *
1053      * @throws MojoExecutionException
1054      */
1055     private boolean isBuildConfigPresent( Artifact artifact ) throws MojoExecutionException
1056     {
1057         String depPackageName = extractPackageNameFromAndroidArtifact( artifact );
1058 
1059         return isBuildConfigPresent( artifact, depPackageName );
1060     }
1061 
1062     /**
1063      * Check whether the artifact includes a BuildConfig located in a given package.
1064      *
1065      * @param artifact    an AAR artifact to look for BuildConfig in
1066      * @param packageName BuildConfig package name
1067      * @throws MojoExecutionException
1068      */
1069     private boolean isBuildConfigPresent( Artifact artifact, String packageName ) throws MojoExecutionException
1070     {
1071         try
1072         {
1073             JarFile jar = new JarFile( getUnpackedAarClassesJar( artifact ) );
1074             JarEntry entry = jar.getJarEntry( packageName.replace( '.', '/' ) + "/BuildConfig.class" );
1075 
1076             return ( entry != null );
1077         }
1078         catch ( IOException e )
1079         {
1080             getLog().error( "Error generating BuildConfig ", e );
1081             throw new MojoExecutionException( "Error generating BuildConfig", e );
1082         }
1083     }
1084 
1085     private void generateBuildConfigForPackage( String packageName ) throws MojoExecutionException
1086     {
1087         getLog().debug( "Creating BuildConfig for " + packageName );
1088 
1089         File outputFolder = new File( genDirectory, packageName.replace( ".", File.separator ) );
1090         outputFolder.mkdirs();
1091 
1092         StringBuilder buildConfig = new StringBuilder();
1093         buildConfig.append( "package " ).append( packageName ).append( ";\n\n" );
1094         buildConfig.append( "public final class BuildConfig {\n" );
1095         buildConfig.append( "  public static final boolean DEBUG = " ).append( !release ).append( ";\n" );
1096         for ( BuildConfigConstant constant : buildConfigConstants )
1097         {
1098             String value = constant.getValue();
1099             if ( "String".equals( constant.getType() ) )
1100             {
1101                 value = "\"" + value + "\"";
1102             }
1103 
1104             buildConfig.append( "  public static final " )
1105                     .append( constant.getType() )
1106                     .append( " " )
1107                     .append( constant.getName() )
1108                     .append( " = " )
1109                     .append( value )
1110                     .append( ";\n" );
1111         }
1112         buildConfig.append( "}\n" );
1113 
1114         File outputFile = new File( outputFolder, "BuildConfig.java" );
1115         try
1116         {
1117             FileUtils.writeStringToFile( outputFile, buildConfig.toString() );
1118         }
1119         catch ( IOException e )
1120         {
1121             getLog().error( "Error generating BuildConfig ", e );
1122             throw new MojoExecutionException( "Error generating BuildConfig", e );
1123         }
1124     }
1125 
1126     /**
1127      * Given a map of source directories to list of AIDL (relative) filenames within each,
1128      * runs the AIDL compiler for each, such that all source directories are available to
1129      * the AIDL compiler.
1130      *
1131      * @param files Map of source directory File instances to the relative paths to all AIDL files within
1132      * @throws MojoExecutionException If the AIDL compiler fails
1133      */
1134     private void generateAidlFiles( Map<File /*sourceDirectory*/, String[] /*relativeAidlFileNames*/> files )
1135             throws MojoExecutionException
1136     {
1137         List<String> protoCommands = new ArrayList<String>();
1138         protoCommands.add( "-p" + getAndroidSdk().getPathForFrameworkAidl() );
1139 
1140         genDirectoryAidl.mkdirs();
1141         getLog().info( "Adding AIDL gen folder to compile classpath: " + genDirectoryAidl );
1142         project.addCompileSourceRoot( genDirectoryAidl.getPath() );
1143         Set<File> sourceDirs = files.keySet();
1144         for ( File sourceDir : sourceDirs )
1145         {
1146             protoCommands.add( "-I" + sourceDir );
1147         }
1148         for ( File sourceDir : sourceDirs )
1149         {
1150             for ( String relativeAidlFileName : files.get( sourceDir ) )
1151             {
1152                 File targetDirectory = new File( genDirectoryAidl, new File( relativeAidlFileName ).getParent() );
1153                 targetDirectory.mkdirs();
1154 
1155                 final String shortAidlFileName = new File( relativeAidlFileName ).getName();
1156                 final String shortJavaFileName = shortAidlFileName.substring( 0, shortAidlFileName.lastIndexOf( "." ) )
1157                         + ".java";
1158                 final File aidlFileInSourceDirectory = new File( sourceDir, relativeAidlFileName );
1159 
1160                 List<String> commands = new ArrayList<String>( protoCommands );
1161                 commands.add( aidlFileInSourceDirectory.getAbsolutePath() );
1162                 commands.add( new File( targetDirectory, shortJavaFileName ).getAbsolutePath() );
1163                 try
1164                 {
1165                     CommandExecutor executor = CommandExecutor.Factory.createDefaultCommmandExecutor();
1166                     executor.setLogger( this.getLog() );
1167                     executor.setCaptureStdOut( true );
1168                     executor.executeCommand( getAndroidSdk().getAidlPath(), commands, project.getBasedir(),
1169                             false );
1170                 }
1171                 catch ( ExecutionException e )
1172                 {
1173                     throw new MojoExecutionException( "", e );
1174                 }
1175             }
1176         }
1177     }
1178 
1179     private String[] findRelativeAidlFileNames( File sourceDirectory )
1180     {
1181         final FileRetriever retriever = new FileRetriever( "**/*.aidl" );
1182         final String[] relativeAidlFileNames = retriever.getFileNames( sourceDirectory );
1183         if ( relativeAidlFileNames.length == 0 )
1184         {
1185             getLog().debug( "ANDROID-904-002: No aidl files found" );
1186         }
1187         else
1188         {
1189             getLog().info( "ANDROID-904-002: Found aidl files: Count = " + relativeAidlFileNames.length );
1190         }
1191         return relativeAidlFileNames;
1192     }
1193 
1194     /**
1195      * @return true if the pom type is APK, APKLIB, or APKSOURCES
1196      */
1197     private boolean isCurrentProjectAndroid()
1198     {
1199         Set<String> androidArtifacts = new HashSet<String>()
1200         {
1201             {
1202                 addAll( Arrays.asList( APK, APKLIB, APKSOURCES, AAR ) );
1203             }
1204         };
1205         return androidArtifacts.contains( project.getArtifact().getType() );
1206     }
1207 
1208 }