File Coverage

blib/lib/Android/Build.pm
Criterion Covered Total %
statement 31 330 9.3
branch 3 90 3.3
condition 0 26 0.0
subroutine 9 52 17.3
pod 6 43 13.9
total 49 541 9.0


line stmt bran cond sub pod time code
1             #!/usr/bin/perl
2             #-------------------------------------------------------------------------------
3             # Command line build of an Android apk without resorting to ant or gradle
4             # Philip R Brenan at gmail dot com, Appa Apps Ltd, 2017
5             #-------------------------------------------------------------------------------
6             # perl Build.PL && perl Build test && sudo perl Build install
7             package Android::Build;
8             require v5.16.0;
9 1     1   611 use warnings FATAL => qw(all);
  1         7  
  1         37  
10 1     1   5 use strict;
  1         2  
  1         32  
11 1     1   6 use Carp qw(confess);
  1         2  
  1         81  
12 1     1   567 use Data::Dump qw(dump);
  1         7634  
  1         66  
13 1     1   3662 use Data::Table::Text qw(:all);
  1         135803  
  1         1841  
14 1     1   690 use File::Copy;
  1         2438  
  1         62  
15 1     1   12 use POSIX qw(strftime); # http://www.cplusplus.com/reference/ctime/strftime/
  1         3  
  1         10  
16              
17             our $VERSION = '20201114';
18              
19             #-------------------------------------------------------------------------------
20             # Constants
21             #-------------------------------------------------------------------------------
22              
23             my $home = currentDirectory(); # Home directory
24             my $permissions = # Default permissions
25             [qw(INTERNET ACCESS_WIFI_STATE ACCESS_NETWORK_STATE WRITE_EXTERNAL_STORAGE),
26             qw(READ_EXTERNAL_STORAGE RECEIVE_BOOT_COMPLETED)];
27             my $version = strftime('%Y%m%d', localtime); # Version number without dots
28             my $javaTarget = 7; # Java release level to target
29              
30             #-------------------------------------------------------------------------------
31             # Private methods
32             #-------------------------------------------------------------------------------
33              
34             sub getSDKLevels($) # File name of Android jar for linting
35 0     0 0 0 {my ($android) = @_; # Android build
36 0         0 my $l = $android->sdkLevels;
37 0 0 0     0 return @$l if $l and @$l;
38 0         0 (15,25)
39             }
40              
41             sub getInstructions # How to get the build tools
42             {<
43             Download the Linux tools as specified at the end of page:
44              
45             https://developer.android.com/studio/index.html
46              
47             last set to:
48              
49             https://dl.google.com/android/repository/sdk-tools-linux-3859397.zip
50              
51             Unzip the retrieved file to get the sdkmanager. Use the sdkmanager to get the
52             version of the SDK that you need, for example:
53              
54             sdkmanager 'platforms;android-25' 'build-tools;25.0.3
55             END
56 0     0 0 0 }
57              
58             sub getPlatform # Get and validate the SDK Platform folder
59 0     0 0 0 {my ($a) = @_;
60 0         0 my $f = $a->platformX;
61 0 0       0 $f or confess <
62              
63             "platform" parameter required - it should be the name of the folder containing
64             the android.jar file that you wish to use. You can get this jar file from:
65              
66             END
67 0 0       0 -d $f or confess <
68             Cannot find platformTools folder:
69             $f
70             END
71 0         0 $f
72             }
73              
74             sub getBuildTools # Get and validate the SDK Platform build-tools folder
75 0     0 0 0 {my ($a) = @_;
76 0         0 my $f = $a->buildToolsX;
77 0 0       0 $f or confess <
78              
79             "buildTools" parameter required - it should be the name of the folder
80             containing the Android SDK build tools. You can get these tools from:
81              
82             END
83 0 0       0 -d $f or confess <
84             Cannot find buildTools folder:
85             $f
86             END
87 0         0 $f
88             }
89              
90             sub getPlatformTools # Get and validate the SDK Platform tools folder
91 0     0 0 0 {my ($a) = @_;
92 0         0 my $f = $a->platformToolsX;
93 0 0       0 $f or confess <
94              
95             "platformTools" parameter required - it should be the name of the folder
96             containing the Android SDK platform tools. You can get these tools from:
97              
98             END
99 0 0       0 -d $f or confess <
100             Cannot find platformTools folder:
101             $f
102             END
103 0         0 $f
104             }
105              
106             sub getDevice($) # Device to be used
107 0     0 0 0 {my ($android) = @_;
108 0         0 my $d = $android->device;
109 0 0       0 return '-e' unless $d;
110 0 0       0 return $d if $d =~ m(\A-)s;
111 0         0 "-s $d"
112             }
113              
114             sub getAndroidJar($) # File name of Android jar for linting
115 0     0 0 0 {my ($android) = @_;
116 0         0 my $p = $android->getPlatform;
117 0         0 my $a = filePath($p, qw(android.jar));
118 0 0       0 -e $a or confess "Cannot find android.jar via file:\n$a\n";
119 0         0 $a
120             }
121              
122             sub getPackage # Get and validate the package name for this app
123 0     0 0 0 {my ($a) = @_;
124 0         0 my $d = $a->package;
125 0 0       0 $d or confess <
126             "package" parameter required - it should be the value used on the package
127             statement in the Activity for this app
128             END
129 0 0       0 $d =~ /\./ or confess <
130             package "$d" should contain at least one '.'
131             END
132 0         0 $d
133             }
134              
135             sub getLintFile # Name of the file to be linted
136 0     0 0 0 {my ($a) = @_;
137 0         0 my $f = $a->lintFileX;
138 0 0       0 $f or confess <
139             "lintFile" parameter required to lint a file
140             END
141 0 0       0 -e $f or confess <
142             File to be linted does not exist:
143             $f
144             END
145 0         0 $f
146             }
147              
148             sub getActivity # Activity for app
149 0     0 0 0 {my ($a) = @_;
150 0   0     0 $a->activity // 'Activity';
151             }
152              
153             sub getAppName # Single word name of app used to construct file names
154 0     0 0 0 {my ($a) = @_;
155 0         0 my $d = $a->getPackage;
156 0         0 (split /\./, $d)[-1];
157             }
158              
159             sub getTitle # Title of app
160 0     0 0 0 {my ($a) = @_;
161 0   0     0 $a->title // $a->getAppName;
162             }
163              
164             sub apkFileName # Apk name - shorn of path
165 0     0 0 0 {my ($a) = @_;
166 0         0 $a->getAppName.'.apk';
167             }
168              
169             sub apk # Apk name - with full path
170 0     0 0 0 {my ($a) = @_;
171 0         0 $a->getBinFolder.$a->apkFileName;
172             }
173              
174             sub getVersion # Version of the app or default to today's date
175 0     0 0 0 {my ($a) = @_;
176 0   0     0 $a->version // $version;
177             }
178              
179             sub buildArea($) # Build folder name
180 0     0 0 0 {my ($a) = @_;
181 0   0     0 $a->buildFolder // '/tmp/app/' # Either the user supplied build folder name or the default
182             }
183              
184 0     0 0 0 sub getAssFolder($) {my ($a) = @_; $a->buildArea.'assets/'} # Assets folder name
  0         0  
