File Coverage

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