All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.jayway.maven.plugins.android.AbstractAndroidMojo Maven / Gradle / Ivy

There is a newer version: 4.0.0-rc.2
Show newest version
/*
 * Copyright (C) 2009-2011 Jayway AB
 * Copyright (C) 2007-2008 JVending Masa
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package com.jayway.maven.plugins.android;

import com.android.ddmlib.AndroidDebugBridge;
import com.android.ddmlib.IDevice;
import com.android.ddmlib.InstallException;
import com.jayway.maven.plugins.android.common.AetherHelper;
import com.jayway.maven.plugins.android.common.AndroidExtension;
import com.jayway.maven.plugins.android.common.DeviceHelper;
import com.jayway.maven.plugins.android.config.ConfigPojo;
import com.jayway.maven.plugins.android.configuration.Ndk;
import com.jayway.maven.plugins.android.configuration.Sdk;
import org.apache.commons.jxpath.JXPathContext;
import org.apache.commons.jxpath.JXPathNotFoundException;
import org.apache.commons.jxpath.xml.DocumentContainer;
import org.apache.commons.lang.StringUtils;
import org.apache.maven.artifact.Artifact;
import org.apache.maven.execution.MavenSession;
import org.apache.maven.plugin.AbstractMojo;
import org.apache.maven.plugin.MojoExecutionException;
import org.apache.maven.plugin.MojoFailureException;
import org.apache.maven.project.MavenProject;
import org.apache.maven.project.MavenProjectHelper;
import org.codehaus.plexus.util.DirectoryScanner;
import org.eclipse.aether.RepositorySystem;
import org.eclipse.aether.RepositorySystemSession;
import org.eclipse.aether.repository.RemoteRepository;

import java.io.File;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Scanner;
import java.util.Set;
import java.util.concurrent.atomic.AtomicBoolean;

import static com.jayway.maven.plugins.android.common.AndroidExtension.APK;
import static org.apache.commons.lang.StringUtils.isBlank;

/**
 * Contains common fields and methods for android mojos.
 *
 * @author [email protected]
 * @author Manfred Moser 
 */
public abstract class AbstractAndroidMojo extends AbstractMojo
{

    public static final List SUPPORTED_PACKAGING_TYPES = new ArrayList();

    static
    {
        SUPPORTED_PACKAGING_TYPES.add( AndroidExtension.APK );
    }

    /**
     * Android Debug Bridge initialization timeout in milliseconds.
     */
    private static final long ADB_TIMEOUT_MS = 60L * 1000;

    /**
     * The ANDROID_NDK_HOME environment variable name.
     */
    public static final String ENV_ANDROID_NDK_HOME = "ANDROID_NDK_HOME";
    
