Android-Build

 view release on metacpan or  search on metacpan

lib/Android/Build.pm  view on Meta::CPAN

  my @i = ([48, "m"], [72, "h"], [96, "xh"], [144, "xxh"]);                     # Icon specifications

  if ($android->fastIcons)                                                      # Speed up - but it does produce a lot of error messages
   {for(@i)
     {if (my $pid = fork())
       {push @pid, $pid
       }
      else
       {eval {$android->pushIcon($icon, @$_)};
        exit;
       }
     }
    waitpid($_, 0) for @pid;
   }
  else
   {$android->pushIcon($icon, @$_) for @i;
   }
 }

#-------------------------------------------------------------------------------
# Create manifest for app
#-------------------------------------------------------------------------------

sub addPermissions                                                              # Create permissions
 {my ($android) = @_;
  my $P = "android.permission";
  my %p = (map {$_=>1} @{$android->permissions});
  my $p = "\n";

  for(sort keys %p)
   {$p .= "  <uses-permission android:name=\"$P.$_\"/>\n";
   }

  $p
 }

sub manifest
 {my ($android) = @_;
  my $permissions = $android->addPermissions;
  my ($minSdk, $targetSdk) = $android->getSDKLevels;
  my $package     = $android->getPackage;
  my $version     = $android->getVersion;
  my $man         = $android->getManifestFile;
  my $activity    = $android->activityX;

  my $manifest = << "END";
<?xml version="1.0" encoding="utf-8"?>
  <manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="$package"
    android:installLocation="auto"
    android:versionCode="$version"
    android:versionName="\@string/versionName">

  <uses-sdk
    android:minSdkVersion="$minSdk"
    android:targetSdkVersion="$targetSdk"/>
  <application
    android:allowBackup="true"
    android:icon="\@drawable/ic_launcher"
    android:largeHeap="true"
    android:debuggable="true"
    android:hardwareAccelerated="true"
    android:label="\@string/app_name">
    <activity
      android:name=".$activity"
      android:configChanges="keyboard|keyboardHidden|orientation|screenSize"
      android:screenOrientation="sensor"
      android:theme="\@android:style/Theme.NoTitleBar"
      android:label="\@string/app_name">
      <intent-filter>
        <action android:name="android.intent.action.MAIN"/>
        <category android:name="android.intent.category.LAUNCHER"/>
      </intent-filter>
    </activity>
  </application>
  $permissions
</manifest>
END
  $manifest =~ s/android:debuggable="true"//gs unless $android->debug;
  overWriteFile($man, $manifest);
 }

#-------------------------------------------------------------------------------
# Create resources for app
#-------------------------------------------------------------------------------

sub resources()
 {my ($android)  = @_;
  my $title      = $android->getTitle;
  my $version    = $android->getVersion;
  my $parameters = $android->parameters // '';
  my $package    = $android->getPackage;
  my $res        = $android->getResFolder;
  my $strings    = sub
   {return qq(<string name="parameters">$parameters</string>)
      unless ref $parameters;
    my $s = '';
    for my $key(sort keys %$parameters)
     {my $val = $parameters->{$key};
      $s .= qq(<string name="$key">$val</string>\n);
     }
    $s
   }->();
  my $t = << "END";
<?xml version="1.0" encoding="utf-8"?>
<resources>
    <string name="packageName">$package</string>
    <string name="app_name">$title</string>
    <string name="versionName">$version</string>
    $strings
</resources>
END
  overWriteFile($res."values/strings.xml", $t);

  if (my $titles = $android->titles)                                            # Create additional titles from a hash of: {ISO::639 2 digit language code=>title in that language}
   {for my $l(sort keys %$titles)
     {my $t = $title->{$l};
      overWriteFile($res."values-$l/strings.xml", <<END);
      <?xml version="1.0" encoding="utf-8"?>
<resources>
    <string name="app_name">$t</string>
</resources>
END
     }
   }
 }

