com.jayway.maven.plugins.android.AbstractAndroidMojo Maven / Gradle / Ivy
/*
* 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 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.sonatype.aether.RepositorySystem;
import org.sonatype.aether.RepositorySystemSession;
import org.sonatype.aether.repository.LocalRepository;
import org.sonatype.aether.repository.RemoteRepository;
import java.io.File;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.*;
import java.util.concurrent.atomic.AtomicBoolean;
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);
}
/**
* 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 overriden
* 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;
/**
* @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 device to connect to, by serial number. Special values "usb" and "emulator" are also valid, for
* selecting the only USB connected device or the only running emulator, respectively.
*
* @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;
/**
* 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 as well as 3, 4, 5, 6, 7, 8. 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 hardcoded 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 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 envANDROID_HOME;
/**
* 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 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;
/**
* Whether to execute tests only in given packages
*
* <testPackages>
* <testPackage>your.package.name</testPackage>
* </testPackages>
*
*
* @parameter
*/
protected List testPackages;
/**
* Whether to execute test classes which are specified.
*
* <testClasses>
* <testClass>your.package.name.YourTestClass</testClass>
* </testClasses>
*
*
* @parameter
*/
protected List testClasses;
private static final Object adbLock = new Object();
private static boolean adbInitialized = false;
/**
* @return Given test classes as a comma separated string
*/
@SuppressWarnings("unchecked")
protected String buildTestClassesString() {
return buildCommaSeperatedString(testClasses);
}
/**
* @return Given test packages as a comma separated string
*/
@SuppressWarnings("unchecked")
protected String buildTestPackagesString() {
return buildCommaSeperatedString(testPackages);
}
/**
* Helper method to build a comma separated string from a list.
* Blank strings are filtered out
*
* @param lines A list of strings
* @return Comma separated String from given list
*/
protected static String buildCommaSeperatedString(List lines) {
if(lines == null || lines.size() == 0) {
return null;
}
List strings = new ArrayList(lines.size());
for(String str : lines) { // filter out blank strings
if(StringUtils.isNotBlank(str)) {
strings.add(StringUtils.trimToEmpty(str));
}
}
return StringUtils.join(strings, ",");
}
/**
* 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;
}
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;
}
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
*/
private AndroidDebugBridge initAndroidDebugBridge() throws MojoExecutionException {
synchronized (adbLock) {
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;
while (trials > 0) {
try {
Thread.sleep(50);
} catch (InterruptedException e) {
e.printStackTrace();
}
if (adb.isConnected()) {
break;
}
trials--;
}
}
/**
* 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 {
try {
device.installPackage(apkFile.getAbsolutePath(), true);
getLog().info("Successfully installed "
+ apkFile.getAbsolutePath() + " to "
+ device.getSerialNumber() + " (avdName="
+ device.getAvdName() + ")");
} catch (InstallException e) {
throw new MojoExecutionException("Install of "
+ apkFile.getAbsolutePath() + "failed.", e);
}
}
});
}
/**
* Determines which {@link IDevice}(s) to use, and performs the callback action on it/them.
*
* @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()) {
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) {
if (StringUtils.isNotBlank(device)) {
getLog().info("android.device parameter set to " + device);
for (IDevice idevice : devices) {
// use specified device or all emulators or all devices
if (("emulator".equals(device) && idevice.isEmulator())
|| ("usb".equals(device) && !idevice.isEmulator())
|| (idevice.getAvdName() != null && idevice.getAvdName().equals(device))) {
deviceCallback.doWithDevice(idevice);
}
}
} else {
getLog().info("android.device parameter not set, using all attached devices");
for (IDevice idevice : devices) {
deviceCallback.doWithDevice(idevice);
}
}
} else {
throw new MojoExecutionException("No online devices attached.");
}
} else {
throw new MojoExecutionException("Android Debug Bridge is not connected.");
}
}
/**
* Adds relevant parameter to the parameters list for chosen device.
*
* @param commands the parameters to be used with the {@code adb} command
* @param device the device to be used
*/
protected void addDeviceParameter(List commands, IDevice device) {
commands.add("-s");
commands.add(device.getSerialNumber());
}
/**
* 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 {
try {
device.uninstallPackage(packageName);
getLog().info("Successfully uninstalled " + packageName +
" from " + device.getSerialNumber() + " (avdName="
+ device.getAvdName() + ")");
result.set(true);
} catch (InstallException e) {
result.set(false);
throw new MojoExecutionException("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().getPathForTool("aapt") + " " + commands.toString());
try {
executor.executeCommand(getAndroidSdk().getPathForTool("aapt"), 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;
}
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;
}
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 <sdk><path>
configuration in pom, then System
* property android.sdk.path
, 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);
}
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 pom using ... or ... or on command-line using -Dandroid.sdk.path=... or by setting environment variable " + ENV_ANDROID_HOME);
}
return androidHome;
}
protected String getLibraryUnpackDirectory(Artifact apkLibraryArtifact) {
return unpackedApkLibsDirectory.getAbsolutePath() + "/" + apkLibraryArtifact.getId().replace(":", "_");
}
}