1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17 package com.jayway.maven.plugins.android.phase09package;
18
19 import com.jayway.maven.plugins.android.AbstractAndroidMojo;
20 import com.jayway.maven.plugins.android.AndroidNdk;
21 import com.jayway.maven.plugins.android.AndroidSigner;
22 import com.jayway.maven.plugins.android.CommandExecutor;
23 import com.jayway.maven.plugins.android.ExecutionException;
24 import com.jayway.maven.plugins.android.common.NativeHelper;
25 import com.jayway.maven.plugins.android.config.ConfigHandler;
26 import com.jayway.maven.plugins.android.config.ConfigPojo;
27 import com.jayway.maven.plugins.android.config.PullParameter;
28 import com.jayway.maven.plugins.android.configuration.Apk;
29 import com.jayway.maven.plugins.android.configuration.Sign;
30 import org.apache.commons.io.FileUtils;
31 import org.apache.commons.io.filefilter.DirectoryFileFilter;
32 import org.apache.commons.io.filefilter.FileFileFilter;
33 import org.apache.commons.io.filefilter.FileFilterUtils;
34 import org.apache.commons.io.filefilter.IOFileFilter;
35 import org.apache.commons.lang.StringUtils;
36 import org.apache.maven.artifact.Artifact;
37 import org.apache.maven.artifact.factory.ArtifactFactory;
38 import org.apache.maven.plugin.MojoExecutionException;
39 import org.apache.maven.plugin.MojoFailureException;
40 import org.codehaus.plexus.util.AbstractScanner;
41 import org.codehaus.plexus.util.DirectoryScanner;
42 import org.codehaus.plexus.util.SelectorUtils;
43
44 import java.io.File;
45 import java.io.FileFilter;
46 import java.io.FileNotFoundException;
47 import java.io.FileOutputStream;
48 import java.io.FilenameFilter;
49 import java.io.IOException;
50 import java.io.InputStream;
51 import java.io.OutputStream;
52 import java.util.ArrayList;
53 import java.util.Enumeration;
54 import java.util.HashMap;
55 import java.util.HashSet;
56 import java.util.List;
57 import java.util.Map;
58 import java.util.Set;
59 import java.util.regex.Matcher;
60 import java.util.regex.Pattern;
61 import java.util.zip.ZipEntry;
62 import java.util.zip.ZipFile;
63 import java.util.zip.ZipOutputStream;
64
65 import static com.jayway.maven.plugins.android.common.AndroidExtension.APK;
66 import static com.jayway.maven.plugins.android.common.AndroidExtension.APKLIB;
67
68
69
70
71
72
73
74
75
76
77
78
79 public class ApkMojo extends AbstractAndroidMojo
80 {
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103 private Sign sign;
104
105
106
107
108
109
110
111
112
113 private String signDebug;
114
115
116
117
118
119
120
121
122 private String renameInstrumentationTargetPackage;
123
124
125
126
127
128
129
130
131
132 private boolean extractDuplicates;
133
134
135
136
137
138
139
140 private File nativeLibrariesOutputDirectory;
141
142
143
144
145
146
147 private String classifier;
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167 private File[] sourceDirectories;
168
169
170
171
172
173
174 protected ArtifactFactory artifactFactory;
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206 @PullParameter( defaultValueGetterMethod = "getDefaultMetaIncludes" )
207 private String[] apkMetaIncludes;
208
209
210
211
212
213
214 @PullParameter( defaultValue = "false" )
215 private Boolean apkDebug;
216
217
218
219
220 @PullParameter( defaultValue = "arm-linux-androideabi-4.4.3" )
221 private String apkNativeToolchain;
222
223
224
225
226
227
228 private String ndkFinalLibraryName;
229
230
231
232
233
234
235
236
237
238
239 private String[] excludeJarResources;
240 private Pattern[] excludeJarResourcesPatterns;
241
242
243
244
245
246
247 @ConfigPojo( prefix = "apk" )
248 private Apk apk;
249
250 private static final Pattern PATTERN_JAR_EXT = Pattern.compile( "^.+\\.jar$", 2 );
251
252
253
254
255
256
257 public void execute() throws MojoExecutionException, MojoFailureException
258 {
259
260
261 if ( ! generateApk )
262 {
263 return;
264 }
265
266 ConfigHandler cfh = new ConfigHandler( this );
267
268 cfh.parseConfiguration();
269
270 generateIntermediateApk();
271
272
273 if ( excludeJarResources != null && excludeJarResources.length > 0 )
274 {
275 getLog().debug( "Compiling " + excludeJarResources.length + " patterns" );
276
277 excludeJarResourcesPatterns = new Pattern[excludeJarResources.length];
278
279 for ( int index = 0; index < excludeJarResources.length; ++index )
280 {
281 excludeJarResourcesPatterns[index] = Pattern.compile( excludeJarResources[index] );
282 }
283 }
284
285
286 File outputFile = new File( project.getBuild().getDirectory(), project.getBuild().getFinalName() + "." + APK );
287 final boolean signWithDebugKeyStore = getAndroidSigner().isSignWithDebugKeyStore();
288
289 if ( getAndroidSigner().shouldCreateBothSignedAndUnsignedApk() )
290 {
291 getLog().info( "Creating debug key signed apk file " + outputFile );
292 createApkFile( outputFile, true );
293 final File unsignedOutputFile = new File( project.getBuild().getDirectory(),
294 project.getBuild().getFinalName() + "-unsigned." + APK );
295 getLog().info( "Creating additional unsigned apk file " + unsignedOutputFile );
296 createApkFile( unsignedOutputFile, false );
297 projectHelper.attachArtifact( project, unsignedOutputFile,
298 classifier == null ? "unsigned" : classifier + "_unsigned" );
299 }
300 else
301 {
302 createApkFile( outputFile, signWithDebugKeyStore );
303 }
304
305 if ( classifier == null )
306 {
307
308 project.getArtifact().setFile( outputFile );
309 }
310 else
311 {
312
313 projectHelper.attachArtifact( project, outputFile, classifier );
314 }
315 }
316
317 void createApkFile( File outputFile, boolean signWithDebugKeyStore ) throws MojoExecutionException
318 {
319 File dexFile = new File( project.getBuild().getDirectory(), "classes.dex" );
320 File zipArchive = new File( project.getBuild().getDirectory(), project.getBuild().getFinalName() + ".ap_" );
321 ArrayList<File> sourceFolders = new ArrayList<File>();
322 if ( sourceDirectories != null )
323 {
324 for ( File f : sourceDirectories )
325 {
326 sourceFolders.add( f );
327 }
328 }
329 ArrayList<File> jarFiles = new ArrayList<File>();
330 ArrayList<File> nativeFolders = new ArrayList<File>();
331
332 boolean useInternalAPKBuilder = true;
333 try
334 {
335 initializeAPKBuilder();
336
337
338 }
339 catch ( Throwable e )
340 {
341
342 useInternalAPKBuilder = false;
343 }
344
345
346
347 processNativeLibraries( nativeFolders );
348
349 if ( useInternalAPKBuilder )
350 {
351 doAPKWithAPKBuilder( outputFile, dexFile, zipArchive, sourceFolders, jarFiles, nativeFolders,
352 signWithDebugKeyStore );
353 }
354 else
355 {
356 doAPKWithCommand( outputFile, dexFile, zipArchive, sourceFolders, jarFiles, nativeFolders,
357 signWithDebugKeyStore );
358 }
359
360 if ( this.apkMetaIncludes != null && this.apkMetaIncludes.length > 0 )
361 {
362 try
363 {
364 addMetaInf( outputFile, jarFiles );
365 }
366 catch ( IOException e )
367 {
368 throw new MojoExecutionException( "Could not add META-INF resources.", e );
369 }
370 }
371 }
372
373 private void addMetaInf( File outputFile, ArrayList<File> jarFiles ) throws IOException
374 {
375 File tmp = File.createTempFile( outputFile.getName(), ".add", outputFile.getParentFile() );
376
377 FileOutputStream fos = new FileOutputStream( tmp );
378 ZipOutputStream zos = new ZipOutputStream( fos );
379 Set<String> entries = new HashSet<String>();
380
381 updateWithMetaInf( zos, outputFile, entries, false );
382
383 for ( File f : jarFiles )
384 {
385 updateWithMetaInf( zos, f, entries, true );
386 }
387
388 zos.close();
389
390 outputFile.delete();
391
392 if ( ! tmp.renameTo( outputFile ) )
393 {
394 throw new IOException( String.format( "Cannot rename %s to %s", tmp, outputFile.getName() ) );
395 }
396 }
397
398 private void updateWithMetaInf( ZipOutputStream zos, File jarFile, Set<String> entries, boolean metaInfOnly )
399 throws IOException
400 {
401 ZipFile zin = new ZipFile( jarFile );
402
403 for ( Enumeration<? extends ZipEntry> en = zin.entries(); en.hasMoreElements(); )
404 {
405 ZipEntry ze = en.nextElement();
406
407 if ( ze.isDirectory() )
408 {
409 continue;
410 }
411
412 String zn = ze.getName();
413
414 if ( metaInfOnly )
415 {
416 if ( ! zn.startsWith( "META-INF/" ) )
417 {
418 continue;
419 }
420
421 if ( this.extractDuplicates && ! entries.add( zn ) )
422 {
423 continue;
424 }
425
426 if ( ! metaInfMatches( zn ) )
427 {
428 continue;
429 }
430 }
431
432 zos.putNextEntry( new ZipEntry( zn ) );
433
434 InputStream is = zin.getInputStream( ze );
435
436 copyStreamWithoutClosing( is, zos );
437
438 is.close();
439 zos.closeEntry();
440 }
441
442 zin.close();
443 }
444
445 private boolean metaInfMatches( String path )
446 {
447 for ( String inc : this.apkMetaIncludes )
448 {
449 if ( SelectorUtils.matchPath( "META-INF/" + inc, path ) )
450 {
451 return true;
452 }
453 }
454
455 return false;
456 }
457
458 private Map<String, List<File>> jars = new HashMap<String, List<File>>();
459
460 private void computeDuplicateFiles( File jar ) throws IOException
461 {
462 ZipFile file = new ZipFile( jar );
463 Enumeration<? extends ZipEntry> list = file.entries();
464 while ( list.hasMoreElements() )
465 {
466 ZipEntry ze = list.nextElement();
467 if ( ! ( ze.getName().contains( "META-INF/" ) || ze.isDirectory() ) )
468 {
469 List<File> l = jars.get( ze.getName() );
470 if ( l == null )
471 {
472 l = new ArrayList<File>();
473 jars.put( ze.getName(), l );
474 }
475 l.add( jar );
476 }
477 }
478 }
479
480
481
482
483
484
485
486
487
488
489
490
491
492 private void doAPKWithAPKBuilder( File outputFile, File dexFile, File zipArchive, ArrayList<File> sourceFolders,
493 ArrayList<File> jarFiles, ArrayList<File> nativeFolders,
494 boolean signWithDebugKeyStore ) throws MojoExecutionException
495 {
496 getLog().debug( "Building APK with internal APKBuilder" );
497 sourceFolders.add( new File( project.getBuild().getOutputDirectory() ) );
498
499 for ( Artifact artifact : getRelevantCompileArtifacts() )
500 {
501 if ( extractDuplicates )
502 {
503 try
504 {
505 computeDuplicateFiles( artifact.getFile() );
506 }
507 catch ( Exception e )
508 {
509 getLog().warn( "Cannot compute duplicates files from " + artifact.getFile().getAbsolutePath(), e );
510 }
511 }
512 jarFiles.add( artifact.getFile() );
513 }
514
515
516 if ( extractDuplicates )
517 {
518 List<String> duplicates = new ArrayList<String>();
519 List<File> jarToModify = new ArrayList<File>();
520 for ( String s : jars.keySet() )
521 {
522 List<File> l = jars.get( s );
523 if ( l.size() > 1 )
524 {
525 getLog().warn( "Duplicate file " + s + " : " + l );
526 duplicates.add( s );
527 for ( int i = 1; i < l.size(); i++ )
528 {
529 if ( ! jarToModify.contains( l.get( i ) ) )
530 {
531 jarToModify.add( l.get( i ) );
532 }
533 }
534 }
535 }
536
537
538 for ( File file : jarToModify )
539 {
540 File newJar;
541 newJar = removeDuplicatesFromJar( file, duplicates );
542 int index = jarFiles.indexOf( file );
543 if ( newJar != null )
544 {
545 jarFiles.set( index, newJar );
546 }
547
548 }
549 }
550
551 ApkBuilder builder = new ApkBuilder( outputFile, zipArchive, dexFile, signWithDebugKeyStore, null );
552
553 if ( apkDebug )
554 {
555 builder.setDebugMode( apkDebug );
556 }
557
558 for ( File sourceFolder : sourceFolders )
559 {
560 builder.addSourceFolder( sourceFolder );
561 }
562
563 for ( File jarFile : jarFiles )
564 {
565 boolean excluded = false;
566
567 if ( excludeJarResourcesPatterns != null )
568 {
569 final String name = jarFile.getName();
570 getLog().debug( "Checking " + name + " against patterns" );
571 for ( Pattern pattern : excludeJarResourcesPatterns )
572 {
573 final Matcher matcher = pattern.matcher( name );
574 if ( matcher.matches() )
575 {
576 getLog().debug( "Jar " + name + " excluded by pattern " + pattern );
577 excluded = true;
578 break;
579 }
580 else
581 {
582 getLog().debug( "Jar " + name + " not excluded by pattern " + pattern );
583 }
584 }
585 }
586
587 if ( excluded )
588 {
589 continue;
590 }
591
592 if ( jarFile.isDirectory() )
593 {
594 String[] filenames = jarFile.list( new FilenameFilter()
595 {
596 public boolean accept( File dir, String name )
597 {
598 return PATTERN_JAR_EXT.matcher( name ).matches();
599 }
600 } );
601
602 for ( String filename : filenames )
603 {
604 builder.addResourcesFromJar( new File( jarFile, filename ) );
605 }
606 }
607 else
608 {
609 builder.addResourcesFromJar( jarFile );
610 }
611 }
612
613 for ( File nativeFolder : nativeFolders )
614 {
615 builder.addNativeLibraries( nativeFolder, null );
616 }
617
618 builder.sealApk();
619 }
620
621 private File removeDuplicatesFromJar( File in, List<String> duplicates )
622 {
623 String target = project.getBuild().getOutputDirectory();
624 File tmp = new File( target, "unpacked-embedded-jars" );
625 tmp.mkdirs();
626 File out = new File( tmp, in.getName() );
627
628 if ( out.exists() )
629 {
630 return out;
631 }
632 else
633 {
634 try
635 {
636 out.createNewFile();
637 }
638 catch ( IOException e )
639 {
640 e.printStackTrace();
641 }
642 }
643
644
645 FileOutputStream fos = null;
646 ZipOutputStream jos = null;
647 try
648 {
649 fos = new FileOutputStream( out );
650 jos = new ZipOutputStream( fos );
651 }
652 catch ( FileNotFoundException e1 )
653 {
654 getLog().error( "Cannot remove duplicates : the output file " + out.getAbsolutePath() + " does not found" );
655 return null;
656 }
657
658 ZipFile inZip = null;
659 try
660 {
661 inZip = new ZipFile( in );
662 Enumeration<? extends ZipEntry> entries = inZip.entries();
663 while ( entries.hasMoreElements() )
664 {
665 ZipEntry entry = entries.nextElement();
666
667 if ( ! duplicates.contains( entry.getName() ) )
668 {
669
670 jos.putNextEntry( entry );
671 InputStream currIn = inZip.getInputStream( entry );
672 copyStreamWithoutClosing( currIn, jos );
673 currIn.close();
674 jos.closeEntry();
675 }
676 }
677 }
678 catch ( IOException e )
679 {
680 getLog().error( "Cannot removing duplicates : " + e.getMessage() );
681 return null;
682 }
683
684 try
685 {
686 if ( inZip != null )
687 {
688 inZip.close();
689 }
690 jos.close();
691 fos.close();
692 jos = null;
693 fos = null;
694 }
695 catch ( IOException e )
696 {
697
698 }
699 getLog().info( in.getName() + " rewritten without duplicates : " + out.getAbsolutePath() );
700 return out;
701 }
702
703
704
705
706
707
708
709
710 private static void copyStreamWithoutClosing( InputStream in, OutputStream out ) throws IOException
711 {
712 final int bufferSize = 4096;
713 byte[] b = new byte[ bufferSize ];
714 int n;
715 while ( ( n = in.read( b ) ) != - 1 )
716 {
717 out.write( b, 0, n );
718 }
719 }
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734 private void doAPKWithCommand( File outputFile, File dexFile, File zipArchive, ArrayList<File> sourceFolders,
735 ArrayList<File> jarFiles, ArrayList<File> nativeFolders,
736 boolean signWithDebugKeyStore ) throws MojoExecutionException
737 {
738 getLog().debug( "Building APK from command line" );
739 CommandExecutor executor = CommandExecutor.Factory.createDefaultCommmandExecutor();
740 executor.setLogger( this.getLog() );
741
742 List<String> commands = new ArrayList<String>();
743 commands.add( outputFile.getAbsolutePath() );
744
745 if ( ! signWithDebugKeyStore )
746 {
747 commands.add( "-u" );
748 }
749
750 commands.add( "-z" );
751 commands.add( new File( project.getBuild().getDirectory(), project.getBuild().getFinalName() + ".ap_" )
752 .getAbsolutePath() );
753 commands.add( "-f" );
754 commands.add( new File( project.getBuild().getDirectory(), "classes.dex" ).getAbsolutePath() );
755 commands.add( "-rf" );
756 commands.add( new File( project.getBuild().getOutputDirectory() ).getAbsolutePath() );
757
758 if ( nativeFolders != null && ! nativeFolders.isEmpty() )
759 {
760 for ( File lib : nativeFolders )
761 {
762 commands.add( "-nf" );
763 commands.add( lib.getAbsolutePath() );
764 }
765 }
766
767 for ( Artifact artifact : getRelevantCompileArtifacts() )
768 {
769 commands.add( "-rj" );
770 commands.add( artifact.getFile().getAbsolutePath() );
771 }
772
773
774 getLog().info( getAndroidSdk().getApkBuilderPath() + " " + commands.toString() );
775 try
776 {
777 executor.executeCommand( getAndroidSdk().getApkBuilderPath(), commands, project.getBasedir(),
778 false );
779 }
780 catch ( ExecutionException e )
781 {
782 throw new MojoExecutionException( "", e );
783 }
784 }
785
786
787 private void initializeAPKBuilder() throws MojoExecutionException
788 {
789 File file = getAndroidSdk().getSDKLibJar();
790 ApkBuilder.initialize( getLog(), file );
791 }
792
793 private void processNativeLibraries( final List<File> natives ) throws MojoExecutionException
794 {
795 for ( String ndkArchitecture : AndroidNdk.NDK_ARCHITECTURES )
796 {
797 processNativeLibraries( natives, ndkArchitecture );
798 }
799 }
800
801 private void addNativeDirectory( final List<File> natives, final File nativeDirectory )
802 {
803 if ( ! natives.contains( nativeDirectory ) )
804 {
805 natives.add( nativeDirectory );
806 }
807 }
808
809 private void processNativeLibraries( final List<File> natives, String ndkArchitecture )
810 throws MojoExecutionException
811 {
812
813
814
815 final boolean hasValidNativeLibrariesDirectory = nativeLibrariesDirectory != null
816 && nativeLibrariesDirectory.exists()
817 && ( nativeLibrariesDirectory.listFiles() != null && nativeLibrariesDirectory.listFiles().length > 0 );
818
819
820 NativeHelper nativeHelper = new NativeHelper( project, projectRepos, repoSession, repoSystem, artifactFactory,
821 getLog() );
822 final Set<Artifact> artifacts = nativeHelper.getNativeDependenciesArtifacts( unpackedApkLibsDirectory, true );
823
824 final boolean hasValidBuildNativeLibrariesDirectory = nativeLibrariesOutputDirectory.exists() && (
825 nativeLibrariesOutputDirectory.listFiles() != null
826 && nativeLibrariesOutputDirectory.listFiles().length > 0 );
827
828 if ( artifacts.isEmpty() && hasValidNativeLibrariesDirectory && ! hasValidBuildNativeLibrariesDirectory )
829 {
830
831 getLog().debug(
832 "No native library dependencies detected, will point directly to " + nativeLibrariesDirectory );
833
834
835 addNativeDirectory( natives, nativeLibrariesDirectory );
836
837
838
839 optionallyCopyGdbServer( nativeLibrariesDirectory, ndkArchitecture );
840
841 }
842 else
843 {
844 if ( ! artifacts.isEmpty() || hasValidNativeLibrariesDirectory )
845 {
846
847
848
849
850 final File destinationDirectory = new File( nativeLibrariesOutputDirectory.getAbsolutePath() );
851 destinationDirectory.mkdirs();
852
853
854 addNativeDirectory( natives, destinationDirectory );
855
856
857 if ( hasValidNativeLibrariesDirectory )
858 {
859 copyLocalNativeLibraries( nativeLibrariesDirectory, destinationDirectory );
860 }
861
862 if ( ! artifacts.isEmpty() )
863 {
864 for ( Artifact resolvedArtifact : artifacts )
865 {
866 if ( "so".equals( resolvedArtifact.getType() ) && ndkArchitecture.equals(
867 resolvedArtifact.getClassifier() ) )
868 {
869 final File artifactFile = resolvedArtifact.getFile();
870 try
871 {
872 final String artifactId = resolvedArtifact.getArtifactId();
873 String filename = artifactId.startsWith( "lib" )
874 ? artifactId + ".so"
875 : "lib" + artifactId + ".so";
876 if ( ndkFinalLibraryName != null
877 && ( resolvedArtifact.getFile().getName()
878 .startsWith( "lib" + ndkFinalLibraryName ) ) )
879 {
880
881
882 filename = resolvedArtifact.getFile().getName();
883 }
884
885 final File finalDestinationDirectory = getFinalDestinationDirectoryFor(
886 resolvedArtifact, destinationDirectory, ndkArchitecture );
887 final File file = new File( finalDestinationDirectory, filename );
888 getLog().debug(
889 "Copying native dependency " + artifactId + " (" + resolvedArtifact.getGroupId()
890 +
891 ") to " + file );
892 org.apache.commons.io.FileUtils.copyFile( artifactFile, file );
893 }
894 catch ( Exception e )
895 {
896 throw new MojoExecutionException( "Could not copy native dependency.", e );
897 }
898 }
899 else
900 {
901 if ( APKLIB.equals( resolvedArtifact.getType() ) )
902 {
903 addNativeDirectory( natives, new File( getLibraryUnpackDirectory( resolvedArtifact )
904 + "/libs" ) );
905 }
906 }
907 }
908 }
909
910
911 optionallyCopyGdbServer( destinationDirectory, ndkArchitecture );
912 }
913 }
914 }
915
916 private void optionallyCopyGdbServer( File destinationDirectory, String architecture ) throws MojoExecutionException
917 {
918
919 try
920 {
921 final File destDir = new File( destinationDirectory, architecture );
922 if ( apkDebug && destDir.exists() )
923 {
924
925 final File gdbServerFile = getAndroidNdk().getGdbServer( architecture );
926 final File destFile = new File( destDir, "gdbserver" );
927 if ( ! destFile.exists() )
928 {
929 FileUtils.copyFile( gdbServerFile, destFile );
930 }
931 else
932 {
933 getLog().info( "Note: gdbserver binary already exists at destination, will not copy over" );
934 }
935 }
936 }
937 catch ( Exception e )
938 {
939 getLog().error( "Error while copying gdbserver: " + e.getMessage(), e );
940 throw new MojoExecutionException( "Error while copying gdbserver: " + e.getMessage(), e );
941 }
942
943 }
944
945 private File getFinalDestinationDirectoryFor( Artifact resolvedArtifact, File destinationDirectory,
946 String ndkArchitecture )
947 {
948 File finalDestinationDirectory = new File( destinationDirectory, ndkArchitecture + "/" );
949 finalDestinationDirectory.mkdirs();
950 return finalDestinationDirectory;
951 }
952
953 private void copyLocalNativeLibraries( final File localNativeLibrariesDirectory, final File destinationDirectory )
954 throws MojoExecutionException
955 {
956 getLog().debug( "Copying existing native libraries from " + localNativeLibrariesDirectory );
957 try
958 {
959
960 IOFileFilter libSuffixFilter = FileFilterUtils.suffixFileFilter( ".so" );
961
962 IOFileFilter gdbserverNameFilter = FileFilterUtils.nameFileFilter( "gdbserver" );
963 IOFileFilter orFilter = FileFilterUtils.or( libSuffixFilter, gdbserverNameFilter );
964
965 IOFileFilter libFiles = FileFilterUtils.and( FileFileFilter.FILE, orFilter );
966 FileFilter filter = FileFilterUtils.or( DirectoryFileFilter.DIRECTORY, libFiles );
967 org.apache.commons.io.FileUtils
968 .copyDirectory( localNativeLibrariesDirectory, destinationDirectory, filter );
969
970 }
971 catch ( IOException e )
972 {
973 getLog().error( "Could not copy native libraries: " + e.getMessage(), e );
974 throw new MojoExecutionException( "Could not copy native dependency.", e );
975 }
976 }
977
978
979
980
981
982
983
984 private void generateIntermediateApk() throws MojoExecutionException
985 {
986 CommandExecutor executor = CommandExecutor.Factory.createDefaultCommmandExecutor();
987 executor.setLogger( this.getLog() );
988 File[] overlayDirectories = getResourceOverlayDirectories();
989
990 if ( extractedDependenciesRes.exists() )
991 {
992 copyDependenciesRes();
993 }
994 if ( resourceDirectory.exists() && combinedRes.exists() )
995 {
996 copyLocalResourceFiles();
997 }
998
999
1000
1001
1002
1003 if ( extractedDependenciesAssets.exists() )
1004 {
1005 copyDependencyAssets();
1006 }
1007
1008 processApkLibAssets();
1009
1010 if ( assetsDirectory.exists() )
1011 {
1012 copyLocalAssets();
1013 }
1014
1015 File androidJar = getAndroidSdk().getAndroidJar();
1016 File outputFile = new File( project.getBuild().getDirectory(), project.getBuild().getFinalName() + ".ap_" );
1017
1018 List<String> commands = new ArrayList<String>();
1019 commands.add( "package" );
1020 commands.add( "-f" );
1021 commands.add( "-M" );
1022 commands.add( androidManifestFile.getAbsolutePath() );
1023 for ( File resOverlayDir : overlayDirectories )
1024 {
1025 if ( resOverlayDir != null && resOverlayDir.exists() )
1026 {
1027 commands.add( "-S" );
1028 commands.add( resOverlayDir.getAbsolutePath() );
1029 }
1030 }
1031 if ( combinedRes.exists() )
1032 {
1033 commands.add( "-S" );
1034 commands.add( combinedRes.getAbsolutePath() );
1035 }
1036 else
1037 {
1038 if ( resourceDirectory.exists() )
1039 {
1040 commands.add( "-S" );
1041 commands.add( resourceDirectory.getAbsolutePath() );
1042 }
1043 }
1044 for ( Artifact artifact : getAllRelevantDependencyArtifacts() )
1045 {
1046 if ( artifact.getType().equals( APKLIB ) )
1047 {
1048 final String apkLibResDir = getLibraryUnpackDirectory( artifact ) + "/res";
1049 if ( new File( apkLibResDir ).exists() )
1050 {
1051 commands.add( "-S" );
1052 commands.add( apkLibResDir );
1053 }
1054 }
1055 }
1056 commands.add( "--auto-add-overlay" );
1057
1058
1059
1060 if ( combinedAssets.exists() )
1061 {
1062 commands.add( "-A" );
1063 commands.add( combinedAssets.getAbsolutePath() );
1064 }
1065
1066 if ( StringUtils.isNotBlank( renameManifestPackage ) )
1067 {
1068 commands.add( "--rename-manifest-package" );
1069 commands.add( renameManifestPackage );
1070 }
1071
1072 if ( StringUtils.isNotBlank( renameInstrumentationTargetPackage ) )
1073 {
1074 commands.add( "--rename-instrumentation-target-package" );
1075 commands.add( renameInstrumentationTargetPackage );
1076 }
1077
1078 commands.add( "-I" );
1079 commands.add( androidJar.getAbsolutePath() );
1080 commands.add( "-F" );
1081 commands.add( outputFile.getAbsolutePath() );
1082 if ( StringUtils.isNotBlank( configurations ) )
1083 {
1084 commands.add( "-c" );
1085 commands.add( configurations );
1086 }
1087
1088 for ( String aaptExtraArg : aaptExtraArgs )
1089 {
1090 commands.add( aaptExtraArg );
1091 }
1092
1093 if ( !release )
1094 {
1095 getLog().info( "Enabling debug build for apk." );
1096 commands.add( "--debug-mode" );
1097 }
1098 else
1099 {
1100 getLog().info( "Enabling release build for apk." );
1101 }
1102
1103 getLog().info( getAndroidSdk().getAaptPath() + " " + commands.toString() );
1104 try
1105 {
1106 executor.executeCommand( getAndroidSdk().getAaptPath(), commands, project.getBasedir(), false );
1107 }
1108 catch ( ExecutionException e )
1109 {
1110 throw new MojoExecutionException( "", e );
1111 }
1112 }
1113
1114 private void copyDependenciesRes() throws MojoExecutionException
1115 {
1116 try
1117 {
1118 getLog().info( "Copying dependency resource files to combined resource directory." );
1119 if ( ! combinedRes.exists() )
1120 {
1121 if ( ! combinedRes.mkdirs() )
1122 {
1123 throw new MojoExecutionException(
1124 "Could not create directory for combined resources at "
1125 + combinedRes.getAbsolutePath() );
1126 }
1127 }
1128 FileUtils.copyDirectory( extractedDependenciesRes, combinedRes );
1129 }
1130 catch ( IOException e )
1131 {
1132 throw new MojoExecutionException( "", e );
1133 }
1134 }
1135
1136 private void copyLocalResourceFiles() throws MojoExecutionException
1137 {
1138 try
1139 {
1140 getLog().info( "Copying local resource files to combined resource directory." );
1141 org.apache.commons.io.FileUtils.copyDirectory( resourceDirectory, combinedRes, new FileFilter()
1142 {
1143
1144
1145
1146
1147
1148
1149
1150 public boolean accept( File file )
1151 {
1152 for ( String pattern : DirectoryScanner.DEFAULTEXCLUDES )
1153 {
1154 if ( DirectoryScanner.match( pattern, file.getAbsolutePath() ) )
1155 {
1156 getLog().debug(
1157 "Excluding " + file.getName() + " from resource copy : matching " + pattern );
1158 return false;
1159 }
1160 }
1161 return true;
1162 }
1163 } );
1164 }
1165 catch ( IOException e )
1166 {
1167 throw new MojoExecutionException( "", e );
1168 }
1169 }
1170
1171 private void copyDependencyAssets() throws MojoExecutionException
1172 {
1173 try
1174 {
1175 getLog().info( "Copying dependency assets files to combined assets directory." );
1176 FileUtils.copyDirectory( extractedDependenciesAssets, combinedAssets, new FileFilter()
1177 {
1178
1179
1180
1181
1182
1183
1184 public boolean accept( File file )
1185 {
1186 for ( String pattern : AbstractScanner.DEFAULTEXCLUDES )
1187 {
1188 if ( AbstractScanner.match( pattern, file.getAbsolutePath() ) )
1189 {
1190 getLog().debug(
1191 "Excluding " + file.getName() + " from asset copy : matching " + pattern );
1192 return false;
1193 }
1194 }
1195
1196 return true;
1197
1198 }
1199 } );
1200 }
1201 catch ( IOException e )
1202 {
1203 throw new MojoExecutionException( "", e );
1204 }
1205 }
1206
1207 private void copyLocalAssets() throws MojoExecutionException
1208 {
1209 try
1210 {
1211 getLog().info( "Copying local assets files to combined assets directory." );
1212 org.apache.commons.io.FileUtils.copyDirectory( assetsDirectory, combinedAssets, new FileFilter()
1213 {
1214
1215
1216
1217
1218
1219
1220 public boolean accept( File file )
1221 {
1222 for ( String pattern : AbstractScanner.DEFAULTEXCLUDES )
1223 {
1224 if ( AbstractScanner.match( pattern, file.getAbsolutePath() ) )
1225 {
1226 getLog().debug(
1227 "Excluding " + file.getName() + " from asset copy : matching " + pattern );
1228 return false;
1229 }
1230 }
1231
1232 return true;
1233
1234 }
1235 } );
1236 }
1237 catch ( IOException e )
1238 {
1239 throw new MojoExecutionException( "", e );
1240 }
1241 }
1242
1243 private void processApkLibAssets() throws MojoExecutionException
1244 {
1245
1246 List<Artifact> artifactList = new ArrayList<Artifact>( getAllRelevantDependencyArtifacts() );
1247 for ( Artifact artifact : artifactList )
1248 {
1249 if ( artifact.getType().equals( APKLIB ) )
1250 {
1251 File apklibAsssetsDirectory = new File( getLibraryUnpackDirectory( artifact ) + "/assets" );
1252 if ( apklibAsssetsDirectory.exists() )
1253 {
1254 try
1255 {
1256 getLog().info( "Copying dependency assets files to combined assets directory." );
1257 org.apache.commons.io.FileUtils
1258 .copyDirectory( apklibAsssetsDirectory, combinedAssets, new FileFilter()
1259 {
1260
1261
1262
1263
1264
1265
1266 public boolean accept( File file )
1267 {
1268 for ( String pattern : AbstractScanner.DEFAULTEXCLUDES )
1269 {
1270 if ( AbstractScanner.match( pattern, file.getAbsolutePath() ) )
1271 {
1272 getLog().debug( "Excluding " + file.getName() + " from asset copy : "
1273 + "matching " + pattern );
1274 return false;
1275 }
1276 }
1277
1278 return true;
1279
1280 }
1281 } );
1282 }
1283 catch ( IOException e )
1284 {
1285 throw new MojoExecutionException( "", e );
1286 }
1287
1288 }
1289 }
1290 }
1291 }
1292
1293
1294
1295
1296
1297
1298 protected AndroidSigner getAndroidSigner()
1299 {
1300 if ( sign == null )
1301 {
1302 return new AndroidSigner( signDebug );
1303 }
1304 else
1305 {
1306 return new AndroidSigner( sign.getDebug() );
1307 }
1308 }
1309
1310
1311
1312
1313
1314 private String[] getDefaultMetaIncludes()
1315 {
1316 return new String[ 0 ];
1317 }
1318 }