185 0     0 0 0 sub getBinFolder($) {my ($a) = @_; $a->buildArea.'bin/'} # Bin folder name
  0         0  
186 0     0 0 0 sub getGenFolder($) {my ($a) = @_; $a->buildArea.'gen/'} # Gen folder name
  0         0  
187 0     0 0 0 sub getResFolder($) {my ($a) = @_; $a->buildArea.'res/'} # Res folder name
  0         0  
188 0     0 0 0 sub getManifestFile($) {my ($a) = @_; $a->buildArea.'AndroidManifest.xml'} # Name of manifest file
  0         0  
189              
190             sub logMessage($@) # Log a message
191 0     0 0 0 {my ($android, @message) = @_;
192 0         0 my $s = join '', grep {$_} @message;
  0         0  
193 0 0       0 chomp($s) if $s =~ /\n\Z/;
194 0         0 push @{$android->log}, $s;
  0         0  
195 0 0       0 say STDERR $s if -t STDERR;
196             }
197              
198             #-------------------------------------------------------------------------------
199             # Create icons for app
200             #-------------------------------------------------------------------------------
201              
202             sub pushIcon # Create and transfer each icon using Imagemagick
203 0     0 0 0 {my ($android, $icon, $size, $dir) = @_;
204 0         0 my $res = $android->getResFolder;
205 0         0 my $man = $android->getManifestFile;
206              
207 0         0 for my $i(qw(ic_launcher))
208 0         0 {for my $d(qw(drawable))
209 0         0 {my $s = $size;
210 0         0 my $T = $res.$d.'-'.$dir.'dpi/'.$i.'.png';
211 0         0 makePath($T);
212 0         0 unlink $T;
213 0         0 my $c = "convert -strip \"$icon\" -resize ${s}x${s}! \"$T\""; # Convert icon to required size and make it square
214              
215 0         0 my $r = zzz($c);
216             # say STDERR dump([$c, $icon, $T, -e $T, fileSize($T), $r ]);
217 0 0 0     0 confess "Unable to create icon:\n$T\n$r\n" # Check icon was created
      0        
218             if $r or !-e $T or fileSize($T) < 10;
219             }
220             }
221             }
222              
223             sub pushIcons # Create icons possibly in parallel
224 0     0 0 0 {my ($android) = @_;
225 0         0 my $icon = $android->iconX;
226 0 0       0 -e $icon or confess "Cannot find icon file:\n$icon\n";
227 0         0 my @pid;
228 0         0 my @i = ([48, "m"], [72, "h"], [96, "xh"], [144, "xxh"]); # Icon specifications
229              
230 0 0       0 if ($android->fastIcons) # Speed up - but it does produce a lot of error messages
231 0         0 {for(@i)
232 0 0       0 {if (my $pid = fork())
233 0         0 {push @pid, $pid
234             }
235             else
236 0         0 {eval {$android->pushIcon($icon, @$_)};
  0         0  
237 0         0 exit;
238             }
239             }
240 0         0 waitpid($_, 0) for @pid;
241             }
242             else
243 0         0 {$android->pushIcon($icon, @$_) for @i;
244             }
245             }
246              
247             #-------------------------------------------------------------------------------
248             # Create manifest for app
249             #-------------------------------------------------------------------------------
250              
251             sub addPermissions # Create permissions
252 0     0 0 0 {my ($android) = @_;
253 0         0 my $P = "android.permission";
254 0         0 my %p = (map {$_=>1} @{$android->permissions});
  0         0  
  0         0  
255 0         0 my $p = "\n";
256              
257 0         0 for(sort keys %p)
258 0         0 {$p .= " \n";
259             }
260              
261             $p
262 0         0 }
263              
264             sub manifest
265 0     0 0 0 {my ($android) = @_;
266 0         0 my $permissions = $android->addPermissions;
267 0         0 my ($minSdk, $targetSdk) = $android->getSDKLevels;
268 0         0 my $package = $android->getPackage;
269 0         0 my $version = $android->getVersion;
270 0         0 my $man = $android->getManifestFile;
271 0         0 my $activity = $android->activityX;
272              
273 0         0 my $manifest = << "END";
274            
275            
276             package="$package"
277             android:installLocation="auto"
278             android:versionCode="$version"
279             android:versionName="\@string/versionName">
280              
281            
282             android:minSdkVersion="$minSdk"
283             android:targetSdkVersion="$targetSdk"/>
284            
285             android:allowBackup="true"
286             android:icon="\@drawable/ic_launcher"
287             android:largeHeap="true"
288             android:debuggable="true"
289             android:hardwareAccelerated="true"
290             android:label="\@string/app_name">
291            
292             android:name=".$activity"
293             android:configChanges="keyboard|keyboardHidden|orientation|screenSize"
294             android:screenOrientation="sensor"
295             android:theme="\@android:style/Theme.NoTitleBar"
296             android:label="\@string/app_name">
297            
298            
299            
300            
301            
302            
303             $permissions
304            
305             END
306 0 0       0 $manifest =~ s/android:debuggable="true"//gs unless $android->debug;
307 0         0 overWriteFile($man, $manifest);
308             }
309              
310             #-------------------------------------------------------------------------------
311             # Create resources for app
312             #-------------------------------------------------------------------------------
313              
314             sub resources()
315 0     0 0 0 {my ($android) = @_;
316 0         0 my $title = $android->getTitle;
317 0         0 my $version = $android->getVersion;
318 0   0     0 my $parameters = $android->parameters // '';
319 0         0 my $package = $android->getPackage;
320 0         0 my $res = $android->getResFolder;
321             my $strings = sub
322 0 0   0   0 {return qq($parameters)
323             unless ref $parameters;
324 0         0 my $s = '';
325 0         0 for my $key(sort keys %$parameters)
326 0         0 {my $val = $parameters->{$key};
327 0         0 $s .= qq($val\n);
328             }
329             $s
330 0         0 }->();
  0         0  
331 0         0 my $t = << "END";
332            
333            
334             $package
335             $title
336             $version
337             $strings
338            
339             END
340 0         0 overWriteFile($res."values/strings.xml", $t);
341              
342 0 0       0 if (my $titles = $android->titles) # Create additional titles from a hash of: {ISO::639 2 digit language code=>title in that language}
343 0         0 {for my $l(sort keys %$titles)
344 0         0 {my $t = $title->{$l};
345 0         0 overWriteFile($res."values-$l/strings.xml", <
346            
347            
348             $t
349            
350             END
351             }
352             }
353             }
354              
355             #-------------------------------------------------------------------------------
356             # Create app
357             #-------------------------------------------------------------------------------
358              
359             sub create
360 0     0 0 0 {my ($android) = @_;
361 0         0 $android->pushIcons;
362 0         0 $android->manifest;
363 0         0 $android->resources;
364             }
365              
366             #-------------------------------------------------------------------------------
367             # Make app
368             #-------------------------------------------------------------------------------
369              
370             sub getAdb
371 0     0 0 0 {my ($android) = @_;
372 0         0 filePath($android->getPlatformTools, qw(adb))
373             }
374              
375             my $confirmRequiredUtilitiesAreInPosition;
376              
377             sub confirmRequiredUtilitiesAreInPosition($) # Confirm required utilities are in position
378 0     0 0 0 {my ($android) = @_;
379 0 0       0 return if $confirmRequiredUtilitiesAreInPosition++; # Only do this once per run
380              
381 0         0 my $buildTools = $android->getBuildTools;
382 0         0 my $adb = $android->getAdb;
383 0         0 my $aapt = filePath($buildTools, qw(aapt));
384 0         0 my $dx = filePath($buildTools, qw(dx));
385 0         0 my $zipAlign = filePath($buildTools, qw(zipalign));
386              
387 0         0 zzz("$aapt version", qr(Android Asset Packaging Tool), 0,
388             "aapt not found at:\n$aapt");
389 0         0 zzz("$adb version", qr(Android Debug Bridge), 0, "adb not found at:\n$adb");
390 0         0 zzz("$dx --version", qr(dx version), 0, "dx not found at:\n$dx");
391 0         0 zzz("jarsigner", qr(Usage: jarsigner), 0, "jarsigner not found");
392 0         0 zzz("javac -version", qr(javac), 0, "javac not found");
393 0         0 zzz("zip -v", qr(Info-ZIP), 0, "zip not found\n");
394 0         0 zzz("$zipAlign", 0, 2, "zipalign not found at:\n$zipAlign");
395             }
396              
397             sub signApkFile($$) # Sign an apk file
398 0     0 0 0 {my ($android, $apkFile) = @_; # Android, apk file to sign
399 0         0 $android->confirmRequiredUtilitiesAreInPosition;
400              
401 0         0 my $keyStoreFile = $android->keyStoreFileX;
402 0 0       0 -e $keyStoreFile or confess"Key store file does not exists:\n$keyStoreFile\n";
403 0         0 my $keyAlias = $android->keyAliasX;
404 0         0 my $keyStorePwd = $android->keyStorePwd;
405              
406 0 0       0 my $alg = $android->debug ? '' : "-sigalg SHA1withRSA -digestalg SHA1";
407              
408 0         0 my $c =
409             "echo $keyStorePwd |".
410             "jarsigner $alg -keystore $keyStoreFile $apkFile $keyAlias";
411 0         0 my $s = zzz($c);
412              
413 0 0       0 $s =~ /reference a valid KeyStore key entry containing a private key/s and
414             confess "Invalid keystore password: $keyStorePwd ".
415             "for keystore:\n$keyStoreFile\n".
416             "Specify the correct password via the keyStorePwd() method\n";
417              
418 0 0       0 $s =~ /jar signed/s or confess "Unable to sign $apkFile\n";
419              
420 0 0       0 if ($android->verifyApk) # Optional verify
421 0         0 {my $v = zzz("jarsigner -verify $apkFile");
422 0 0       0 $v =~ /jar verified/s or confess "Unable to verify $apkFile\n";
423             }
424             }
425              
426             sub make
427 0     0 0 0 {my ($android) = @_;
428 0         0 $android->confirmRequiredUtilitiesAreInPosition;
429 0         0 my $getAppName = $android->getAppName;
430 0         0 my $buildTools = $android->getBuildTools;
431 0         0 my $buildArea = $android->buildArea;
432 0         0 my $adb = $android->getAdb;
433 0         0 my $androidJar = $android->getAndroidJar;
434 0         0 my $aapt = filePath($buildTools, qw(aapt));
435 0         0 my $dx = filePath($buildTools, qw(dx));
436 0         0 my $zipAlign = filePath($buildTools, qw(zipalign));
437 0         0 my $bin = $android->getBinFolder;
438 0         0 my $gen = $android->getGenFolder;
439 0         0 my $res = $android->getResFolder;
440 0         0 my @src = @{$android->src};
  0         0  
441 0         0 my @libs = @{$android->libs};
  0         0  
442 0         0 my $manifest = $android->getManifestFile;
443 0         0 my $binRes = filePath($bin, q(res));
444 0         0 my $classes = filePath($bin, q(classes));
445 0         0 my $api = $bin."$getAppName.ap_";
446 0         0 my $apj = $bin."$getAppName-unaligned.apk";
447 0         0 my $apk = $bin."$getAppName.apk";
448              
449 0         0 if (1) # Confirm required files are in position
450 0         0 {for(
451             [qq(buildArea), $buildArea ],
452             [qq(androidJar), $androidJar],
453             [qq(res), $res ],
454             [qq(manifest), $manifest ],
455             )
456 0         0 {my ($name, $file) = @$_;
457 0 0       0 -e $file or confess "Unable to find $name:\n$file\n";
458             }
459             }
460              
461 0         0 for my $file(@{$android->src}) # Check source files
  0         0  
462 0 0       0 {-e $file or confess "Unable to find source file:\n$file\n";
463             }
464              
465 0         0 for my $file(@{$android->libs}) # Check library files
  0         0  
466 0 0       0 {-e $file or confess "Unable to find library:\n$file\n";
467             }
468              
469 0         0 unlink $_ for $api, $apj, $apk; # Remove any existing apks
470              
471 0         0 if (1) # Generate R.java
472 0         0 {makePath($gen);
473 0         0 my $c = "$aapt package -f -m -0 apk -M $manifest -S $res -I $androidJar".
474             " -J $gen --generate-dependencies";
475 0         0 zzz($c);
476             }
477              
478 0         0 if (1) # Compile java
479 0         0 {makePath(filePathDir($classes));
480 0         0 my $j = join ' ', grep {/\.java\Z/} @src, # Java sources files
  0         0  
481             findFiles(filePathDir($gen));
482              
483 0         0 my $J = join ':', $androidJar, @libs; # Jar files for javac
484              
485 0         0 my $c = "javac -g -Xlint:-options -source $javaTarget ". # Compile java source files
486             " -target $javaTarget -cp $J -d $classes $j";
487 0         0 zzz($c);
488             }
489              
490 0         0 if (1) # 'Dx'
491 0         0 {my $j = join ' ', @libs; # Jar files to include in dex
492 0         0 zzz("$dx --incremental --dex --force-jumbo ".
493             " --output $classes.dex $classes $j");
494             }
495              
496 0         0 if (1) # Crunch
497 0         0 {makePath($binRes);
498 0         0 zzz("$aapt crunch -S $res -C $binRes");
499             }
500              
501 0         0 if (1) # Package
502 0         0 {zzz
503             ("$aapt package --no-crunch -f -0 apk -M $manifest".
504             " -S $binRes -S $res -I $androidJar".
505             " -F $api".
506             " --generate-dependencies");
507             }
508              
509 0         0 if (1) # Create apk and sign
510 0         0 {zzz("mv $api $apj"); # Create apk
511 0         0 zzz("cd $bin && zip -qv $apj classes.dex"); # Add dexed classes
512             }
513              
514 0         0 my $assetsFolder = $android->getAssFolder;
515 0         0 my $assetsFiles = $android->assets; # Create asset files if necessary
516              
517 0 0 0     0 if ($assetsFiles or -d $assetsFolder) # Create asset files if necessary
518 0 0       0 {writeFiles($assetsFiles, $assetsFolder) if $assetsFiles; # Write assets file system hash if supplied
519 0         0 zzz(qq(cd $assetsFolder && cd .. && zip -rv $apj assets)); # Add assets to apk
520             }
521              
522 0         0 $android->signApkFile($apj); # Sign the apk file
523              
524 0         0 zzz("$zipAlign -f 4 $apj $apk"); # Zip align
525              
526 0         0 unlink $_ for $api, $apj; # Remove intermediate apks
527             }
528              
529             sub cloneApk2($$) # Clone an apk file: copy the apk, replace the L, re-sign, zipalign, return the name of the newly created apk file.
530 0     0 0 0 {my ($android, $oldApk) = @_; # Android, file name of apk to be cloned
531 0         0 $android->confirmRequiredUtilitiesAreInPosition;
532              
533 0 0       0 confess "Old apk file name not supplied\n" unless $oldApk;
534 0 0       0 confess "Old apk does not exist:\n$oldApk\n" unless -e $oldApk;
535              
536 0         0 my $buildTools = $android->getBuildTools;
537 0         0 my $zipAlign = filePath($buildTools, qw(zipalign));
538              
539 0         0 my $tempFolder = temporaryFolder; # Temporary folder to unzip into
540 0         0 zzz(<<"END", 0, 0, "Unable to unzip"); # Unzip old apk
541             unzip -o $oldApk -d $tempFolder -x "assets/*" "META-INF/*"
542             END
543              
544 0 0       0 if (my $assetsFiles = $android->assets) # Create asset files if necessary
545 0         0 {my $assetsFolder = fpd($tempFolder, q(assets));
546 0         0 writeFiles($assetsFiles, $assetsFolder);
547             }
548              
549 0         0 my $tmpApk = fpe(temporaryFile, q(apk)); # Temporary Apk
550 0         0 zzz(qq(cd $tempFolder && zip -rv $tmpApk *), 0, 0, "Unable to rezip"); # Recreate apk
551              
552 0         0 $android->signApkFile($tmpApk); # Sign
553              
554 0         0 my $newApk = fpe(temporaryFile, q(apk)); # New apk
555 0         0 zzz("$zipAlign -f 4 $tmpApk $newApk", 0, 0, "Unable to zipalign"); # Zip align
556              
557 0         0 unlink $tmpApk; # Clean up
558 0         0 clearFolder($tempFolder, 100);
559              
560 0         0 return $newApk;
561             }
562              
563             sub compile2($) #P Compile the app
564 0     0 0 0 {my ($android) = @_; # Android build
565 0         0 $android->create;
566 0         0 $android->make; # Compile the app
567             }
568              
569             sub install2($) #P Install an already L app on the selected L:
570 0     0 0 0 {my ($android) = @_; # Android build
571 0         0 my $apk = $android->apk;
572 0         0 my $device = $android->getDevice;
573 0         0 my $package = $android->getPackage;
574 0         0 my $activity = $android->activityX;
575 0         0 my $adb = $android->getAdb." $device ";
576             # say STDERR "Install app";
577 0         0 zzz("$adb install -r $apk");
578             # say STDERR "Start app";
579 0         0 zzz("$adb shell am start $package/.Activity");
580             # say STDERR "App installed and started";
581             }
582              
583             sub lint2($) #P Lint all the source code java files for the app
584 0     0 0 0 {my ($android) = @_; # Android build
585 0         0 my $src = $android->getLintFile;
586 0         0 my $androidJar = $android->getAndroidJar;
587 0   0     0 my $area = $android->classes // 'Classes';
588 0         0 makePath($area);
589 0         0 zzz("javac *.java -d $area -cp $androidJar:$area"); # Android, plus locally created classes
590             }
591              
592             #1 Methods and attributes
593              
594             sub new() #S Create a new build.
595 0     0 1 0 {bless{action =>qq(run),
596             activity =>qw(Activity),
597             device =>qq(emulator-5554),
598             home =>$home,
599             icon =>'icon.png',
600             log =>[],
601             parameters =>'',
602             permissions=>$permissions,
603             version =>$version};
604             }
605              
606             if (1) { # Parameters that can be set by the caller - see the pod at the end of this file for a complete description of what each parameter does
607             genLValueScalarMethods(qw(activity)); # Activity name: default is B. The name of the activity to start on your android device: L is L/L
608             genLValueScalarMethods(qw(assets)); # A hash containing your assets folder (if any). Each key is the file name in the assets folder, each corresponding value is the data for that file. The keys of this hash may contain B to create sub folders.
609             genLValueScalarMethods(qw(buildTools)); # Name of the folder containing the build tools to be used to build the app, see L
610             genLValueScalarMethods(qw(buildFolder)); # Name of a folder in which to build the app, The default is B. If you wish to include assets with your app, specify a named build folder and load it with the desired assets before calling L or specify the assets via L.
611             genLValueScalarMethods(qw(classes)); # A folder containing precompiled java classes and jar files that you wish to L against.
612             genLValueScalarMethods(qw(debug)); # The app will be debuggable if this option is true.
613             genLValueScalarMethods(qw(device)); # Device to run on, default is the only emulator or specify '-d', '-e', or '-s SERIAL' per L
614             genLValueScalarMethods(qw(fastIcons)); # Create icons in parallel if true - the default is to create them serially which takes more elapsed time.
615             genLValueScalarMethods(qw(icon)); # Jpg file containing a picture that will be converted and scaled by L to make an icon for the app, default is B in the current directory.
616             genLValueScalarMethods(qw(keyAlias)); # Alias of the key in your key store file which will be used to sign this app. See L for how to generate a key.
617             genLValueScalarMethods(qw(keyStoreFile)); # Name of your key store file. See L for how to generate a key.
618             genLValueScalarMethods(qw(keyStorePwd)); # Password of your key store file. See L for how to generate a key.
619             genLValueArrayMethods (qw(libs)); # A reference to an array of jar files to be copied into the app build to be used as libraries.
620             genLValueScalarMethods(qw(lintFile)); # A file to be linted with the L action using the android L and the L specified.
621             genLValueArrayMethods (qw(log)); # Output: a reference to an array of messages showing all the non fatal errors produced by this running this build. To catch fatal error enclose L with L
622             genLValueScalarMethods(qw(package)); # The package name used in the manifest file to identify the app. The java file containing the L for this app should use this package name on its B statement.
623             genLValueScalarMethods(qw(parameters)); # Optional parameter string to be placed in folder: B as a string accessible via: B from within the app. Alternatively, if this is a reference to a hash, strings are created for each hash key=value
624             genLValueArrayMethods (qw(permissions)); # A reference to an array of permissions, a standard useful set is applied by default if none are specified.
625             genLValueScalarMethods(qw(platform)); # Folder containing B. For example B<~/Android/sdk/platforms/25.0.2>
626             genLValueScalarMethods(qw(platformTools)); # Folder containing L
627             genLValueArrayMethods (qw(sdkLevels)); # [minSdkVersion, targetSdkVersion], default is [15, 25]
628             genLValueArrayMethods (qw(src)); # A reference to an array of java source files to be compiled to create this app.
629             genLValueScalarMethods(qw(title)); # Title of app, the default is the L name of the app.
630             genLValueScalarMethods(qw(titles)); # A hash of translated titles: {ISO::639 2 digit language code=>title in that language}* for this app.
631             genLValueScalarMethods(qw(verifyApk)); # Verify the signed apk if this is true.
632             genLValueScalarMethods(qw(version)); # The version number of the app. Default is today's date, formatted as B
633             }
634              
635             sub compile($) # Compile the app.
636 0     0 1 0 {my ($android) = @_; # Android build
637 0         0 eval {&compile2(@_)};
  0         0  
638 0 0       0 if ($@)
639 0         0 {$android->logMessage($@);
640 0         0 return $@;
641             }
642             undef # No errors encountered
643 0         0 }
644              
645             sub cloneApk($$) # Clone an apk file: copy the existing apk, replace the L, re-sign, zipalign, return the name of the newly created apk file.
646 0     0 1 0 {my ($android, $oldApk) = @_; # Android build, the file name of the apk to be cloned
647 0         0 &cloneApk2(@_);
648             }
649              
650             sub lint($) # Lint all the Java source code files for the app.
651 0     0 1 0 {my ($android) = @_; # Android build
652 0         0 eval {&lint2(@_)};
  0         0  
653 0 0       0 if ($@)
654 0         0 {$android->logMessage($@);
655 0         0 return $@;
656             }
657             undef # No errors encountered
658 0         0 }
659              
660             sub install($) # Install an already L app on to the selected L
661 0     0 1 0 {my ($android) = @_; # Android build
662 0         0 eval {&install2(@_)};
  0         0  
663 0 0       0 if ($@)
664 0         0 {$android->logMessage($@);
665 0         0 return $@;
666             }
667             undef # No errors encountered
668 0         0 }
669              
670             sub run($) # L the app, L and then run it on the selected L
671 0     0 1 0 {my ($android) = @_; # Android build
672 0         0 for(qw(compile install)) # Compile, install and run
673 0         0 {my $r = $android->$_;
674 0 0       0 return $r if $r;
675             }
676             undef # No errors encountered
677 0         0 }
678              
679             # podDocumentation
680              
681             =encoding utf-8
682              
683             =head1 Name
684              
685             Android::Build - Lint, compile, install, run an Android app using the command line tools minus Ant and Gradle thus freeing development effort from the strictures imposed by Android Studio.
686              
687             =head1 Synopsis
688              
689             =head2 Prerequisites
690              
691             sudo apt-get install imagemagick zip openjdk-8-jdk openjdk-8-jre
692             sudo cpan install Data::Table::Text Data::Dump Carp POSIX File::Copy;
693              
694             You will need a version of the
695             L
696             as specified right at the end of the page below all the inappropriate
697             advertising for Android Studio.
698              
699             Download:
700              
701             wget https://dl.google.com/android/repository/sdk-tools-linux-3859397.zip
702              
703             and unzip to get a copy of B which can be used to get the version of
704             the SDK that you want to use, for example:
705              
706             sdkmanager --list --verbose
707              
708             Download the SDK components to be used:
709              
710             (cd android/sdk/; tools/bin/sdkmanager \
711             echo 'y' | android/sdk/tools/bin/sdkmanager \
712             'build-tools;25.0.3' emulator 'platform-tools' \
713             'platforms;android-25' 'system-images;android-25;google_apis;x86_64')
714              
715             Add to these components to the B<$PATH> variable for easy command line use:
716              
717             export PATH=$PATH:~/android/sdk/tools/:~/android/sdk/tools/bin:\
718             ~/android/sdk/platform-tools/:~/android/sdk/build-tools/25.0.3
719              
720             Create an AVD:
721              
722             avdmanager create avd --name aaa \
723             -k 'system-images;android-25;google_apis;x86_64' -g google_apis
724              
725             Start the B with a specified screen size (you might need to go into the
726             B folder):
727              
728             emulator -avd aaa -skin "2000x1000"
729              
730             Running L will load the newly created L into the emulator as long
731             as it is the only one running.
732              
733             =head1 Synopsis
734              
735             use Android::Build;
736              
737             my $a = &Android::Build::new(); # Create new builder
738              
739             $a->buildTools = qq(~/Android/sdk/build-tools/25.0.2/); # Android SDK Build tools folder
740             $a->icon = qq(~/images/Jets/EEL.jpg); # Image that will be scaled to make an icon using Imagemagick - the English Electric Lightening
741             $a->keyAlias = qq(xxx); # Alias of key to be used to sign this app
742             $a->keyStoreFile = qq(~/keystore/release-key.keystore); # Key store file
743             $a->keyStorePwd = qq(xxx); # Password for key store file
744             $a->package = qq(com.appaapps.genapp); # Package name containing the activity for this app
745             $a->platform = qq(~/Android/sdk/platforms/android-25/); # Android SDK platform folder
746             $a->platformTools = qq(~/Android/sdk/platform-tools/); # Android SDK platform tools folder
747             $a->src = [q(~/AndroidBuild/SampleApp/src/Activity.java)]; # Source code for the app
748             $a->title = qq(Generic App); # Title of the app as seen under the icon
749              
750             $a->run; # Build, install and run the app on the only emulator
751              
752             Modify the code above to reflect your local environment, then start an emulator
753             and run the modified code to compile your app and load it into the emulator.
754              
755             If you wish to include assets with your app, either use L to
756             specify a build area and place your assets in the B sub directory of
757             this folder before using L to compile your app, else use the L
758             keyword to specify a hash of assets to be included with your app.
759              
760             =head2 Sample App
761              
762             A sample app is included in folder:
763              
764             ./SampleApp
765              
766             Modify the values in
767              
768             ./SampleApp/perl/makeWithPerl.pl
769              
770             to reflect your local environment, then start an emulator and run:
771              
772             perl ./SampleApp/perl/makeWithPerl.pl
773              
774             to compile the sample app and load it into the running emulator.
775              
776             =head2 Signing key
777              
778             If you do not already have a signing key, you can create one with the supplied
779             script:
780              
781             ./SampleApp/perl/generateAKey.pl
782              
783             =head1 Description
784              
785             Lint, compile, install, run an Android app using the command line tools minus Ant and Gradle thus freeing development effort from the strictures imposed by Android Studio.
786              
787             The following sections describe the methods in each functional area of this
788             module. For an alphabetic listing of all methods by name see L.
789              
790              
791              
792             =head1 Methods and attributes
793              
794             =head2 new()
795              
796             Create a new build.
797              
798              
799             This is a static method and so should be invoked as:
800              
801             Android::Build::new
802              
803              
804             =head2 activity :lvalue
805              
806             Activity name: default is B. The name of the activity to start on your android device: L is L/L
807              
808              
809             =head2 assets :lvalue
810              
811             A hash containing your assets folder (if any). Each key is the file name in the assets folder, each corresponding value is the data for that file. The keys of this hash may contain B to create sub folders.
812              
813              
814             =head2 buildTools :lvalue
815              
816             Name of the folder containing the build tools to be used to build the app, see L
817              
818              
819             =head2 buildFolder :lvalue
820              
821             Name of a folder in which to build the app, The default is B
822              
823              
824             =head2 classes :lvalue
825              
826             A folder containing precompiled java classes and jar files that you wish to L against.
827              
828              
829             =head2 debug :lvalue
830              
831             The app will be debuggable if this option is true.
832              
833              
834             =head2 device :lvalue
835              
836             Device to run on, default is the only emulator or specify '-d', '-e', or '-s SERIAL' per L
837              
838              
839             =head2 fastIcons :lvalue
840              
841             Create icons in parallel if true - the default is to create them serially which takes more elapsed time.
842              
843              
844             =head2 icon :lvalue
845              
846             Jpg file containing a picture that will be converted and scaled by L to make an icon for the app, default is B in the current directory.
847              
848              
849             =head2 keyAlias :lvalue
850              
851             Alias of the key in your key store file which will be used to sign this app. See L for how to generate a key.
852              
853              
854             =head2 keyStoreFile :lvalue
855              
856             Name of your key store file. See L for how to generate a key.
857              
858              
859             =head2 keyStorePwd :lvalue
860              
861             Password of your key store file. See L for how to generate a key.
862              
863              
864             =head2 libs :lvalue
865              
866             A reference to an array of jar files to be copied into the app build to be used as libraries.
867              
868              
869             =head2 lintFile :lvalue
870              
871             A file to be linted with the L action using the android L and the L specified.
872              
873              
874             =head2 log :lvalue
875              
876             Output: a reference to an array of messages showing all the non fatal errors produced by this running this build. To catch fatal error enclose L with L
877              
878              
879             =head2 package :lvalue
880              
881             The package name used in the manifest file to identify the app. The java file containing the L for this app should use this package name on its B statement.
882              
883              
884             =head2 parameters :lvalue
885              
886             Optional parameter string to be placed in folder: B as a string accessible via: B from within the app. Alternatively, if this is a reference to a hash, strings are created for each hash key=value
887              
888              
889             =head2 permissions :lvalue
890              
891             A reference to an array of permissions, a standard useful set is applied by default if none are specified.
892              
893              
894             =head2 platform :lvalue
895              
896             Folder containing B. For example B<~/Android/sdk/platforms/25.0.2>
897              
898              
899             =head2 platformTools :lvalue
900              
901             Folder containing L
902              
903              
904             =head2 sdkLevels :lvalue
905              
906             [minSdkVersion, targetSdkVersion], default is [15, 25]
907              
908              
909             =head2 src :lvalue
910              
911             A reference to an array of java source files to be compiled to create this app.
912              
913              
914             =head2 title :lvalue
915              
916             Title of app, the default is the L name of the app.
917              
918              
919             =head2 titles :lvalue
920              
921             A hash of translated titles: {ISO::639 2 digit language code=>title in that language}* for this app.
922              
923              
924             =head2 verifyApk :lvalue
925              
926             Verify the signed apk if this is true.
927              
928              
929             =head2 version :lvalue
930              
931             The version number of the app. Default is today's date, formatted as B
932              
933              
934             =head2 compile($)
935              
936             Compile the app.
937              
938             Parameter Description
939             1 $android Android build
940              
941             =head2 cloneApk($$)
942              
943             Clone an apk file: copy the existing apk, replace the L, re-sign, zipalign, return the name of the newly created apk file.
944              
945             Parameter Description
946             1 $android Android build
947             2 $oldApk The file name of the apk to be cloned
948              
949             =head2 lint($)
950              
951             Lint all the Java source code files for the app.
952              
953             Parameter Description
954             1 $android Android build
955              
956             =head2 install($)
957              
958             Install an already L app on to the selected L
959              
960             Parameter Description
961             1 $android Android build
962              
963             =head2 run($)
964              
965             L the app, L and then run it on the selected L
966              
967             Parameter Description
968             1 $android Android build
969              
970              
971             =head1 Index
972              
973              
974             1 L
975              
976             2 L
977              
978             3 L
979              
980             4 L
981              
982             5 L
983              
984             6 L
985              
986             7 L
987              
988             8 L
989              
990             9 L
991              
992             10 L
993              
994             11 L
995              
996             12 L
997              
998             13 L
999              
1000             14 L
1001              
1002             15 L
1003              
1004             16 L
1005              
1006             17 L
1007              
1008             18 L
1009              
1010             19 L
1011              
1012             20 L
1013              
1014             21 L
1015              
1016             22 L
1017              
1018             23 L
1019              
1020             24 L
1021              
1022             25 L
1023              
1024             26 L
1025              
1026             27 L
1027              
1028             28 L
1029              
1030             29 L
1031              
1032             30 L
1033              
1034             31 L
1035              
1036             32 L
1037              
1038             =head1 Installation
1039              
1040             This module is written in 100% Pure Perl and, thus, it is easy to read,
1041             comprehend, use, modify and install via B:
1042              
1043             sudo cpan install Android::Build
1044              
1045             =head1 Author
1046              
1047             L
1048              
1049             L
1050              
1051             =head1 Copyright
1052              
1053             Copyright (c) 2016-2018 Philip R Brenan.
1054              
1055             This module is free software. It may be used, redistributed and/or modified
1056             under the same terms as Perl itself.
1057              
1058             =cut
1059              
1060              
1061              
1062             # Tests and documentation
1063              
1064             sub test
1065 1     1 0 13 {my $p = __PACKAGE__;
1066 1         10 binmode($_, ":utf8") for *STDOUT, *STDERR;
1067 1 50       67 return if eval "eof(${p}::DATA)";
1068 1         71 my $s = eval "join('', <${p}::DATA>)";
1069 1 50       7 $@ and die $@;
1070 1     1   632 eval $s;
  1         64972  
  1         11  
  1         60  
1071 1 50       415 $@ and die $@;
1072             }
1073              
1074             test unless caller;
1075              
1076             1;
1077             # podDocumentation
1078             __DATA__