#-------------------------------------------------------------------------------
# Create app
#-------------------------------------------------------------------------------

sub create
 {my ($android) = @_;
  $android->pushIcons;
  $android->manifest;
  $android->resources;
 }

#-------------------------------------------------------------------------------
# Make app
#-------------------------------------------------------------------------------

sub getAdb
 {my ($android) = @_;
  filePath($android->getPlatformTools, qw(adb))
 }

my $confirmRequiredUtilitiesAreInPosition;

sub confirmRequiredUtilitiesAreInPosition($)                                    # Confirm required utilities are in position
 {my ($android)  = @_;
  return if $confirmRequiredUtilitiesAreInPosition++;                           # Only do this once per run

  my $buildTools   = $android->getBuildTools;
  my $adb          = $android->getAdb;
  my $aapt         = filePath($buildTools, qw(aapt));
  my $dx           = filePath($buildTools, qw(dx));
  my $zipAlign     = filePath($buildTools, qw(zipalign));

  zzz("$aapt version", qr(Android Asset Packaging Tool), 0,
      "aapt not found at:\n$aapt");
  zzz("$adb version", qr(Android Debug Bridge), 0, "adb not found at:\n$adb");
  zzz("$dx --version", qr(dx version), 0, "dx not found at:\n$dx");
  zzz("jarsigner", qr(Usage: jarsigner), 0, "jarsigner not found");
  zzz("javac -version", qr(javac), 0, "javac not found");
  zzz("zip -v", qr(Info-ZIP), 0, "zip not found\n");
  zzz("$zipAlign", 0, 2, "zipalign not found at:\n$zipAlign");
 }

sub signApkFile($$)                                                             # Sign an apk file
 {my ($android, $apkFile) = @_;                                                 # Android, apk file to sign
  $android->confirmRequiredUtilitiesAreInPosition;

  my $keyStoreFile = $android->keyStoreFileX;
  -e $keyStoreFile or confess"Key store file does not exists:\n$keyStoreFile\n";
  my $keyAlias     = $android->keyAliasX;
  my $keyStorePwd  = $android->keyStorePwd;

  my $alg = $android->debug ? '' : "-sigalg SHA1withRSA -digestalg SHA1";

  my $c =
    "echo $keyStorePwd |".
    "jarsigner $alg -keystore $keyStoreFile $apkFile $keyAlias";
  my $s = zzz($c);

  $s =~ /reference a valid KeyStore key entry containing a private key/s and
    confess "Invalid keystore password: $keyStorePwd ".
            "for keystore:\n$keyStoreFile\n".
            "Specify the correct password via the keyStorePwd() method\n";

  $s =~ /jar signed/s or confess "Unable to sign $apkFile\n";

  if ($android->verifyApk)                                                      # Optional verify
   {my $v = zzz("jarsigner -verify $apkFile");
    $v =~ /jar verified/s or confess "Unable to verify $apkFile\n";
   }
 }

