blob: e283fd6b2a140071e3cfe55ae3769637338d1ab6 [file] [log] [blame]
/*
* Copyright (C) 2008 The Android Open Source Project
*
* 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.android.sdkmanager;
import com.android.SdkConstants;
import com.android.annotations.NonNull;
import com.android.annotations.VisibleForTesting;
import com.android.annotations.VisibleForTesting.Visibility;
import com.android.io.FileWrapper;
import com.android.prefs.AndroidLocation;
import com.android.prefs.AndroidLocation.AndroidLocationException;
import com.android.sdklib.IAndroidTarget;
import com.android.sdklib.IAndroidTarget.IOptionalLibrary;
import com.android.sdklib.ISystemImage;
import com.android.sdklib.SdkManager;
import com.android.sdklib.internal.avd.AvdInfo;
import com.android.sdklib.internal.avd.AvdManager;
import com.android.sdklib.internal.avd.HardwareProperties;
import com.android.sdklib.internal.avd.HardwareProperties.HardwareProperty;
import com.android.sdklib.internal.project.ProjectCreator;
import com.android.sdklib.internal.project.ProjectCreator.OutputLevel;
import com.android.sdklib.internal.project.ProjectProperties;
import com.android.sdklib.internal.project.ProjectProperties.PropertyType;
import com.android.sdklib.internal.project.ProjectPropertiesWorkingCopy;
import com.android.sdklib.internal.repository.DownloadCache;
import com.android.sdklib.internal.repository.DownloadCache.Strategy;
import com.android.sdklib.internal.repository.packages.PlatformToolPackage;
import com.android.sdklib.internal.repository.packages.ToolPackage;
import com.android.sdklib.internal.repository.updater.SdkUpdaterNoWindow;
import com.android.sdklib.repository.SdkAddonConstants;
import com.android.sdklib.repository.SdkRepoConstants;
import com.android.sdkuilib.internal.widgets.MessageBoxLog;
import com.android.sdkuilib.repository.AvdManagerWindow;
import com.android.sdkuilib.repository.AvdManagerWindow.AvdInvocationContext;
import com.android.sdkuilib.repository.SdkUpdaterWindow;
import com.android.sdkuilib.repository.SdkUpdaterWindow.SdkInvocationContext;
import com.android.utils.ILogger;
import com.android.utils.IReaderLogger;
import com.android.utils.Pair;
import com.android.xml.AndroidXPathFactory;
import org.eclipse.swt.widgets.Display;
import org.xml.sax.InputSource;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;
import java.util.TreeSet;
import java.util.concurrent.atomic.AtomicBoolean;
import javax.xml.xpath.XPath;
import javax.xml.xpath.XPathExpressionException;
/**
* Main class for the 'android' application.
*/
public class Main {
/** Java property that defines the location of the sdk/tools directory. */
public final static String TOOLSDIR = "com.android.sdkmanager.toolsdir";
/** Java property that defines the working directory. On Windows the current working directory
* is actually the tools dir, in which case this is used to get the original CWD. */
private final static String WORKDIR = "com.android.sdkmanager.workdir";
/** Value returned by {@link #resolveTargetName(String)} when the target id does not match. */
private final static int INVALID_TARGET_ID = 0;
private final static String[] BOOLEAN_YES_REPLIES = new String[] { "yes", "y" };
private final static String[] BOOLEAN_NO_REPLIES = new String[] { "no", "n" };
/** Path to the SDK folder. This is the parent of {@link #TOOLSDIR}. */
private String mOsSdkFolder;
/** Logger object. Use this to print normal output, warnings or errors. */
private ILogger mSdkLog;
/** The SDK manager parses the SDK folder and gives access to the content. */
private SdkManager mSdkManager;
/** Command-line processor with options specific to SdkManager. */
private SdkCommandLine mSdkCommandLine;
/** The working directory, either null or set to an existing absolute canonical directory. */
private File mWorkDir;
public static void main(String[] args) {
new Main().run(args);
}
/** Used by tests to set the sdk manager. */
@VisibleForTesting(visibility=Visibility.PRIVATE)
void setSdkManager(SdkManager sdkManager) {
mSdkManager = sdkManager;
}
/**
* Runs the sdk manager app
*/
private void run(String[] args) {
createLogger();
init();
mSdkCommandLine.parseArgs(args);
parseSdk();
doAction();
}
/**
* Creates the {@link #mSdkLog} object.
* This must be done before {@link #init()} as it will be used to report errors.
* This logger prints to the attached console.
*/
private void createLogger() {
mSdkLog = new IReaderLogger() {
@Override
public void error(Throwable t, String errorFormat, Object... args) {
if (errorFormat != null) {
System.err.printf("Error: " + errorFormat, args);
if (!errorFormat.endsWith("\n")) {
System.err.printf("\n");
}
}
if (t != null) {
System.err.printf("Error: %s\n", t.getMessage());
}
}
@Override
public void warning(@NonNull String warningFormat, Object... args) {
if (mSdkCommandLine.isVerbose()) {
System.out.printf("Warning: " + warningFormat, args);
if (!warningFormat.endsWith("\n")) {
System.out.printf("\n");
}
}
}
@Override
public void info(@NonNull String msgFormat, Object... args) {
System.out.printf(msgFormat, args);
}
@Override
public void verbose(@NonNull String msgFormat, Object... args) {
System.out.printf(msgFormat, args);
}
/**
* Used by UpdaterData.acceptLicense() to prompt for license acceptance
* when updating the SDK from the command-line.
* <p/>
* {@inheritDoc}
*/
@Override
public int readLine(byte[] inputBuffer) throws IOException {
return System.in.read(inputBuffer);
}
};
}
/** For testing */
public void setLogger(ILogger logger) {
mSdkLog = logger;
}
/**
* Init the application by making sure the SDK path is available and
* doing basic parsing of the SDK.
*/
private void init() {
mSdkCommandLine = new SdkCommandLine(mSdkLog);
// We get passed a property for the tools dir
String toolsDirProp = System.getProperty(TOOLSDIR);
if (toolsDirProp == null) {
// for debugging, it's easier to override using the process environment
toolsDirProp = System.getenv(TOOLSDIR);
}
if (toolsDirProp != null) {
// got back a level for the SDK folder
File tools;
if (toolsDirProp.length() > 0) {
tools = new File(toolsDirProp);
mOsSdkFolder = tools.getParent();
} else {
try {
tools = new File(".").getCanonicalFile();
mOsSdkFolder = tools.getParent();
} catch (IOException e) {
// Will print an error below since mSdkFolder is not defined
}
}
}
if (mOsSdkFolder == null) {
errorAndExit("The tools directory property is not set, please make sure you are executing %1$s",
SdkConstants.androidCmdName());
}
// We might get passed a property for the working directory
// Either it is a valid directory and mWorkDir is set to it's absolute canonical value
// or mWorkDir remains null.
String workDirProp = System.getProperty(WORKDIR);
if (workDirProp == null) {
workDirProp = System.getenv(WORKDIR);
}
if (workDirProp != null) {
// This should be a valid directory
mWorkDir = new File(workDirProp);
try {
mWorkDir = mWorkDir.getCanonicalFile().getAbsoluteFile();
} catch (IOException e) {
mWorkDir = null;
}
if (mWorkDir == null || !mWorkDir.isDirectory()) {
errorAndExit("The working directory does not seem to be valid: '%1$s", workDirProp);
}
}
}
/**
* Does the basic SDK parsing required for all actions
*/
private void parseSdk() {
mSdkManager = SdkManager.createManager(mOsSdkFolder, mSdkLog);
if (mSdkManager == null) {
errorAndExit("Unable to parse SDK content.");
}
}
/**
* Actually do an action...
*/
private void doAction() {
if (mSdkCommandLine.hasClearCache()) {
DownloadCache d = new DownloadCache(Strategy.SERVE_CACHE);
d.clearCache();
mSdkLog.info("SDK Manager repository: manifest cache cleared.\n");
}
String verb = mSdkCommandLine.getVerb();
String directObject = mSdkCommandLine.getDirectObject();
if (SdkCommandLine.VERB_LIST.equals(verb)) {
// list action.
if (SdkCommandLine.OBJECT_TARGET.equals(directObject)) {
displayTargetList();
} else if (SdkCommandLine.OBJECT_AVD.equals(directObject)) {
displayAvdList();
} else if (SdkCommandLine.OBJECT_SDK.equals(directObject)) {
displayRemoteSdkListNoUI();
} else {
displayTargetList();
displayAvdList();
}
} else if (SdkCommandLine.VERB_CREATE.equals(verb)) {
if (SdkCommandLine.OBJECT_AVD.equals(directObject)) {
createAvd();
} else if (SdkCommandLine.OBJECT_PROJECT.equals(directObject)) {
createProject(false /*library*/);
} else if (SdkCommandLine.OBJECT_TEST_PROJECT.equals(directObject)) {
createTestProject();
} else if (SdkCommandLine.OBJECT_LIB_PROJECT.equals(directObject)) {
createProject(true /*library*/);
} else if (SdkCommandLine.OBJECT_UITEST_PROJECT.equals(directObject)) {
createUiTestProject();
}
} else if (SdkCommandLine.VERB_UPDATE.equals(verb)) {
if (SdkCommandLine.OBJECT_AVD.equals(directObject)) {
updateAvd();
} else if (SdkCommandLine.OBJECT_PROJECT.equals(directObject)) {
updateProject(false /*library*/);
} else if (SdkCommandLine.OBJECT_TEST_PROJECT.equals(directObject)) {
updateTestProject();
} else if (SdkCommandLine.OBJECT_LIB_PROJECT.equals(directObject)) {
updateProject(true /*library*/);
} else if (SdkCommandLine.OBJECT_SDK.equals(directObject)) {
if (mSdkCommandLine.getFlagNoUI(verb)) {
updateSdkNoUI();
} else {
showSdkManagerWindow();
}
} else if (SdkCommandLine.OBJECT_ADB.equals(directObject)) {
updateAdb();
}
} else if (SdkCommandLine.VERB_SDK.equals(verb)) {
showSdkManagerWindow();
} else if (SdkCommandLine.VERB_AVD.equals(verb)) {
showAvdManagerWindow();
} else if (SdkCommandLine.VERB_DELETE.equals(verb) &&
SdkCommandLine.OBJECT_AVD.equals(directObject)) {
deleteAvd();
} else if (SdkCommandLine.VERB_MOVE.equals(verb) &&
SdkCommandLine.OBJECT_AVD.equals(directObject)) {
moveAvd();
} else if (verb == null && directObject == null) {
showSdkManagerWindow();
} else {
mSdkCommandLine.printHelpAndExit(null);
}
}
/**
* Display the main SDK Manager app window
*/
private void showSdkManagerWindow() {
try {
MessageBoxLog errorLogger = new MessageBoxLog(
"SDK Manager",
Display.getCurrent(),
true /*logErrorsOnly*/);
SdkUpdaterWindow window = new SdkUpdaterWindow(
null /* parentShell */,
errorLogger,
mOsSdkFolder,
SdkInvocationContext.STANDALONE);
window.open();
errorLogger.displayResult(true);
} catch (Exception e) {
e.printStackTrace();
}
}
/**
* Display the main AVD Manager app window
*/
private void showAvdManagerWindow() {
try {
MessageBoxLog errorLogger = new MessageBoxLog(
"AVD Manager",
Display.getCurrent(),
true /*logErrorsOnly*/);
AvdManagerWindow window = new AvdManagerWindow(
null /* parentShell */,
errorLogger,
mOsSdkFolder,
AvdInvocationContext.STANDALONE);
window.open();
errorLogger.displayResult(true);
} catch (Exception e) {
e.printStackTrace();
}
}
private void displayRemoteSdkListNoUI() {
boolean force = mSdkCommandLine.getFlagForce();
boolean useHttp = mSdkCommandLine.getFlagNoHttps();
boolean all = mSdkCommandLine.getFlagAll();
boolean extended = mSdkCommandLine.getFlagExtended();
String proxyHost = mSdkCommandLine.getParamProxyHost();
String proxyPort = mSdkCommandLine.getParamProxyPort();
boolean obsolete = mSdkCommandLine.getFlagObsolete();
all |= obsolete;
SdkUpdaterNoWindow upd = new SdkUpdaterNoWindow(
mOsSdkFolder,
mSdkManager,
mSdkLog,
force,
useHttp,
proxyHost,
proxyPort);
upd.listRemotePackages(all, extended);
if (obsolete) {
mSdkLog.info("Note: Flag --obsolete is deprecated and will be removed in the next version.\n Please use --all instead.\n");
}
}
/**
* Updates the whole SDK without any UI, just using console output.
*/
private void updateSdkNoUI() {
boolean force = mSdkCommandLine.getFlagForce();
boolean useHttp = mSdkCommandLine.getFlagNoHttps();
boolean dryMode = mSdkCommandLine.getFlagDryMode();
boolean all = mSdkCommandLine.getFlagAll();
String proxyHost = mSdkCommandLine.getParamProxyHost();
String proxyPort = mSdkCommandLine.getParamProxyPort();
// This flag is not yet supported in Tools R22.
// String acceptLicense = mSdkCommandLine.getParamAcceptLicense();
String acceptLicense = null;
boolean obsolete = mSdkCommandLine.getFlagObsolete();
all |= obsolete;
// Check filter types.
Pair<String, ArrayList<String>> filterResult =
checkFilterValues(mSdkCommandLine.getParamFilter());
if (filterResult.getFirst() != null) {
// We got an error.
errorAndExit(filterResult.getFirst());
}
SdkUpdaterNoWindow upd = new SdkUpdaterNoWindow(
mOsSdkFolder,
mSdkManager,
mSdkLog,
force,
useHttp,
proxyHost,
proxyPort);
upd.updateAll(filterResult.getSecond(), all, dryMode, acceptLicense);
if (obsolete) {
mSdkLog.info("Note: Flag --obsolete is deprecated and will be removed in the next version.\n Please use --all instead.\n");
}
}
/**
* Checks the values from the filter parameter and returns a tuple
* (error , accepted values). Either error is null and accepted values is not,
* or the reverse.
* <p/>
* Note that this is a quick sanity check of the --filter parameter *before* we
* start loading the remote repository sources. Loading the remotes takes a while
* so it's worth doing a quick sanity check before hand.
*
* @param filter A comma-separated list of keywords
* @return A pair <error string, usable values>, only one must be null and the other non-null.
*/
@VisibleForTesting(visibility=Visibility.PRIVATE)
Pair<String, ArrayList<String>> checkFilterValues(String filter) {
ArrayList<String> pkgFilter = new ArrayList<String>();
if (filter != null && filter.length() > 0) {
// Available types
Set<String> filterTypes = new TreeSet<String>();
filterTypes.addAll(Arrays.asList(SdkRepoConstants.NODES));
filterTypes.addAll(Arrays.asList(SdkAddonConstants.NODES));
for (String t : filter.split(",")) { //$NON-NLS-1$
if (t == null) {
continue;
}
t = t.trim();
if (t.length() <= 0) {
continue;
}
if (t.indexOf('-') > 0 ||
t.equals(ToolPackage.INSTALL_ID) ||
t.equals(PlatformToolPackage.INSTALL_ID)) {
// Heuristic: if the filter name contains a dash, it is probably
// a variable package install id. Since we haven't loaded the remote
// repositories we can't validate it yet, so just accept it.
pkgFilter.add(t);
continue;
}
if (t.replaceAll("[0-9]+", "").length() == 0) { //$NON-NLS-1$ //$NON-NLS-2$
// If the filter argument *only* contains digits, accept it.
// It's probably an index for the remote repository list,
// which we can't validate yet.
pkgFilter.add(t);
continue;
}
if (filterTypes.contains(t)) {
pkgFilter.add(t);
continue;
}
return Pair.of(
String.format(
"Unknown package filter type '%1$s'.\nAccepted values are: %2$s",
t,
Arrays.toString(filterTypes.toArray())),
null);
}
}
return Pair.of(null, pkgFilter);
}
/**
* Returns a configured {@link ProjectCreator} instance.
*/
private ProjectCreator getProjectCreator() {
ProjectCreator creator = new ProjectCreator(mSdkManager, mOsSdkFolder,
mSdkCommandLine.isVerbose() ? OutputLevel.VERBOSE :
mSdkCommandLine.isSilent() ? OutputLevel.SILENT :
OutputLevel.NORMAL,
mSdkLog);
return creator;
}
/**
* Creates a new Android project based on command-line parameters
*/
private void createProject(boolean library) {
String directObject = library? SdkCommandLine.OBJECT_LIB_PROJECT :
SdkCommandLine.OBJECT_PROJECT;
// get the target and try to resolve it.
int targetId = resolveTargetName(mSdkCommandLine.getParamTargetId());
IAndroidTarget[] targets = mSdkManager.getTargets();
if (targetId == INVALID_TARGET_ID || targetId > targets.length) {
errorAndExit("Target id is not valid. Use '%s list targets' to get the target ids.",
SdkConstants.androidCmdName());
}
IAndroidTarget target = targets[targetId - 1]; // target id is 1-based
ProjectCreator creator = getProjectCreator();
String projectDir = getProjectLocation(mSdkCommandLine.getParamLocationPath());
String projectName = mSdkCommandLine.getParamName();
String packageName = mSdkCommandLine.getParamProjectPackage(directObject);
String activityName = null;
if (library == false) {
activityName = mSdkCommandLine.getParamProjectActivity();
}
if (projectName != null &&
!ProjectCreator.RE_PROJECT_NAME.matcher(projectName).matches()) {
errorAndExit(
"Project name '%1$s' contains invalid characters.\nAllowed characters are: %2$s",
projectName, ProjectCreator.CHARS_PROJECT_NAME);
return;
}
if (activityName != null &&
!ProjectCreator.RE_ACTIVITY_NAME.matcher(activityName).matches()) {
errorAndExit(
"Activity name '%1$s' contains invalid characters.\nAllowed characters are: %2$s",
activityName, ProjectCreator.CHARS_ACTIVITY_NAME);
return;
}
if (packageName != null &&
!ProjectCreator.RE_PACKAGE_NAME.matcher(packageName).matches()) {
errorAndExit(
"Package name '%1$s' contains invalid characters.\n" +
"A package name must be constitued of two Java identifiers.\n" +
"Each identifier allowed characters are: %2$s",
packageName, ProjectCreator.CHARS_PACKAGE_NAME);
return;
}
creator.createProject(projectDir,
projectName,
packageName,
activityName,
target,
library,
null /*pathToMain*/);
}
/**
* Creates a new Android test project based on command-line parameters
*/
private void createTestProject() {
String projectDir = getProjectLocation(mSdkCommandLine.getParamLocationPath());
// first check the path of the parent project, and make sure it's valid.
String pathToMainProject = mSdkCommandLine.getParamTestProjectMain();
File parentProject = new File(pathToMainProject);
if (parentProject.isAbsolute() == false) {
// if the path is not absolute, we need to resolve it based on the
// destination path of the project
try {
parentProject = new File(projectDir, pathToMainProject).getCanonicalFile();
} catch (IOException e) {
errorAndExit("Unable to resolve Main project's directory: %1$s",
pathToMainProject);
return; // help Eclipse static analyzer understand we'll never execute the rest.
}
}
if (parentProject.isDirectory() == false) {
errorAndExit("Main project's directory does not exist: %1$s",
pathToMainProject);
return;
}
// now look for a manifest in there
File manifest = new File(parentProject, SdkConstants.FN_ANDROID_MANIFEST_XML);
if (manifest.isFile() == false) {
errorAndExit("No AndroidManifest.xml file found in the main project directory: %1$s",
parentProject.getAbsolutePath());
return;
}
// now query the manifest for the package file.
XPath xpath = AndroidXPathFactory.newXPath();
String packageName, activityName;
try {
packageName = xpath.evaluate("/manifest/@package",
new InputSource(new FileInputStream(manifest)));
mSdkLog.info("Found main project package: %1$s\n", packageName);
// now get the name of the first activity we find
activityName = xpath.evaluate("/manifest/application/activity[1]/@android:name",
new InputSource(new FileInputStream(manifest)));
// xpath will return empty string when there's no match
if (activityName == null || activityName.length() == 0) {
activityName = null;
} else {
mSdkLog.info("Found main project activity: %1$s\n", activityName);
}
} catch (FileNotFoundException e) {
// this shouldn't happen as we test it above.
errorAndExit("No AndroidManifest.xml file found in main project.");
return; // this is not strictly needed because errorAndExit will stop the execution,
// but this makes the java compiler happy, wrt to uninitialized variables.
} catch (XPathExpressionException e) {
// looks like the main manifest is not valid.
errorAndExit("Unable to parse main project manifest to get information.");
return; // this is not strictly needed because errorAndExit will stop the execution,
// but this makes the java compiler happy, wrt to uninitialized variables.
}
// now get the target hash
ProjectProperties p = ProjectProperties.load(parentProject.getAbsolutePath(),
PropertyType.PROJECT);
if (p == null) {
errorAndExit("Unable to load the main project's %1$s",
PropertyType.PROJECT.getFilename());
return;
}
String targetHash = p.getProperty(ProjectProperties.PROPERTY_TARGET);
if (targetHash == null) {
errorAndExit("Couldn't find the main project target");
return;
}
// and resolve it.
IAndroidTarget target = mSdkManager.getTargetFromHashString(targetHash);
if (target == null) {
errorAndExit(
"Unable to resolve main project target '%1$s'. You may want to install the platform in your SDK.",
targetHash);
return;
}
mSdkLog.info("Found main project target: %1$s\n", target.getFullName());
ProjectCreator creator = getProjectCreator();
String projectName = mSdkCommandLine.getParamName();
if (projectName != null &&
!ProjectCreator.RE_PROJECT_NAME.matcher(projectName).matches()) {
errorAndExit(
"Project name '%1$s' contains invalid characters.\nAllowed characters are: %2$s",
projectName, ProjectCreator.CHARS_PROJECT_NAME);
return;
}
creator.createProject(projectDir,
projectName,
packageName,
activityName,
target,
false /* library*/,
pathToMainProject);
}
/**
* Creates a new Android UI test project based on command-line parameters
*/
private void createUiTestProject() {
String projectDir = getProjectLocation(mSdkCommandLine.getParamLocationPath());
// get the target and try to resolve it.
int targetId = resolveTargetName(mSdkCommandLine.getParamTargetId());
IAndroidTarget[] targets = mSdkManager.getTargets();
if (targetId == INVALID_TARGET_ID || targetId > targets.length) {
errorAndExit("Target id is not valid. Use '%s list targets' to get the target ids.",
SdkConstants.androidCmdName());
}
IAndroidTarget target = targets[targetId - 1]; // target id is 1-based
if (target.getVersion().getApiLevel() < 16) {
errorAndExit("UI test projects can only target API 16 and above");
}
// get the project name
String projectName = mSdkCommandLine.getParamName();
// if none, use the folder name.
if (projectName == null) {
File f = new File(projectDir);
projectName = f.getName();
}
try {
// create src folder
File srcFolder = new File(projectDir, SdkConstants.FD_SOURCES);
srcFolder.mkdir();
// create the local.prop file.
// location of the SDK goes in localProperty
ProjectPropertiesWorkingCopy localProperties = ProjectProperties.create(projectDir,
PropertyType.LOCAL);
localProperties.setProperty(ProjectProperties.PROPERTY_SDK, mOsSdkFolder);
localProperties.save();
// target goes in project properties
ProjectPropertiesWorkingCopy projectProperties = ProjectProperties.create(projectDir,
PropertyType.PROJECT);
projectProperties.setProperty(ProjectProperties.PROPERTY_TARGET, target.hashString());
projectProperties.save();
// copy the build file using the keywords replacement.
final Map<String, String> keywords = new HashMap<String, String>();
keywords.put(ProjectCreator.PH_PROJECT_NAME, projectName);
ProjectCreator creator = getProjectCreator();
creator.installTemplate("uibuild.template",
new File(projectDir, SdkConstants.FN_BUILD_XML),
keywords);
} catch (Exception e) {
mSdkLog.error(e, null);
}
}
/**
* Updates an existing Android project based on command-line parameters
* @param library whether the project is a library project.
*/
private void updateProject(boolean library) {
// get the target and try to resolve it.
IAndroidTarget target = null;
String targetStr = mSdkCommandLine.getParamTargetId();
// For "update project" the target parameter is optional so having null is acceptable.
// However if there's a value, it must be valid.
if (targetStr != null) {
IAndroidTarget[] targets = mSdkManager.getTargets();
int targetId = resolveTargetName(targetStr);
if (targetId == INVALID_TARGET_ID || targetId > targets.length) {
errorAndExit("Target id '%1$s' is not valid. Use '%2$s list targets' to get the target ids.",
targetStr,
SdkConstants.androidCmdName());
}
target = targets[targetId - 1]; // target id is 1-based
}
ProjectCreator creator = getProjectCreator();
String projectDir = getProjectLocation(mSdkCommandLine.getParamLocationPath());
String libraryPath = library ? null :
mSdkCommandLine.getParamProjectLibrary(SdkCommandLine.OBJECT_PROJECT);
creator.updateProject(projectDir,
target,
mSdkCommandLine.getParamName(),
libraryPath);
if (library == false) {
boolean doSubProjects = mSdkCommandLine.getParamSubProject();
boolean couldHaveDone = false;
// If there are any sub-folders with a manifest, try to update them as projects
// too. This will take care of updating any underlying test project even if the
// user changed the folder name.
File[] files = new File(projectDir).listFiles();
if (files != null) {
for (File dir : files) {
if (dir.isDirectory() &&
new File(dir, SdkConstants.FN_ANDROID_MANIFEST_XML).isFile()) {
if (doSubProjects) {
creator.updateProject(dir.getPath(),
target,
mSdkCommandLine.getParamName(),
null /*libraryPath*/);
} else {
couldHaveDone = true;
}
}
}
}
if (couldHaveDone) {
mSdkLog.info(
"It seems that there are sub-projects. If you want to update them\nplease use the --%1$s parameter.\n",
SdkCommandLine.KEY_SUBPROJECTS);
}
}
}
/**
* Updates an existing test project with a new path to the main project.
*/
private void updateTestProject() {
ProjectCreator creator = getProjectCreator();
String projectDir = getProjectLocation(mSdkCommandLine.getParamLocationPath());
creator.updateTestProject(projectDir, mSdkCommandLine.getParamTestProjectMain(),
mSdkManager);
}
/**
* Adjusts the project location to make it absolute & canonical relative to the
* working directory, if any.
*
* @return The project absolute path relative to {@link #mWorkDir} or the original
* newProjectLocation otherwise.
*/
private String getProjectLocation(String newProjectLocation) {
// If the new project location is absolute, use it as-is
File projectDir = new File(newProjectLocation);
if (projectDir.isAbsolute()) {
return newProjectLocation;
}
// if there's no working directory, just use the project location as-is.
if (mWorkDir == null) {
return newProjectLocation;
}
// Combine then and get an absolute canonical directory
try {
projectDir = new File(mWorkDir, newProjectLocation).getCanonicalFile();
return projectDir.getPath();
} catch (IOException e) {
errorAndExit("Failed to combine working directory '%1$s' with project location '%2$s': %3$s",
mWorkDir.getPath(),
newProjectLocation,
e.getMessage());
return null;
}
}
/**
* Displays the list of available Targets (Platforms and Add-ons)
*/
@VisibleForTesting(visibility=Visibility.PRIVATE)
void displayTargetList() {
// Compact output, suitable for scripts.
if (mSdkCommandLine != null && mSdkCommandLine.getFlagCompact()) {
char eol = mSdkCommandLine.getFlagEolNull() ? '\0' : '\n';
for (IAndroidTarget target : mSdkManager.getTargets()) {
mSdkLog.info("%1$s%2$c", target.hashString(), eol);
}
return;
}
mSdkLog.info("Available Android targets:\n");
int index = 1;
for (IAndroidTarget target : mSdkManager.getTargets()) {
mSdkLog.info("----------\n");
mSdkLog.info("id: %1$d or \"%2$s\"\n", index, target.hashString());
mSdkLog.info(" Name: %s\n", target.getName());
if (target.isPlatform()) {
mSdkLog.info(" Type: Platform\n");
mSdkLog.info(" API level: %s\n", target.getVersion().getApiString());
mSdkLog.info(" Revision: %d\n", target.getRevision());
} else {
mSdkLog.info(" Type: Add-On\n");
mSdkLog.info(" Vendor: %s\n", target.getVendor());
mSdkLog.info(" Revision: %d\n", target.getRevision());
if (target.getDescription() != null) {
mSdkLog.info(" Description: %s\n", target.getDescription());
}
mSdkLog.info(" Based on Android %s (API level %s)\n",
target.getVersionName(), target.getVersion().getApiString());
// display the optional libraries.
IOptionalLibrary[] libraries = target.getOptionalLibraries();
if (libraries != null) {
mSdkLog.info(" Libraries:\n");
for (IOptionalLibrary library : libraries) {
mSdkLog.info(" * %1$s (%2$s)\n",
library.getName(), library.getJarName());
mSdkLog.info(" %1$s\n", library.getDescription());
}
}
}
// get the target skins & ABIs
displaySkinList(target, " Skins: ");
displayAbiList (target, " ABIs : ");
if (target.getUsbVendorId() != IAndroidTarget.NO_USB_ID) {
mSdkLog.info(" Adds USB support for devices (Vendor: 0x%04X)\n",
target.getUsbVendorId());
}
index++;
}
}
/**
* Displays the skins valid for the given target.
*/
@VisibleForTesting(visibility=Visibility.PRIVATE)
void displaySkinList(IAndroidTarget target, String message) {
String[] skins = target.getSkins();
String defaultSkin = target.getDefaultSkin();
mSdkLog.info(message);
if (skins != null) {
boolean first = true;
for (String skin : skins) {
if (first == false) {
mSdkLog.info(", ");
} else {
first = false;
}
mSdkLog.info(skin);
if (skin.equals(defaultSkin)) {
mSdkLog.info(" (default)");
}
}
mSdkLog.info("\n");
} else {
mSdkLog.info("no skins.\n");
}
}
/**
* Displays the ABIs valid for the given target.
*/
@VisibleForTesting(visibility=Visibility.PRIVATE)
void displayAbiList(IAndroidTarget target, String message) {
ISystemImage[] systemImages = target.getSystemImages();
mSdkLog.info(message);
if (systemImages.length > 0) {
boolean first = true;
for (ISystemImage si : systemImages) {
if (first == false) {
mSdkLog.info(", ");
} else {
first = false;
}
mSdkLog.info(si.getAbiType());
}
mSdkLog.info("\n");
} else {
mSdkLog.info("no ABIs.\n");
}
}
/**
* Displays the list of available AVDs for the given AvdManager.
*
* @param avdManager
*/
@VisibleForTesting(visibility=Visibility.PRIVATE)
void displayAvdList(AvdManager avdManager) {
AvdInfo[] avds = avdManager.getValidAvds();
// Compact output, suitable for scripts.
if (mSdkCommandLine != null && mSdkCommandLine.getFlagCompact()) {
char eol = mSdkCommandLine.getFlagEolNull() ? '\0' : '\n';
for (int index = 0 ; index < avds.length ; index++) {
AvdInfo info = avds[index];
mSdkLog.info("%1$s%2$c", info.getName(), eol);
}
return;
}
mSdkLog.info("Available Android Virtual Devices:\n");
for (int index = 0 ; index < avds.length ; index++) {
AvdInfo info = avds[index];
if (index > 0) {
mSdkLog.info("---------\n");
}
mSdkLog.info(" Name: %s\n", info.getName());
mSdkLog.info(" Path: %s\n", info.getDataFolderPath());
// get the target of the AVD
IAndroidTarget target = info.getTarget();
if (target.isPlatform()) {
mSdkLog.info(" Target: %s (API level %s)\n", target.getName(),
target.getVersion().getApiString());
} else {
mSdkLog.info(" Target: %s (%s)\n", target.getName(), target
.getVendor());
mSdkLog.info(" Based on Android %s (API level %s)\n",
target.getVersionName(), target.getVersion().getApiString());
}
mSdkLog.info(" ABI: %s\n", info.getAbiType());
// display some extra values.
Map<String, String> properties = info.getProperties();
if (properties != null) {
String skin = properties.get(AvdManager.AVD_INI_SKIN_NAME);
if (skin != null) {
mSdkLog.info(" Skin: %s\n", skin);
}
String sdcard = properties.get(AvdManager.AVD_INI_SDCARD_SIZE);
if (sdcard == null) {
sdcard = properties.get(AvdManager.AVD_INI_SDCARD_PATH);
}
if (sdcard != null) {
mSdkLog.info(" Sdcard: %s\n", sdcard);
}
String snapshot = properties.get(AvdManager.AVD_INI_SNAPSHOT_PRESENT);
if (snapshot != null) {
mSdkLog.info("Snapshot: %s\n", snapshot);
}
}
}
// Are there some unused AVDs?
AvdInfo[] badAvds = avdManager.getBrokenAvds();
if (badAvds.length == 0) {
return;
}
mSdkLog.info("\nThe following Android Virtual Devices could not be loaded:\n");
boolean needSeparator = false;
for (AvdInfo info : badAvds) {
if (needSeparator) {
mSdkLog.info("---------\n");
}
mSdkLog.info(" Name: %s\n", info.getName() == null ? "--" : info.getName());
mSdkLog.info(" Path: %s\n",
info.getDataFolderPath() == null ? "--" : info.getDataFolderPath());
String error = info.getErrorMessage();
mSdkLog.info(" Error: %s\n", error == null ? "Uknown error" : error);
needSeparator = true;
}
}
/**
* Displays the list of available AVDs.
*/
private void displayAvdList() {
try {
AvdManager avdManager = AvdManager.getInstance(mSdkManager, mSdkLog);
displayAvdList(avdManager);
} catch (AndroidLocationException e) {
errorAndExit(e.getMessage());
}
}
/**
* Creates a new AVD. This is a text based creation with command line prompt.
*/
private void createAvd() {
// find a matching target
int targetId = resolveTargetName(mSdkCommandLine.getParamTargetId());
IAndroidTarget[] targets = mSdkManager.getTargets();
if (targetId == INVALID_TARGET_ID || targetId > targets.length) {
errorAndExit("Target id is not valid. Use '%s list targets' to get the target ids.",
SdkConstants.androidCmdName());
}
IAndroidTarget target = targets[targetId-1]; // target id is 1-based
try {
boolean removePrevious = mSdkCommandLine.getFlagForce();
AvdManager avdManager = AvdManager.getInstance(mSdkManager, mSdkLog);
String avdName = mSdkCommandLine.getParamName();
if (!AvdManager.RE_AVD_NAME.matcher(avdName).matches()) {
errorAndExit(
"AVD name '%1$s' contains invalid characters.\nAllowed characters are: %2$s",
avdName, AvdManager.CHARS_AVD_NAME);
return;
}
AvdInfo info = avdManager.getAvd(avdName, false /*validAvdOnly*/);
if (info != null) {
if (removePrevious) {
mSdkLog.warning(
"Android Virtual Device '%s' already exists and will be replaced.",
avdName);
} else {
errorAndExit("Android Virtual Device '%s' already exists.\n" +
"Use --force if you want to replace it.",
avdName);
return;
}
}
String paramFolderPath = mSdkCommandLine.getParamLocationPath();
File avdFolder = null;
if (paramFolderPath != null) {
avdFolder = new File(paramFolderPath);
} else {
avdFolder = AvdInfo.getDefaultAvdFolder(avdManager, avdName);
}
// Validate skin is either default (empty) or NNNxMMM or a valid skin name.
Map<String, String> skinHardwareConfig = null;
String skin = mSdkCommandLine.getParamSkin();
if (skin != null && skin.length() == 0) {
skin = null;
}
if (skin != null && target != null) {
boolean valid = false;
// Is it a know skin name for this target?
for (String s : target.getSkins()) {
if (skin.equalsIgnoreCase(s)) {
skin = s; // Make skin names case-insensitive.
valid = true;
// get the hardware properties for this skin
File skinFolder = avdManager.getSkinPath(skin, target);
FileWrapper skinHardwareFile = new FileWrapper(skinFolder,
AvdManager.HARDWARE_INI);
if (skinHardwareFile.isFile()) {
skinHardwareConfig = ProjectProperties.parsePropertyFile(
skinHardwareFile, mSdkLog);
}
break;
}
}
// Is it NNNxMMM?
if (!valid) {
valid = AvdManager.NUMERIC_SKIN_SIZE.matcher(skin).matches();
}
if (!valid) {
displaySkinList(target, "Valid skins: ");
errorAndExit("'%s' is not a valid skin name or size (NNNxMMM)", skin);
return;
}
}
String abiType = mSdkCommandLine.getParamAbi();
if (target != null && (abiType == null || abiType.length() == 0)) {
ISystemImage[] systemImages = target.getSystemImages();
if (systemImages != null && systemImages.length == 1) {
// Auto-select the single ABI available
abiType = systemImages[0].getAbiType();
mSdkLog.info("Auto-selecting single ABI %1$s\n", abiType);
} else {
displayAbiList(target, "Valid ABIs: ");
errorAndExit("This platform has more than one ABI. Please specify one using --%1$s.",
SdkCommandLine.KEY_ABI);
}
}
Map<String, String> hardwareConfig = null;
if (target != null && target.isPlatform()) {
try {
hardwareConfig = promptForHardware(target, skinHardwareConfig);
} catch (IOException e) {
errorAndExit(e.getMessage());
}
}
@SuppressWarnings("unused") // oldAvdInfo is never read, yet useful for debugging
AvdInfo oldAvdInfo = null;
if (removePrevious) {
oldAvdInfo = avdManager.getAvd(avdName, false /*validAvdOnly*/);
}
@SuppressWarnings("unused") // newAvdInfo is never read, yet useful for debugging
AvdInfo newAvdInfo = avdManager.createAvd(avdFolder,
avdName,
target,
abiType,
skin,
mSdkCommandLine.getParamSdCard(),
hardwareConfig,
mSdkCommandLine.getFlagSnapshot(),
removePrevious,
false, //edit existing
mSdkLog);
} catch (AndroidLocationException e) {
errorAndExit(e.getMessage());
}
}
/**
* Delete an AVD. If the AVD name is not part of the available ones look for an
* invalid AVD (one not loaded due to some error) to remove it too.
*/
private void deleteAvd() {
try {
String avdName = mSdkCommandLine.getParamName();
AvdManager avdManager = AvdManager.getInstance(mSdkManager, mSdkLog);
AvdInfo info = avdManager.getAvd(avdName, false /*validAvdOnly*/);
if (info == null) {
errorAndExit("There is no Android Virtual Device named '%s'.", avdName);
return;
}
avdManager.deleteAvd(info, mSdkLog);
} catch (AndroidLocationException e) {
errorAndExit(e.getMessage());
}
}
/**
* Moves an AVD.
*/
private void moveAvd() {
try {
String avdName = mSdkCommandLine.getParamName();
AvdManager avdManager = AvdManager.getInstance(mSdkManager, mSdkLog);
AvdInfo info = avdManager.getAvd(avdName, true /*validAvdOnly*/);
if (info == null) {
errorAndExit("There is no valid Android Virtual Device named '%s'.", avdName);
return;
}
// This is a rename if there's a new name for the AVD
String newName = mSdkCommandLine.getParamMoveNewName();
if (newName != null && newName.equals(info.getName())) {
// same name, not actually a rename operation
newName = null;
}
// This is a move (of the data files) if there's a new location path
String paramFolderPath = mSdkCommandLine.getParamLocationPath();
if (paramFolderPath != null) {
// check if paths are the same. Use File methods to account for OS idiosyncrasies.
try {
File f1 = new File(paramFolderPath).getCanonicalFile();
File f2 = new File(info.getDataFolderPath()).getCanonicalFile();
if (f1.equals(f2)) {
// same canonical path, so not actually a move
paramFolderPath = null;
}
} catch (IOException e) {
// Fail to resolve canonical path. Fail now since a move operation might fail
// later and be harder to recover from.
errorAndExit(e.getMessage());
return;
}
}
if (newName == null && paramFolderPath == null) {
mSdkLog.warning("Move operation aborted: same AVD name, same canonical data path");
return;
}
// If a rename was requested and no data move was requested, check if the original
// data path is our default constructed from the AVD name. In this case we still want
// to rename that folder too.
if (newName != null && paramFolderPath == null) {
// Compute the original data path
File originalFolder = new File(
AndroidLocation.getFolder() + AndroidLocation.FOLDER_AVD,
info.getName() + AvdManager.AVD_FOLDER_EXTENSION);
if (info.getDataFolderPath() != null &&
originalFolder.equals(new File(info.getDataFolderPath()))) {
try {
// The AVD is using the default data folder path based on the AVD name.
// That folder needs to be adjusted to use the new name.
File f = new File(AndroidLocation.getFolder() + AndroidLocation.FOLDER_AVD,
newName + AvdManager.AVD_FOLDER_EXTENSION);
paramFolderPath = f.getCanonicalPath();
} catch (IOException e) {
// Fail to resolve canonical path. Fail now rather than later.
errorAndExit(e.getMessage());
}
}
}
// Check for conflicts
if (newName != null) {
if (avdManager.getAvd(newName, false /*validAvdOnly*/) != null) {
errorAndExit("There is already an AVD named '%s'.", newName);
return;
}
File ini = info.getIniFile();
if (ini.equals(AvdInfo.getDefaultIniFile(avdManager, newName))) {
errorAndExit("The AVD file '%s' is in the way.", ini.getCanonicalPath());
return;
}
}
if (paramFolderPath != null && new File(paramFolderPath).exists()) {
errorAndExit(
"There is already a file or directory at '%s'.\nUse --path to specify a different data folder.",
paramFolderPath);
}
avdManager.moveAvd(info, newName, paramFolderPath, mSdkLog);
} catch (AndroidLocationException e) {
errorAndExit(e.getMessage());
} catch (IOException e) {
errorAndExit(e.getMessage());
}
}
/**
* Updates a broken AVD.
*/
private void updateAvd() {
try {
String avdName = mSdkCommandLine.getParamName();
AvdManager avdManager = AvdManager.getInstance(mSdkManager, mSdkLog);
avdManager.updateAvd(avdName, mSdkLog);
} catch (AndroidLocationException e) {
errorAndExit(e.getMessage());
} catch (IOException e) {
errorAndExit(e.getMessage());
}
}
/**
* Updates adb with the USB devices declared in the SDK add-ons.
*/
private void updateAdb() {
try {
mSdkManager.updateAdb();
mSdkLog.info(
"adb has been updated. You must restart adb with the following commands\n" +
"\tadb kill-server\n" +
"\tadb start-server\n");
} catch (AndroidLocationException e) {
errorAndExit(e.getMessage());
} catch (IOException e) {
errorAndExit(e.getMessage());
}
}
/**
* Prompts the user to setup a hardware config for a Platform-based AVD.
* @throws IOException
*/
private Map<String, String> promptForHardware(IAndroidTarget createTarget,
Map<String, String> skinHardwareConfig) throws IOException {
byte[] readLineBuffer = new byte[256];
String result;
String defaultAnswer = "no";
mSdkLog.info("%s is a basic Android platform.\n", createTarget.getName());
mSdkLog.info("Do you wish to create a custom hardware profile [%s]",
defaultAnswer);
result = readLine(readLineBuffer).trim();
// handle default:
if (result.length() == 0) {
result = defaultAnswer;
}
if (getBooleanReply(result) == false) {
// no custom config, return the skin hardware config in case there is one.
return skinHardwareConfig;
}
mSdkLog.info("\n"); // empty line
// get the list of possible hardware properties
File hardwareDefs = new File (mOsSdkFolder + File.separator +
SdkConstants.OS_SDK_TOOLS_LIB_FOLDER, SdkConstants.FN_HARDWARE_INI);
Map<String, HardwareProperty> hwMap = HardwareProperties.parseHardwareDefinitions(
hardwareDefs, null /*sdkLog*/);
HashMap<String, String> map = new HashMap<String, String>();
// we just want to loop on the HardwareProperties
HardwareProperty[] hwProperties = hwMap.values().toArray(
new HardwareProperty[hwMap.size()]);
for (int i = 0 ; i < hwProperties.length ;) {
HardwareProperty property = hwProperties[i];
String description = property.getDescription();
if (description != null) {
mSdkLog.info("%s: %s\n", property.getAbstract(), description);
} else {
mSdkLog.info("%s\n", property.getAbstract());
}
String defaultValue = property.getDefault();
String defaultFromSkin = skinHardwareConfig != null ? skinHardwareConfig.get(
property.getName()) : null;
if (defaultFromSkin != null) {
mSdkLog.info("%s [%s (from skin)]:", property.getName(), defaultFromSkin);
} else if (defaultValue != null) {
mSdkLog.info("%s [%s]:", property.getName(), defaultValue);
} else {
mSdkLog.info("%s (%s):", property.getName(), property.getType());
}
result = readLine(readLineBuffer);
if (result.length() == 0) {
if (defaultFromSkin != null || defaultValue != null) {
if (defaultFromSkin != null) {
// we need to write this one in the AVD file
map.put(property.getName(), defaultFromSkin);
}
mSdkLog.info("\n"); // empty line
i++; // go to the next property if we have a valid default value.
// if there's no default, we'll redo this property
}
continue;
}
switch (property.getType()) {
case BOOLEAN:
try {
if (getBooleanReply(result)) {
map.put(property.getName(), "yes");
i++; // valid reply, move to next property
} else {
map.put(property.getName(), "no");
i++; // valid reply, move to next property
}
} catch (IOException e) {
// display error, and do not increment i to redo this property
mSdkLog.info("\n%s\n", e.getMessage());
}
break;
case INTEGER:
try {
Integer.parseInt(result);
map.put(property.getName(), result);
i++; // valid reply, move to next property
} catch (NumberFormatException e) {
// display error, and do not increment i to redo this property
mSdkLog.info("\n%s\n", e.getMessage());
}
break;
case DISKSIZE:
// TODO check validity
map.put(property.getName(), result);
i++; // valid reply, move to next property
break;
}
mSdkLog.info("\n"); // empty line
}
return map;
}
/**
* Reads a line from the input stream.
* @param buffer
* @throws IOException
*/
private String readLine(byte[] buffer) throws IOException {
int count = System.in.read(buffer);
// is the input longer than the buffer?
if (count == buffer.length && buffer[count-1] != 10) {
// create a new temp buffer
byte[] tempBuffer = new byte[256];
// and read the rest
String secondHalf = readLine(tempBuffer);
// return a concat of both
return new String(buffer, 0, count) + secondHalf;
}
// ignore end whitespace
while (count > 0 && (buffer[count-1] == '\r' || buffer[count-1] == '\n')) {
count--;
}
return new String(buffer, 0, count);
}
/**
* Reads a line from the input stream, masking it as much as possible.
*/
@SuppressWarnings("unused")
private String promptPassword(String prompt) throws IOException {
// Setup a thread that tries to overwrite any input by
// masking the last character with a space. This is quite
// crude but is a documented workaround to the lack of a
// proper password getter.
final AtomicBoolean keepErasing = new AtomicBoolean(true);
Thread eraser = new Thread(new Runnable() {
@Override
public void run() {
while (keepErasing.get()) {
System.err.print("\b "); //$NON-NLS-1$. \b=Backspace
try {
Thread.sleep(10 /*millis*/);
} catch (InterruptedException e) {
// Ignore
}
}
}
}, "eraser"); //$NON-NLS-1$
try {
System.err.print(prompt);
eraser.start();
byte[] buffer = new byte[256];
return readLine(buffer);
} finally {
keepErasing.set(false);
try {
eraser.join();
} catch (InterruptedException e) {
// Ignore
}
}
}
/**
* Returns the boolean value represented by the string.
* @throws IOException If the value is not a boolean string.
*/
private boolean getBooleanReply(String reply) throws IOException {
for (String valid : BOOLEAN_YES_REPLIES) {
if (valid.equalsIgnoreCase(reply)) {
return true;
}
}
for (String valid : BOOLEAN_NO_REPLIES) {
if (valid.equalsIgnoreCase(reply)) {
return false;
}
}
throw new IOException(String.format("%s is not a valid reply", reply));
}
private void errorAndExit(String format, Object...args) {
mSdkLog.error(null, format, args);
System.exit(1);
}
/**
* Converts a symbolic target name (such as those accepted by --target on the command-line)
* to an internal target index id. A valid target name is either a numeric target id (> 0)
* or a target hash string.
* <p/>
* If the given target can't be mapped, {@link #INVALID_TARGET_ID} (0) is returned.
* It's up to the caller to output an error.
* <p/>
* On success, returns a value > 0.
*/
private int resolveTargetName(String targetName) {
if (targetName == null) {
return INVALID_TARGET_ID;
}
targetName = targetName.trim();
// Case of an integer number
if (targetName.matches("[0-9]*")) {
try {
int n = Integer.parseInt(targetName);
return n < 1 ? INVALID_TARGET_ID : n;
} catch (NumberFormatException e) {
// Ignore. Should not happen.
}
}
// Let's try to find a platform or addon name.
IAndroidTarget[] targets = mSdkManager.getTargets();
for (int i = 0; i < targets.length; i++) {
if (targetName.equals(targets[i].hashString())) {
return i + 1;
}
}
return INVALID_TARGET_ID;
}
}