    /**
     * 

The Android NDK to use.

*

Looks like this:

*
     * <ndk>
     *     <path>/opt/android-ndk-r4</path>
     * </ndk>
     * 
*

The <path> parameter is optional. The default is the setting of the ANDROID_NDK_HOME * environment variable. The parameter can be used to override this setting with a different environment variable * like this:

*
     * <ndk>
     *     <path>${env.ANDROID_NDK_HOME}</path>
     * </ndk>
     * 
*

or just with a hardcoded absolute path. The parameters can also be configured from command-line with parameter * -Dandroid.ndk.path.

* * @parameter */ @ConfigPojo( prefix = "ndk" ) private Ndk ndk; /** * The maven project. * * @parameter expression="${project}" * @required * @readonly */ protected MavenProject project; /** * The maven session. * * @parameter expression="${session}" * @required * @readonly */ protected MavenSession session; /** * The java sources directory. * * @parameter default-value="${project.build.sourceDirectory}" * @readonly */ protected File sourceDirectory; /** * The android resources directory. * * @parameter default-value="${project.basedir}/res" */ protected File resourceDirectory; /** *

Root folder containing native libraries to include in the application package.

* * @parameter expression="${android.nativeLibrariesDirectory}" default-value="${project.basedir}/libs" */ protected File nativeLibrariesDirectory; /** * The android resources overlay directory. This will be overridden * by resourceOverlayDirectories if present. * * @parameter default-value="${project.basedir}/res-overlay" */ protected File resourceOverlayDirectory; /** * The android resources overlay directories. If this is specified, * the {@link #resourceOverlayDirectory} parameter will be ignored. * * @parameter */ protected File[] resourceOverlayDirectories; /** * The android assets directory. * * @parameter default-value="${project.basedir}/assets" */ protected File assetsDirectory; /** * The AndroidManifest.xml file. * * @parameter default-value="${project.basedir}/AndroidManifest.xml" */ protected File androidManifestFile; /** *

A possibly new package name for the application. This value will be passed on to the aapt * parameter --rename-manifest-package. Look to aapt for more help on this.

* * @parameter expression="${android.renameManifestPackage}" */ protected String renameManifestPackage; /** * @parameter expression="${project.build.directory}/generated-sources/extracted-dependencies" * @readonly */ protected File extractedDependenciesDirectory; /** * @parameter expression="${project.build.directory}/generated-sources/extracted-dependencies/res" * @readonly */ protected File extractedDependenciesRes; /** * @parameter expression="${project.build.directory}/generated-sources/extracted-dependencies/assets" * @readonly */ protected File extractedDependenciesAssets; /** * @parameter expression="${project.build.directory}/generated-sources/extracted-dependencies/src/main/java" * @readonly */ protected File extractedDependenciesJavaSources; /** * @parameter expression="${project.build.directory}/generated-sources/extracted-dependencies/src/main/resources" * @readonly */ protected File extractedDependenciesJavaResources; /** * The combined resources directory. This will contain both the resources found in "res" as well as any resources * contained in a apksources dependency. * * @parameter expression="${project.build.directory}/generated-sources/combined-resources/res" * @readonly */ protected File combinedRes; /** * The combined assets directory. This will contain both the assets found in "assets" as well as any assets * contained in a apksources dependency. * * @parameter expression="${project.build.directory}/generated-sources/combined-assets/assets" * @readonly */ protected File combinedAssets; /** * Extract the apklib dependencies here * * @parameter expression="${project.build.directory}/unpack/apklibs" * @readonly */ protected File unpackedApkLibsDirectory; /** * Specifies which the serial number of the device to connect to. Using the special values "usb" or * "emulator" is also valid. "usb" will connect to all actual devices connected (via usb). "emulator" will * connect to all emulators connected. Multiple devices will be iterated over in terms of goals to run. All * device interaction goals support this so you can e.. deploy the apk to all attached emulators and devices. * Goals supporting this are devices, deploy, undeploy, redeploy, pull, push and instrument. * * @parameter expression="${android.device}" */ protected String device; /** * A selection of configurations to be included in the APK as a comma separated list. This will limit the * configurations for a certain type. For example, specifying hdpi will exclude all resource folders * with the mdpi or ldpi modifiers, but won't affect language or orientation modifiers. * For more information about this option, look in the aapt command line help. * * @parameter expression="${android.configurations}" */ protected String configurations; /** * A list of extra arguments that must be passed to aapt. * * @parameter expression="${android.aaptExtraArgs}" */ protected String[] aaptExtraArgs; /** * Automatically create a ProGuard configuration file that will guard Activity classes and the like that are * defined in the AndroidManifest.xml. This files is then automatically used in the proguard mojo execution, * if enabled. * * @parameter expression="${android.proguardFile}" */ protected File proguardFile; /** * Decides whether the Apk should be generated or not. If set to false, dx and apkBuilder will not run. This is * probably most useful for a project used to generate apk sources to be inherited into another application * project. * * @parameter expression="${android.generateApk}" default-value="true" */ protected boolean generateApk; /** * The entry point to Aether, i.e. the component doing all the work. * * @component */ protected RepositorySystem repoSystem; /** * The current repository/network configuration of Maven. * * @parameter default-value="${repositorySystemSession}" * @readonly */ protected RepositorySystemSession repoSession; /** * The project's remote repositories to use for the resolution of project dependencies. * * @parameter default-value="${project.remoteProjectRepositories}" * @readonly */ protected List projectRepos; /** * Generates R.java into a different package. * * @parameter expression="${android.customPackage}" */ protected String customPackage; /** * Maven ProjectHelper. * * @component * @readonly */ protected MavenProjectHelper projectHelper; /** *

The Android SDK to use.

*

Looks like this:

*
     * <sdk>
     *     <path>/opt/android-sdk-linux</path>
     *     <platform>2.1</platform>
     * </sdk>
     * 
*

The <platform> parameter is optional, and corresponds to the * platforms/android-* directories in the Android SDK directory. Default is the latest available * version, so you only need to set it if you for example want to use platform 1.5 but also have e.g. 2.2 installed. * Has no effect when used on an Android SDK 1.1. The parameter can also be coded as the API level. Therefore valid * values are 1.1, 1.5, 1.6, 2.0, 2.01, 2.1, 2.2 and so as well as 3, 4, 5, 6, 7, 8... 16. If a platform/api level * is not installed on the machine an error message will be produced.

*

The <path> parameter is optional. The default is the setting of the ANDROID_HOME * environment variable. The parameter can be used to override this setting with a different environment variable * like this:

*
     * <sdk>
     *     <path>${env.ANDROID_SDK}</path>
     * </sdk>
     * 
*

or just with a hard-coded absolute path. The parameters can also be configured from command-line with * parameters -Dandroid.sdk.path and -Dandroid.sdk.platform.

* * @parameter */ private Sdk sdk; /** *

Parameter designed to pick up -Dandroid.sdk.path in case there is no pom with an * <sdk> configuration tag.

*

Corresponds to {@link com.jayway.maven.plugins.android.configuration.Sdk#path}.

* * @parameter expression="${android.sdk.path}" * @readonly */ private File sdkPath; /** *

Parameter designed to pick up environment variable ANDROID_HOME in case * android.sdk.path is not configured.

* * @parameter expression="${env.ANDROID_HOME}" * @readonly */ private String envAndroidHome; /** * The ANDROID_HOME environment variable name. */ public static final String ENV_ANDROID_HOME = "ANDROID_HOME"; /** *

Parameter designed to pick up -Dandroid.sdk.platform in case there is no pom with an * <sdk> configuration tag.

*

Corresponds to {@link com.jayway.maven.plugins.android.configuration.Sdk#platform}.

* * @parameter expression="${android.sdk.platform}" * @readonly */ private String sdkPlatform; /** *

Whether to undeploy an apk from the device before deploying it.

*

*

Only has effect when running mvn android:deploy in an Android application project manually, or * when running mvn integration-test (or mvn install) in a project with instrumentation * tests. *

*

*

It is useful to keep this set to true at all times, because if an apk with the same package was * previously signed with a different keystore, and deployed to the device, deployment will fail becuase your * keystore is different.

* * @parameter default-value=false * expression="${android.undeployBeforeDeploy}" */ protected boolean undeployBeforeDeploy; /** *

Whether to attach the normal .jar file to the build, so it can be depended on by for example integration-tests * which may then access {@code R.java} from this project.

*

Only disable it if you know you won't need it for any integration-tests. Otherwise, leave it enabled.

* * @parameter default-value=true * expression="${android.attachJar}" */ protected boolean attachJar; /** *

Whether to attach sources to the build, which can be depended on by other {@code apk} projects, for including * them in their builds.

*

Enabling this setting is only required if this project's source code and/or res(ources) will be included in * other projects, using the Maven <dependency> tag.

* * @parameter default-value=false * expression="${android.attachSources}" */ protected boolean attachSources; /** *

Parameter designed to pick up -Dandroid.ndk.path in case there is no pom with an * <ndk> configuration tag.

*

Corresponds to {@link com.jayway.maven.plugins.android.configuration.Ndk#path}.

* * @parameter expression="${android.ndk.path}" * @readonly */ private File ndkPath; /** * Whether to create a release build (default is false / debug build). This affect BuildConfig generation * and apk generation at this stage, but should probably affect other aspects of the build. * @parameter expression="${android.release}" default-value="false" */ protected boolean release; /** * */ private static final Object ADB_LOCK = new Object(); /** * */ private static boolean adbInitialized = false; /** * Which dependency scopes should not be included when unpacking dependencies into the apk. */ protected static final List EXCLUDED_DEPENDENCY_SCOPES = Arrays.asList( "provided", "system", "import" ); /** * @return a {@code Set} of dependencies which may be extracted and otherwise included in other artifacts. Never * {@code null}. This excludes artifacts of the {@code EXCLUDED_DEPENDENCY_SCOPES} scopes. */ protected Set getRelevantCompileArtifacts() { final List allArtifacts = ( List ) project.getCompileArtifacts(); final Set results = filterOutIrrelevantArtifacts( allArtifacts ); return results; } /** * @return a {@code Set} of direct project dependencies. Never {@code null}. This excludes artifacts of the {@code * EXCLUDED_DEPENDENCY_SCOPES} scopes. */ protected Set getRelevantDependencyArtifacts() { final Set allArtifacts = ( Set ) project.getDependencyArtifacts(); final Set results = filterOutIrrelevantArtifacts( allArtifacts ); return results; } /** * @return a {@code List} of all project dependencies. Never {@code null}. This excludes artifacts of the {@code * EXCLUDED_DEPENDENCY_SCOPES} scopes. And * This should maintain dependency order to comply with library project resource precedence. */ protected Set getAllRelevantDependencyArtifacts() { final Set allArtifacts = ( Set ) project.getArtifacts(); final Set results = filterOutIrrelevantArtifacts( allArtifacts ); return results; } /** * * @param allArtifacts * @return */ private Set filterOutIrrelevantArtifacts( Iterable allArtifacts ) { final Set results = new LinkedHashSet(); for ( Artifact artifact : allArtifacts ) { if ( artifact == null ) { continue; } if ( EXCLUDED_DEPENDENCY_SCOPES.contains( artifact.getScope() ) ) { continue; } if ( APK.equalsIgnoreCase( artifact.getType() ) ) { continue; } results.add( artifact ); } return results; } /** * Attempts to resolve an {@link Artifact} to a {@link File}. * * @param artifact to resolve * @return a {@link File} to the resolved artifact, never null. * @throws MojoExecutionException if the artifact could not be resolved. */ protected File resolveArtifactToFile( Artifact artifact ) throws MojoExecutionException { Artifact resolvedArtifact = AetherHelper.resolveArtifact( artifact, repoSystem, repoSession, projectRepos ); final File jar = resolvedArtifact.getFile(); if ( jar == null ) { throw new MojoExecutionException( "Could not resolve artifact " + artifact.getId() + ". Please install it with \"mvn install:install-file ...\" or deploy it to a repository " + "with \"mvn deploy:deploy-file ...\"" ); } return jar; } /** * Initialize the Android Debug Bridge and wait for it to start. Does not reinitialize it if it has * already been initialized (that would through and IllegalStateException...). Synchronized sine * the init call in the library is also synchronized .. just in case. * * @return */ protected AndroidDebugBridge initAndroidDebugBridge() throws MojoExecutionException { synchronized ( ADB_LOCK ) { if ( ! adbInitialized ) { AndroidDebugBridge.init( false ); adbInitialized = true; } AndroidDebugBridge androidDebugBridge = AndroidDebugBridge .createBridge( getAndroidSdk().getAdbPath(), false ); waitUntilConnected( androidDebugBridge ); return androidDebugBridge; } } /** * Run a wait loop until adb is connected or trials run out. This method seems to work more reliably then using a * listener. * * @param adb */ private void waitUntilConnected( AndroidDebugBridge adb ) { int trials = 10; final int connectionWaitTime = 50; while ( trials > 0 ) { try { Thread.sleep( connectionWaitTime ); } catch ( InterruptedException e ) { e.printStackTrace(); } if ( adb.isConnected() ) { break; } trials--; } } /** * Wait for the Android Debug Bridge to return an initial device list. * * @param androidDebugBridge * @throws MojoExecutionException */ protected void waitForInitialDeviceList( final AndroidDebugBridge androidDebugBridge ) throws MojoExecutionException { if ( ! androidDebugBridge.hasInitialDeviceList() ) { getLog().info( "Waiting for initial device list from the Android Debug Bridge" ); long limitTime = System.currentTimeMillis() + ADB_TIMEOUT_MS; while ( ! androidDebugBridge.hasInitialDeviceList() && ( System.currentTimeMillis() < limitTime ) ) { try { Thread.sleep( 1000 ); } catch ( InterruptedException e ) { throw new MojoExecutionException( "Interrupted waiting for initial device list from Android Debug Bridge" ); } } if ( ! androidDebugBridge.hasInitialDeviceList() ) { getLog().error( "Did not receive initial device list from the Android Debug Bridge." ); } } } /** * Deploys an apk file to a connected emulator or usb device. * * @param apkFile the file to deploy * @throws MojoExecutionException If there is a problem deploying the apk file. */ protected void deployApk( final File apkFile ) throws MojoExecutionException, MojoFailureException { if ( undeployBeforeDeploy ) { undeployApk( apkFile ); } doWithDevices( new DeviceCallback() { public void doWithDevice( final IDevice device ) throws MojoExecutionException { String deviceLogLinePrefix = DeviceHelper.getDeviceLogLinePrefix( device ); try { String result = device.installPackage( apkFile.getAbsolutePath(), true ); // according to the docs for installPackage, not null response is error if ( result != null ) { throw new MojoExecutionException( deviceLogLinePrefix + "Install of " + apkFile.getAbsolutePath() + " failed - [" + result + "]" ); } getLog().info( deviceLogLinePrefix + "Successfully installed " + apkFile.getAbsolutePath() + " to " + DeviceHelper.getDescriptiveName( device ) ); } catch ( InstallException e ) { throw new MojoExecutionException( deviceLogLinePrefix + "Install of " + apkFile.getAbsolutePath() + " failed.", e ); } } } ); } /** * * @throws MojoExecutionException * @throws MojoFailureException */ protected void deployDependencies() throws MojoExecutionException, MojoFailureException { Set directDependentArtifacts = project.getDependencyArtifacts(); if ( directDependentArtifacts != null ) { for ( Artifact artifact : directDependentArtifacts ) { String type = artifact.getType(); if ( type.equals( APK ) ) { getLog().debug( "Detected apk dependency " + artifact + ". Will resolve and deploy to device..." ); final File targetApkFile = resolveArtifactToFile( artifact ); if ( undeployBeforeDeploy ) { getLog().debug( "Attempting undeploy of " + targetApkFile + " from device..." ); undeployApk( targetApkFile ); } getLog().debug( "Deploying " + targetApkFile + " to device..." ); deployApk( targetApkFile ); } } } } /** * * @throws MojoExecutionException * @throws MojoFailureException */ protected void deployBuiltApk() throws MojoExecutionException, MojoFailureException { // If we're not on a supported packaging with just skip (Issue 112) // http://code.google.com/p/maven-android-plugin/issues/detail?id=112 if ( ! SUPPORTED_PACKAGING_TYPES.contains( project.getPackaging() ) ) { getLog().info( "Skipping deployment on " + project.getPackaging() ); return; } File apkFile = new File( project.getBuild().getDirectory(), project.getBuild().getFinalName() + "." + APK ); deployApk( apkFile ); } /** * Performs the callback action on the devices determined by * {@link #shouldDoWithThisDevice(com.android.ddmlib.IDevice)} * * @param deviceCallback the action to perform on each device * @throws org.apache.maven.plugin.MojoExecutionException * in case there is a problem * @throws org.apache.maven.plugin.MojoFailureException * in case there is a problem */ protected void doWithDevices( final DeviceCallback deviceCallback ) throws MojoExecutionException, MojoFailureException { final AndroidDebugBridge androidDebugBridge = initAndroidDebugBridge(); if ( !androidDebugBridge.isConnected() ) { throw new MojoExecutionException( "Android Debug Bridge is not connected." ); } waitForInitialDeviceList( androidDebugBridge ); List devices = Arrays.asList( androidDebugBridge.getDevices() ); int numberOfDevices = devices.size(); getLog().info( "Found " + numberOfDevices + " devices connected with the Android Debug Bridge" ); if ( devices.size() == 0 ) { throw new MojoExecutionException( "No online devices attached." ); } boolean shouldRunOnAllDevices = StringUtils.isBlank( device ); if ( shouldRunOnAllDevices ) { getLog().info( "android.device parameter not set, using all attached devices" ); } else { getLog().info( "android.device parameter set to " + device ); } ArrayList doThreads = new ArrayList(); for ( final IDevice idevice : devices ) { if ( shouldRunOnAllDevices ) { String deviceType = idevice.isEmulator() ? "Emulator " : "Device "; getLog().info( deviceType + DeviceHelper.getDescriptiveName( idevice ) + " found." ); } if ( shouldRunOnAllDevices || shouldDoWithThisDevice( idevice ) ) { DoThread deviceDoThread = new DoThread() { public void runDo() throws MojoFailureException, MojoExecutionException { deviceCallback.doWithDevice( idevice ); } }; doThreads.add( deviceDoThread ); deviceDoThread.start(); } } joinAllThreads( doThreads ); throwAnyDoThreadErrors( doThreads ); if ( ! shouldRunOnAllDevices && doThreads.isEmpty() ) { throw new MojoExecutionException( "No device found for android.device=" + device ); } } private void joinAllThreads( ArrayList doThreads ) { for ( Thread deviceDoThread : doThreads ) { try { deviceDoThread.join(); } catch ( InterruptedException e ) { new MojoExecutionException( "Thread#join error for device: " + device ); } } } private void throwAnyDoThreadErrors( ArrayList doThreads ) throws MojoExecutionException, MojoFailureException { for ( DoThread deviceDoThread : doThreads ) { if ( deviceDoThread.failure != null ) { throw deviceDoThread.failure; } if ( deviceDoThread.execution != null ) { throw deviceDoThread.execution; } } } /** * Determines if this {@link IDevice}(s) should be used * * @param idevice the device to check * @return if the device should be used * @throws org.apache.maven.plugin.MojoExecutionException * in case there is a problem * @throws org.apache.maven.plugin.MojoFailureException * in case there is a problem */ private boolean shouldDoWithThisDevice( IDevice idevice ) throws MojoExecutionException, MojoFailureException { // use specified device or all emulators or all devices if ( "emulator".equals( device ) && idevice.isEmulator() ) { return true; } if ( "usb".equals( device ) && ! idevice.isEmulator() ) { return true; } if ( idevice.isEmulator() && ( device.equalsIgnoreCase( idevice.getAvdName() ) || device .equalsIgnoreCase( idevice.getSerialNumber() ) ) ) { return true; } if ( ! idevice.isEmulator() && device.equals( idevice.getSerialNumber() ) ) { return true; } return false; } /** * Undeploys an apk from a connected emulator or usb device. Also deletes the application's data and cache * directories on the device. * * @param apkFile the file to undeploy * @return true if successfully undeployed, false otherwise. */ protected boolean undeployApk( File apkFile ) throws MojoExecutionException, MojoFailureException { final String packageName; packageName = extractPackageNameFromApk( apkFile ); return undeployApk( packageName ); } /** * Undeploys an apk, specified by package name, from a connected emulator * or usb device. Also deletes the application's data and cache * directories on the device. * * @param packageName the package name to undeploy. * @return true if successfully undeployed, false otherwise. */ protected boolean undeployApk( final String packageName ) throws MojoExecutionException, MojoFailureException { final AtomicBoolean result = new AtomicBoolean( true ); // if no devices are present, it counts as successful doWithDevices( new DeviceCallback() { public void doWithDevice( final IDevice device ) throws MojoExecutionException { String deviceLogLinePrefix = DeviceHelper.getDeviceLogLinePrefix( device ); try { device.uninstallPackage( packageName ); getLog().info( deviceLogLinePrefix + "Successfully uninstalled " + packageName + " from " + DeviceHelper.getDescriptiveName( device ) ); result.set( true ); } catch ( InstallException e ) { result.set( false ); throw new MojoExecutionException( deviceLogLinePrefix + "Uninstall of " + packageName + " failed.", e ); } } } ); return result.get(); } /** * Extracts the package name from an apk file. * * @param apkFile apk file to extract package name from. * @return the package name from inside the apk file. */ protected String extractPackageNameFromApk( File apkFile ) throws MojoExecutionException { CommandExecutor executor = CommandExecutor.Factory.createDefaultCommmandExecutor(); executor.setLogger( this.getLog() ); List commands = new ArrayList(); commands.add( "dump" ); commands.add( "xmltree" ); commands.add( apkFile.getAbsolutePath() ); commands.add( "AndroidManifest.xml" ); getLog().info( getAndroidSdk().getAaptPath() + " " + commands.toString() ); try { executor.executeCommand( getAndroidSdk().getAaptPath(), commands, false ); final String xmlTree = executor.getStandardOut(); return extractPackageNameFromAndroidManifestXmlTree( xmlTree ); } catch ( ExecutionException e ) { throw new MojoExecutionException( "Error while trying to figure out package name from inside apk file " + apkFile ); } finally { String errout = executor.getStandardError(); if ( ( errout != null ) && ( errout.trim().length() > 0 ) ) { getLog().error( errout ); } } } /** * Extracts the package name from an XmlTree dump of AndroidManifest.xml by the aapt tool. * * @param aaptDumpXmlTree output from aapt dump xmltree <apkFile> AndroidManifest.xml * @return the package name from inside the apkFile. */ protected String extractPackageNameFromAndroidManifestXmlTree( String aaptDumpXmlTree ) { final Scanner scanner = new Scanner( aaptDumpXmlTree ); // Finds the root element named "manifest". scanner.findWithinHorizon( "^E: manifest", 0 ); // Finds the manifest element's attribute named "package". scanner.findWithinHorizon( " A: package=\"", 0 ); // Extracts the package value including the trailing double quote. String packageName = scanner.next( ".*?\"" ); // Removes the double quote. packageName = packageName.substring( 0, packageName.length() - 1 ); return packageName; } /** * * @param androidManifestFile * @return * @throws MojoExecutionException */ protected String extractPackageNameFromAndroidManifest( File androidManifestFile ) throws MojoExecutionException { final URL xmlURL; try { xmlURL = androidManifestFile.toURI().toURL(); } catch ( MalformedURLException e ) { throw new MojoExecutionException( "Error while trying to figure out package name from inside AndroidManifest.xml file " + androidManifestFile, e ); } final DocumentContainer documentContainer = new DocumentContainer( xmlURL ); final Object packageName = JXPathContext.newContext( documentContainer ) .getValue( "manifest/@package", String.class ); return ( String ) packageName; } /** * Attempts to find the instrumentation test runner from inside the AndroidManifest.xml file. * * @param androidManifestFile the AndroidManifest.xml file to inspect. * @return the instrumentation test runner declared in AndroidManifest.xml, or {@code null} if it is not declared. * @throws MojoExecutionException */ protected String extractInstrumentationRunnerFromAndroidManifest( File androidManifestFile ) throws MojoExecutionException { final URL xmlURL; try { xmlURL = androidManifestFile.toURI().toURL(); } catch ( MalformedURLException e ) { throw new MojoExecutionException( "Error while trying to figure out instrumentation runner from inside AndroidManifest.xml file " + androidManifestFile, e ); } final DocumentContainer documentContainer = new DocumentContainer( xmlURL ); final Object instrumentationRunner; try { instrumentationRunner = JXPathContext.newContext( documentContainer ) .getValue( "manifest//instrumentation/@android:name", String.class ); } catch ( JXPathNotFoundException e ) { return null; } return ( String ) instrumentationRunner; } /** * TODO .. not used. Delete? * * @param baseDirectory * @param includes * @return * @throws MojoExecutionException */ protected int deleteFilesFromDirectory( File baseDirectory, String... includes ) throws MojoExecutionException { final String[] files = findFilesInDirectory( baseDirectory, includes ); if ( files == null ) { return 0; } for ( String file : files ) { final boolean successfullyDeleted = new File( baseDirectory, file ).delete(); if ( ! successfullyDeleted ) { throw new MojoExecutionException( "Failed to delete \"" + file + "\"" ); } } return files.length; } /** * Finds files. * * @param baseDirectory Directory to find files in. * @param includes Ant-style include statements, for example "** /*.aidl" (but without the space * in the middle) * @return String[] of the files' paths and names, relative to baseDirectory. Empty * String[] if baseDirectory does not exist. */ protected String[] findFilesInDirectory( File baseDirectory, String... includes ) { if ( baseDirectory.exists() ) { DirectoryScanner directoryScanner = new DirectoryScanner(); directoryScanner.setBasedir( baseDirectory ); directoryScanner.setIncludes( includes ); directoryScanner.addDefaultExcludes(); directoryScanner.scan(); String[] files = directoryScanner.getIncludedFiles(); return files; } else { return new String[ 0 ]; } } /** *

Returns the Android SDK to use.

*

*

Current implementation looks for System property android.sdk.path, then * <sdk><path> configuration in pom, then environment variable ANDROID_HOME. *

*

This is where we collect all logic for how to lookup where it is, and which one to choose. The lookup is * based on available parameters. This method should be the only one you should need to look at to understand how * the Android SDK is chosen, and from where on disk.

* * @return the Android SDK to use. * @throws org.apache.maven.plugin.MojoExecutionException * if no Android SDK path configuration is available at all. */ protected AndroidSdk getAndroidSdk() throws MojoExecutionException { File chosenSdkPath; String chosenSdkPlatform; if ( sdk != null ) { // An tag exists in the pom. if ( sdk.getPath() != null ) { // An tag is set in the pom. chosenSdkPath = sdk.getPath(); } else { // There is no tag in the pom. if ( sdkPath != null ) { // -Dandroid.sdk.path is set on command line, or via ... chosenSdkPath = sdkPath; } else { // No -Dandroid.sdk.path is set on command line, or via ... chosenSdkPath = new File( getAndroidHomeOrThrow() ); } } // Use from pom if it's there, otherwise try -Dandroid.sdk.platform from command line or // ... if ( ! isBlank( sdk.getPlatform() ) ) { chosenSdkPlatform = sdk.getPlatform(); } else { chosenSdkPlatform = sdkPlatform; } } else { // There is no tag in the pom. if ( sdkPath != null ) { // -Dandroid.sdk.path is set on command line, or via ... chosenSdkPath = sdkPath; } else { // No -Dandroid.sdk.path is set on command line, or via ... chosenSdkPath = new File( getAndroidHomeOrThrow() ); } // Use any -Dandroid.sdk.platform from command line or ... chosenSdkPlatform = sdkPlatform; } return new AndroidSdk( chosenSdkPath, chosenSdkPlatform ); } /** * * @return * @throws MojoExecutionException */ private String getAndroidHomeOrThrow() throws MojoExecutionException { final String androidHome = System.getenv( ENV_ANDROID_HOME ); if ( isBlank( androidHome ) ) { throw new MojoExecutionException( "No Android SDK path could be found. You may configure it in the " + "plugin configuration section in the pom file using ... or " + "... or on command-line " + "using -Dandroid.sdk.path=... or by setting environment variable " + ENV_ANDROID_HOME ); } return androidHome; } /** * * @param apkLibraryArtifact * @return */ protected String getLibraryUnpackDirectory( Artifact apkLibraryArtifact ) { return AbstractAndroidMojo.getLibraryUnpackDirectory( unpackedApkLibsDirectory, apkLibraryArtifact ); } /** * * @param unpackedApkLibsDirectory * @param apkLibraryArtifact * @return */ public static String getLibraryUnpackDirectory( File unpackedApkLibsDirectory, Artifact apkLibraryArtifact ) { return unpackedApkLibsDirectory.getAbsolutePath() + "/" + apkLibraryArtifact.getId().replace( ":", "_" ); } /** *

Returns the Android NDK to use.

*

*

Current implementation looks for <ndk><path> configuration in pom, then System * property android.ndk.path, then environment variable ANDROID_NDK_HOME. *

*

This is where we collect all logic for how to lookup where it is, and which one to choose. The lookup is * based on available parameters. This method should be the only one you should need to look at to understand how * the Android NDK is chosen, and from where on disk.

* * @return the Android NDK to use. * @throws org.apache.maven.plugin.MojoExecutionException * if no Android NDK path configuration is available at all. */ protected AndroidNdk getAndroidNdk() throws MojoExecutionException { File chosenNdkPath = null; // There is no tag in the pom. if ( ndkPath != null ) { // -Dandroid.ndk.path is set on command line, or via ... chosenNdkPath = ndkPath; } else if ( ndk != null && ndk.getPath() != null ) { chosenNdkPath = ndk.getPath(); } else { // No -Dandroid.ndk.path is set on command line, or via ... chosenNdkPath = new File( getAndroidNdkHomeOrThrow() ); } return new AndroidNdk( chosenNdkPath ); } /** * * @return * @throws MojoExecutionException */ private String getAndroidNdkHomeOrThrow() throws MojoExecutionException { final String androidHome = System.getenv( ENV_ANDROID_NDK_HOME ); if ( isBlank( androidHome ) ) { throw new MojoExecutionException( "No Android NDK path could be found. You may configure it in the pom " + "using ... or ... or on " + "command-line using -Dandroid.ndk.path=... or by setting environment variable " + ENV_ANDROID_NDK_HOME ); } return androidHome; } /** * Get the resource directories if defined or the resource directory * @return */ public File[] getResourceOverlayDirectories() { File[] overlayDirectories; if ( resourceOverlayDirectories == null || resourceOverlayDirectories.length == 0 ) { overlayDirectories = new File[]{ resourceOverlayDirectory }; } else { overlayDirectories = resourceOverlayDirectories; } return overlayDirectories; } private abstract class DoThread extends Thread { private MojoFailureException failure; private MojoExecutionException execution; public final void run() { try { runDo(); } catch ( MojoFailureException e ) { failure = e; } catch ( MojoExecutionException e ) { execution = e; } } protected abstract void runDo() throws MojoFailureException, MojoExecutionException; } }




© 2015 - 2024 Weber Informatics LLC | Privacy Policy