1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
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
78
79
80
81
82
83
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
97
98 private static final long ADB_TIMEOUT_MS = 60L * 1000;
99
100
101
102
103 public static final String ENV_ANDROID_NDK_HOME = "ANDROID_NDK_HOME";
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124 @Parameter
125 @ConfigPojo( prefix = "ndk" )
126 private Ndk ndk;
127
128
129
130
131 @Component
132 protected MavenProject project;
133
134
135
136
137 @Component
138 protected MavenSession session;
139
140
141
142 @Component
143 protected MojoExecution execution;
144
145
146
147
148 @Parameter( defaultValue = "${project.build.sourceDirectory}", readonly = true )
149 protected File sourceDirectory;
150
151
152
153
154 @Parameter( defaultValue = "${project.build.directory}", readonly = true )
155 protected File targetDirectory;
156
157
158
159
160 @Parameter( defaultValue = "${project.build.outputDirectory}", readonly = true )
161 protected File projectOutputDirectory;
162
163
164
165
166 @Parameter( defaultValue = "${project.build.resources}", readonly = true )
167 protected List<Resource> resources;
168
169
170
171
172 @Parameter( defaultValue = "${project.build.finalName}", readonly = true )
173 protected String finalName;
174
175
176
177
178
179
180 @Parameter( defaultValue = "${project.basedir}/src/main/res" )
181 protected File resourceDirectory;
182
183
184
185
186 @Parameter( defaultValue = "${project.build.sourceEncoding}", readonly = true )
187 protected String sourceEncoding;
188
189
190
191
192 @Parameter( property = "android.genDirectory", defaultValue = "${project.build.directory}/generated-sources/r" )
193 protected File genDirectory;
194
195
196
197
198 @Parameter( property = "android.nativeLibrariesDirectory", defaultValue = "${project.basedir}/src/main/libs" )
199 protected File nativeLibrariesDirectory;
200
201
202
203
204 @Parameter( defaultValue = "${project.build.directory}/ndk-libs", readonly = true )
205 protected File ndkOutputDirectory;
206
207
208
209
210
211
212 @Parameter( defaultValue = "${project.basedir}/res-overlay" )
213 protected File resourceOverlayDirectory;
214
215
216
217
218
219 @Parameter
220 protected File[] resourceOverlayDirectories;
221
222
223
224
225 @Parameter( defaultValue = "${project.basedir}/src/main/assets" )
226 protected File assetsDirectory;
227
228
229
230
231 @Parameter( property = "android.manifestFile", defaultValue = "${project.basedir}/src/main/AndroidManifest.xml" )
232 protected File androidManifestFile;
233
234
235
236
237
238
239 @Parameter( property = "destination.manifestFile", defaultValue = "${project.build.directory}/AndroidManifest.xml" )
240 protected File destinationManifestFile;
241
242
243
244
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
266
267
268 @Parameter( defaultValue = "${project.build.directory}/generated-sources/combined-assets", readonly = true )
269 protected File combinedAssets;
270
271
272
273
274
275
276
277 @Parameter( defaultValue = "false" )
278 private boolean includeLibsJarsFromApklib;
279
280
281
282
283
284
285
286 @Parameter( defaultValue = "true" )
287 private boolean includeLibsJarsFromAar;
288
289
290
291
292
293
294
295
296 @Parameter( property = "android.device" )
297 protected String device;
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314 @Parameter( property = "android.devices" )
315 protected String[] devices;
316
317
318
319
320
321
322
323 @Parameter( property = "android.deviceThreads" )
324 protected int deviceThreads;
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340 @Parameter( property = "android.ips" )
341 protected String[] ips;
342
343
344
345
346
347
348
349 @Parameter( property = "android.configurations" )
350 protected String configurations;
351
352
353
354
355 @Parameter( property = "android.aaptExtraArgs" )
356 protected String[] aaptExtraArgs;
357
358
359
360
361 @Parameter( property = "android.aaptVerbose" )
362 protected boolean aaptVerbose;
363
364
365
366
367
368
369 @Parameter( property = "android.proguardFile" )
370 protected File proguardFile;
371
372
373
374
375
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
388
389 @Parameter( property = "android.customPackage" )
390 protected String customPackage;
391
392
393
394
395 @Component
396 protected MavenProjectHelper projectHelper;
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424 @Parameter
425 private Sdk sdk;
426
427
428
429
430
431
432 @Parameter( property = "android.sdk.path", readonly = true )
433 private File sdkPath;
434
435
436
437
438
439 @Parameter( defaultValue = "${env.ANDROID_HOME}", readonly = true )
440 private String envAndroidHome;
441
442
443
444
445 public static final String ENV_ANDROID_HOME = "ANDROID_HOME";
446
447
448
449
450
451
452 @Parameter( property = "android.sdk.platform", readonly = true )
453 private String sdkPlatform;
454
455
456
457
458
459
460
461
462
463
464
465
466
467 @Parameter( property = "android.undeployBeforeDeploy", defaultValue = "false" )
468 protected boolean undeployBeforeDeploy;
469
470
471
472
473
474
475 @Parameter( property = "android.attachJar", defaultValue = "true" )
476 protected boolean attachJar;
477
478
479
480
481
482
483
484 @Parameter( property = "android.attachSources", defaultValue = "false" )
485 protected boolean attachSources;
486
487
488
489
490
491
492 @Parameter( property = "android.ndk.path", readonly = true )
493 private File ndkPath;
494
495
496
497
498
499 @Parameter( property = "android.release", defaultValue = "false" )
500 protected boolean release;
501
502
503
504
505 @Parameter( property = "android.adb.connectionTimeout", defaultValue = "5000" )
506 protected int adbConnectionTimeout;
507
508
509
510
511 @Parameter( property = "unpackedLibsFolder", defaultValue = "${project.build.directory}/unpacked-libs" )
512 private File unpackedLibsFolder;
513
514
515
516
517
518
519 @Parameter( defaultValue = "false" )
520 private File disableConflictingDependenciesWarning;
521
522
523
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
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
556
557
558 protected Set<Artifact> getRelevantCompileArtifacts()
559 {
560 final List<Artifact> allArtifacts = project.getCompileArtifacts();
561 return getArtifactResolverHelper().getFilteredArtifacts( allArtifacts );
562 }
563
564
565
566
567
568 protected Set<Artifact> getDirectDependencyArtifacts()
569 {
570 final Set<Artifact> allArtifacts = project.getDependencyArtifacts();
571 return getArtifactResolverHelper().getFilteredArtifacts( allArtifacts );
572 }
573
574
575
576
577
578
579
580
581
582
583 protected Set<Artifact> getTransitiveDependencyArtifacts( String... types )
584 {
585 return getArtifactResolverHelper().getFilteredArtifacts( project.getArtifacts(), types );
586 }
587
588
589
590
591
592
593
594
595
596
597 protected Set<Artifact> getTransitiveDependencyArtifacts( List<String> filteredScopes, String... types )
598 {
599 return getArtifactResolverHelper().getFilteredArtifacts( filteredScopes, project.getArtifacts(), types );
600 }
601
602
603
604
605
606
607
608
609 protected File resolveArtifactToFile( Artifact artifact ) throws MojoExecutionException
610 {
611 return getArtifactResolverHelper().resolveArtifactToFile( artifact );
612 }
613
614
615
616
617
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
638
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
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
692
693
694
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
725
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
748
749
750
751
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
769
770
771
772
773
774
775
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
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
869
870
871
872
873
874
875
876
877 private boolean shouldDoWithThisDevice( IDevice idevice ) throws MojoExecutionException, MojoFailureException
878 {
879
880 for ( String device : getDevices() )
881 {
882
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
910
911
912
913
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
924
925
926
927
928
929
930 protected boolean undeployApk( final String packageName ) throws MojoExecutionException, MojoFailureException
931 {
932
933 final AtomicBoolean result = new AtomicBoolean( true );
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
961
962
963
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
1002
1003
1004
1005
1006 protected String extractPackageNameFromAndroidManifestXmlTree( String aaptDumpXmlTree )
1007 {
1008 final Scanner scanner = new Scanner( aaptDumpXmlTree );
1009
1010 scanner.findWithinHorizon( "^E: manifest", 0 );
1011
1012 scanner.findWithinHorizon( " A: package=\"", 0 );
1013
1014 String packageName = scanner.next( ".*?\"" );
1015
1016 packageName = packageName.substring( 0, packageName.length() - 1 );
1017 return packageName;
1018 }
1019
1020
1021
1022
1023
1024
1025
1026
1027
1028
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
1049
1050 protected final String getAndroidManifestPackageName()
1051 {
1052 return extractPackageNameFromAndroidManifest( destinationManifestFile );
1053 }
1054
1055
1056
1057
1058
1059
1060
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
1097
1098
1099
1100
1101
1102
1103
1104
1105
1106
1107
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
1118 buildToolsVersion = sdk.getBuildTools();
1119
1120 if ( sdk.getPath() != null )
1121 {
1122
1123
1124 chosenSdkPath = sdk.getPath();
1125 }
1126 else
1127 {
1128
1129
1130 if ( sdkPath != null )
1131 {
1132
1133 chosenSdkPath = sdkPath;
1134 }
1135 else
1136 {
1137
1138 chosenSdkPath = new File( getAndroidHomeOrThrow() );
1139 }
1140 }
1141
1142
1143
1144 if ( ! isBlank( sdk.getPlatform() ) )
1145 {
1146 chosenSdkPlatform = sdk.getPlatform();
1147 }
1148 else
1149 {
1150 chosenSdkPlatform = sdkPlatform;
1151 }
1152 }
1153 else
1154 {
1155
1156
1157 if ( sdkPath != null )
1158 {
1159
1160 chosenSdkPath = sdkPath;
1161 }
1162 else
1163 {
1164
1165 chosenSdkPath = new File( getAndroidHomeOrThrow() );
1166 }
1167
1168
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
1232
1233
1234 public final File getUnpackedLibNativesFolder( Artifact artifact )
1235 {
1236 return getUnpackedLibHelper().getUnpackedLibNativesFolder( artifact );
1237 }
1238
1239
1240 public static File getLibraryUnpackDirectory( File unpackedApkLibsDirectory, Artifact artifact )
1241 {
1242 return new File( unpackedApkLibsDirectory.getAbsolutePath(), artifact.getArtifactId() );
1243 }
1244
1245
1246
1247
1248
1249
1250
1251
1252
1253
1254
1255
1256
1257
1258
1259 protected AndroidNdk getAndroidNdk() throws MojoExecutionException
1260 {
1261 File chosenNdkPath;
1262
1263 if ( ndkPath != null )
1264 {
1265
1266 chosenNdkPath = ndkPath;
1267 }
1268 else if ( ndk != null && ndk.getPath() != null )
1269 {
1270 chosenNdkPath = ndk.getPath();
1271 }
1272 else
1273 {
1274
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
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
1360
1361 protected final boolean isAPKBuild()
1362 {
1363 return getUnpackedLibHelper().isAPKBuild( project );
1364 }
1365
1366
1367
1368
1369
1370
1371
1372
1373
1374
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 }