File Coverage

blib/lib/Android/Build.pm
Criterion Covered Total %
statement 32 331 9.6
branch 3 90 3.3
condition 0 26 0.0
subroutine 9 52 17.3
pod 0 43 0.0
total 44 542 8.1


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   587 use warnings FATAL => qw(all);
  1         8  
  1         38  
10 1     1   5 use strict;
  1         2  
  1         34  
11 1     1   6 use Carp qw(confess);
  1         2  
  1         76  
12 1     1   561 use Data::Dump qw(dump);
  1         7554  
  1         67  
13 1     1   3592 use Data::Table::Text qw(:all);
  1         136014  
  1         1919  
14 1     1   716 use File::Copy;
  1         2541  
  1         63  
15 1     1   8 use POSIX qw(strftime); # http://www.cplusplus.com/reference/ctime/strftime/
  1         2  
  1         11  
16              
17             our $VERSION = '20201115';
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 0 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 0 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 0 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 0 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 0 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 0 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             You can see Android::Build in action in GitHub Actions at the end of:
690             L
691              
692             =head2 Prerequisites
693              
694             sudo apt-get install imagemagick zip openjdk-8-jdk openjdk-8-jre
695             sudo cpan install Data::Table::Text Data::Dump Carp POSIX File::Copy;
696              
697             You will need a version of the
698             L
699             as specified right at the end of the page below all the inappropriate
700             advertising for Android Studio.
701              
702             Download:
703              
704             wget https://dl.google.com/android/repository/sdk-tools-linux-3859397.zip
705              
706             and unzip to get a copy of B which can be used to get the version of
707             the SDK that you want to use, for example:
708              
709             sdkmanager --list --verbose
710              
711             Download the SDK components to be used:
712              
713             (cd android/sdk/; tools/bin/sdkmanager \
714             echo 'y' | android/sdk/tools/bin/sdkmanager \
715             'build-tools;25.0.3' emulator 'platform-tools' \
716             'platforms;android-25' 'system-images;android-25;google_apis;x86_64')
717              
718             Add to these components to the B<$PATH> variable for easy command line use:
719              
720             export PATH=$PATH:~/android/sdk/tools/:~/android/sdk/tools/bin:\
721             ~/android/sdk/platform-tools/:~/android/sdk/build-tools/25.0.3
722              
723             Create an AVD:
724              
725             avdmanager create avd --name aaa \
726             -k 'system-images;android-25;google_apis;x86_64' -g google_apis
727              
728             Start the B with a specified screen size (you might need to go into the
729             B folder):
730              
731             emulator -avd aaa -skin "2000x1000"
732              
733             Running L will load the newly created L into the emulator as long
734             as it is the only one running.
735              
736             =head1 Synopsis
737              
738             use Android::Build;
739              
740             my $a = &Android::Build::new(); # Create new builder
741              
742             $a->buildTools = qq(~/Android/sdk/build-tools/25.0.2/); # Android SDK Build tools folder
743             $a->icon = qq(~/images/Jets/EEL.jpg); # Image that will be scaled to make an icon using Imagemagick - the English Electric Lightening
744             $a->keyAlias = qq(xxx); # Alias of key to be used to sign this app
745             $a->keyStoreFile = qq(~/keystore/release-key.keystore); # Key store file
746             $a->keyStorePwd = qq(xxx); # Password for key store file
747             $a->package = qq(com.appaapps.genapp); # Package name containing the activity for this app
748             $a->platform = qq(~/Android/sdk/platforms/android-25/); # Android SDK platform folder
749             $a->platformTools = qq(~/Android/sdk/platform-tools/); # Android SDK platform tools folder
750             $a->src = [q(~/AndroidBuild/SampleApp/src/Activity.java)]; # Source code for the app
751             $a->title = qq(Generic App); # Title of the app as seen under the icon
752              
753             $a->run; # Build, install and run the app on the only emulator
754              
755             Modify the code above to reflect your local environment, then start an emulator
756             and run the modified code to compile your app and load it into the emulator.
757              
758             If you wish to include assets with your app, either use L to
759             specify a build area and place your assets in the B sub directory of
760             this folder before using L to compile your app, else use the L
761             keyword to specify a hash of assets to be included with your app.
762              
763             =head2 Sample App
764              
765             A sample app is included in folder:
766              
767             ./SampleApp
768              
769             Modify the values in
770              
771             ./SampleApp/perl/makeWithPerl.pl
772              
773             to reflect your local environment, then start an emulator and run:
774              
775             perl ./SampleApp/perl/makeWithPerl.pl
776              
777             to compile the sample app and load it into the running emulator.
778              
779             =head2 Signing key
780              
781             If you do not already have a signing key, you can create one with the supplied
782             script:
783              
784             ./SampleApp/perl/generateAKey.pl
785              
786             =head1 Description
787              
788             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.
789              
790              
791             Version '20201114'.
792              
793              
794             The following sections describe the methods in each functional area of this
795             module. For an alphabetic listing of all methods by name see L.
796              
797              
798              
799              
800             =head1 Index
801              
802              
803             =head1 Installation
804              
805             This module is written in 100% Pure Perl and, thus, it is easy to read,
806             comprehend, use, modify and install via B:
807              
808             sudo cpan install Android::Build
809              
810             =head1 Author
811              
812             L
813              
814             L
815              
816             =head1 Copyright
817              
818             Copyright (c) 2016-2019 Philip R Brenan.
819              
820             This module is free software. It may be used, redistributed and/or modified
821             under the same terms as Perl itself.
822              
823             =cut
824              
825              
826              
827             # Tests and documentation
828              
829             sub test
830 1     1 0 11 {my $p = __PACKAGE__;
831 1         11 binmode($_, ":utf8") for *STDOUT, *STDERR;
832 1 50       62 return if eval "eof(${p}::DATA)";
833 1         50 my $s = eval "join('', <${p}::DATA>)";
834 1 50       6 $@ and die $@;
835 1     1   700 eval $s;
  1         65209  
  1         12  
  1         75  
836 1 50       298 $@ and die $@;
837 1         120 1
838             }
839              
840             test unless caller;
841              
842             1;
843             # podDocumentation
844             __DATA__