sub make
 {my ($android)  = @_;
  $android->confirmRequiredUtilitiesAreInPosition;
  my $getAppName    = $android->getAppName;
  my $buildTools   = $android->getBuildTools;
  my $buildArea    = $android->buildArea;
  my $adb          = $android->getAdb;
  my $androidJar   = $android->getAndroidJar;
  my $aapt         = filePath($buildTools, qw(aapt));
  my $dx           = filePath($buildTools, qw(dx));
  my $zipAlign     = filePath($buildTools, qw(zipalign));
  my $bin          = $android->getBinFolder;
  my $gen          = $android->getGenFolder;
  my $res          = $android->getResFolder;
  my @src          = @{$android->src};
  my @libs         = @{$android->libs};
  my $manifest     = $android->getManifestFile;
  my $binRes       = filePath($bin, q(res));
  my $classes      = filePath($bin, q(classes));
  my $api          = $bin."$getAppName.ap_";
  my $apj          = $bin."$getAppName-unaligned.apk";
  my $apk          = $bin."$getAppName.apk";

  if (1)                                                                        # Confirm required files are in position
   {for(
  [qq(buildArea),  $buildArea ],
  [qq(androidJar), $androidJar],
  [qq(res),        $res       ],
  [qq(manifest),   $manifest  ],
  )
     {my ($name, $file) = @$_;
      -e $file or confess "Unable to find $name:\n$file\n";
     }
   }

  for my $file(@{$android->src})                                                # Check source files
   {-e $file or confess "Unable to find source file:\n$file\n";
   }

  for my $file(@{$android->libs})                                               # Check library files
   {-e $file or confess "Unable to find library:\n$file\n";

lib/Android/Build.pm  view on Meta::CPAN

  $android->signApkFile($tmpApk);                                               # Sign

  my $newApk = fpe(temporaryFile, q(apk));                                      # New apk
  zzz("$zipAlign -f 4 $tmpApk $newApk", 0, 0, "Unable to zipalign");            # Zip align

  unlink $tmpApk;                                                               # Clean up
  clearFolder($tempFolder, 100);

  return $newApk;
 }

sub compile2($)                                                                 #P Compile the app
 {my ($android) = @_;                                                           # Android build
  $android->create;
  $android->make;                                                               # Compile the app
 }

sub install2($)                                                                 #P Install an already L<compiled|/compile> app on the selected L<device|/device>:
 {my ($android)  = @_;                                                          # Android build
  my $apk        = $android->apk;
  my $device     = $android->getDevice;
  my $package    = $android->getPackage;
  my $activity   = $android->activityX;
  my $adb        = $android->getAdb." $device ";
# say STDERR "Install app";
  zzz("$adb install -r $apk");
# say STDERR "Start app";
  zzz("$adb shell am start $package/.Activity");
# say STDERR "App installed and started";
 }

sub lint2($)                                                                    #P Lint all the source code java files for the app
 {my ($android)  = @_;                                                          # Android build
  my $src        = $android->getLintFile;
  my $androidJar = $android->getAndroidJar;
  my $area       = $android->classes // 'Classes';
  makePath($area);
  zzz("javac *.java -d $area -cp $androidJar:$area");                           # Android, plus locally created classes
 }

#1 Methods and attributes

sub new()                                                                       #S Create a new build.
 {bless{action     =>qq(run),
        activity   =>qw(Activity),
        device     =>qq(emulator-5554),
        home       =>$home,
        icon       =>'icon.png',
        log        =>[],
        parameters =>'',
        permissions=>$permissions,
        version    =>$version};
 }

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
  genLValueScalarMethods(qw(activity));                                         # Activity name: default is B<Activity>. The name of the activity to start on your android device: L<device|/device> is L<package|/package>/L<Activity|/Activity>
  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 has...
  genLValueScalarMethods(qw(buildTools));                                       # Name of the folder containing the build tools to be used to build the app, see L<prerequisites|/prerequisites>
  genLValueScalarMethods(qw(buildFolder));                                      # Name of a folder in which to build the app, The default is B</tmp/app/>. If you wish to include assets with your app, specify a named build folder and load it with the ...
  genLValueScalarMethods(qw(classes));                                          # A folder containing precompiled java classes and jar files that you wish to L<lint|/lint> against.
  genLValueScalarMethods(qw(debug));                                            # The app will be debuggable if this option is true.
  genLValueScalarMethods(qw(device));                                           # Device to run on, default is the only emulator or specify '-d', '-e', or '-s SERIAL' per L<adb|http://developer.android.com/guide/developing/tools/adb.html>
  genLValueScalarMethods(qw(fastIcons));                                        # Create icons in parallel if true - the default is to create them serially which takes more elapsed time.
  genLValueScalarMethods(qw(icon));                                             # Jpg file containing a picture that will be converted and scaled by L<ImageMagick|http://imagemagick.org/script/index.php> to make an icon for the app, default is B<icon...
  genLValueScalarMethods(qw(keyAlias));                                         # Alias of the key in your key store file which will be used to sign this app. See L<Signing key|/Signing key> for how to generate a key.
  genLValueScalarMethods(qw(keyStoreFile));                                     # Name of your key store file.  See L<Signing key|/Signing key> for how to generate a key.
  genLValueScalarMethods(qw(keyStorePwd));                                      # Password of your key store file.  See L<Signing key|/Signing key> for how to generate a key.
  genLValueArrayMethods (qw(libs));                                             # A reference to an array of jar files to be copied into the app build to be used as libraries.
  genLValueScalarMethods(qw(lintFile));                                         # A file to be linted with the L<lint|/lint> action using the android L<platform|/platform> and the L<classes|/classes> specified.
  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<build|/build> with L<ev...
  genLValueScalarMethods(qw(package));                                          # The package name used in the manifest file to identify the app. The java file containing the L<activity|/activity> for this app should use this package name on its B<pa...
  genLValueScalarMethods(qw(parameters));                                       # Optional parameter string to be placed in folder: B<res> as a string accessible via: B<R.string.parameters> from within the app. Alternatively, if this is a reference t...
  genLValueArrayMethods (qw(permissions));                                      # A reference to an array of permissions, a standard useful set is applied by default if none are specified.
  genLValueScalarMethods(qw(platform));                                         # Folder containing B<android.jar>. For example B<~/Android/sdk/platforms/25.0.2>
  genLValueScalarMethods(qw(platformTools));                                    # Folder containing L<adb|https://developer.android.com/studio/command-line/adb.html>
  genLValueArrayMethods (qw(sdkLevels));                                        # [minSdkVersion, targetSdkVersion], default is [15, 25]
  genLValueArrayMethods (qw(src));                                              # A reference to an array of java source files to be compiled to create this app.
  genLValueScalarMethods(qw(title));                                            # Title of app, the default is the L<package|/package> name of the app.
  genLValueScalarMethods(qw(titles));                                           # A hash of translated titles: {ISO::639 2 digit language code=>title in that language}* for this app.
  genLValueScalarMethods(qw(verifyApk));                                        # Verify the signed apk if this is true.
  genLValueScalarMethods(qw(version));                                          # The version number of the app. Default is today's date, formatted as B<YYYYMMDD>
 }

sub compile($)                                                                  # Compile the app.
 {my ($android)  = @_;                                                          # Android build
  eval {&compile2(@_)};
  if ($@)
   {$android->logMessage($@);
    return $@;
   }
  undef                                                                         # No errors encountered
 }

sub cloneApk($$)                                                                # Clone an apk file: copy the existing apk, replace the L<assets|/assets>, re-sign, zipalign, return the name of the newly created apk file.
 {my ($android, $oldApk) = @_;                                                  # Android build, the file name of the apk to be cloned
  &cloneApk2(@_);
 }

sub lint($)                                                                     # Lint all the Java source code files for the app.
 {my ($android)  = @_;                                                          # Android build
  eval {&lint2(@_)};
  if ($@)
   {$android->logMessage($@);
    return $@;
   }
  undef                                                                         # No errors encountered
 }

sub install($)                                                                  # Install an already L<compiled|/compile> app on to the selected L<device|/device>
 {my ($android)  = @_;                                                          # Android build
  eval {&install2(@_)};
  if ($@)
   {$android->logMessage($@);
    return $@;
   }
  undef                                                                         # No errors encountered
 }

sub run($)                                                                      # L<Compile|/compile> the app, L<install|/install> and then run it on the selected L<device|/device>
 {my ($android)  = @_;                                                          # Android build
  for(qw(compile install))                                                      # Compile, install and run



( run in 1.315 second using v1.01-cache-2.11-cpan-02777c243ea )