1190 lines
46 KiB
Java
1190 lines
46 KiB
Java
/*
|
|
* 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.prefs.AndroidLocation;
|
|
import com.android.prefs.AndroidLocation.AndroidLocationException;
|
|
import com.android.sdklib.IAndroidTarget;
|
|
import com.android.sdklib.ISdkLog;
|
|
import com.android.sdklib.SdkConstants;
|
|
import com.android.sdklib.SdkManager;
|
|
import com.android.sdklib.IAndroidTarget.IOptionalLibrary;
|
|
import com.android.sdklib.internal.avd.AvdManager;
|
|
import com.android.sdklib.internal.avd.HardwareProperties;
|
|
import com.android.sdklib.internal.avd.AvdManager.AvdInfo;
|
|
import com.android.sdklib.internal.avd.HardwareProperties.HardwareProperty;
|
|
import com.android.sdklib.internal.project.ProjectCreator;
|
|
import com.android.sdklib.internal.project.ProjectProperties;
|
|
import com.android.sdklib.internal.project.ProjectCreator.OutputLevel;
|
|
import com.android.sdklib.internal.project.ProjectProperties.PropertyType;
|
|
import com.android.sdklib.xml.AndroidXPathFactory;
|
|
import com.android.sdkmanager.internal.repository.AboutPage;
|
|
import com.android.sdkmanager.internal.repository.SettingsPage;
|
|
import com.android.sdkuilib.internal.repository.LocalPackagesPage;
|
|
import com.android.sdkuilib.repository.UpdaterWindow;
|
|
|
|
import org.xml.sax.InputSource;
|
|
|
|
import java.io.File;
|
|
import java.io.FileInputStream;
|
|
import java.io.FileNotFoundException;
|
|
import java.io.IOException;
|
|
import java.util.HashMap;
|
|
import java.util.Map;
|
|
|
|
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 ISdkLog 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);
|
|
}
|
|
|
|
/**
|
|
* Runs the sdk manager app
|
|
*/
|
|
private void run(String[] args) {
|
|
createLogger();
|
|
init();
|
|
mSdkCommandLine.parseArgs(args);
|
|
parseSdk();
|
|
doAction();
|
|
}
|
|
|
|
/**
|
|
* Creates the {@link #mSdkLog} object.
|
|
* <p/>
|
|
* This must be done before {@link #init()} as it will be used to report errors.
|
|
*/
|
|
private void createLogger() {
|
|
mSdkLog = new ISdkLog() {
|
|
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());
|
|
}
|
|
}
|
|
|
|
public void warning(String warningFormat, Object... args) {
|
|
if (mSdkCommandLine.isVerbose()) {
|
|
System.out.printf("Warning: " + warningFormat, args);
|
|
if (!warningFormat.endsWith("\n")) {
|
|
System.out.printf("\n");
|
|
}
|
|
}
|
|
}
|
|
|
|
public void printf(String msgFormat, Object... args) {
|
|
System.out.printf(msgFormat, args);
|
|
}
|
|
};
|
|
}
|
|
|
|
/**
|
|
* 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() {
|
|
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 {
|
|
displayTargetList();
|
|
displayAvdList();
|
|
}
|
|
|
|
} else if (SdkCommandLine.VERB_CREATE.equals(verb) &&
|
|
SdkCommandLine.OBJECT_AVD.equals(directObject)) {
|
|
createAvd();
|
|
|
|
} 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 (SdkCommandLine.VERB_UPDATE.equals(verb) &&
|
|
SdkCommandLine.OBJECT_AVD.equals(directObject)) {
|
|
updateAvd();
|
|
|
|
} else if (SdkCommandLine.VERB_CREATE.equals(verb) &&
|
|
SdkCommandLine.OBJECT_PROJECT.equals(directObject)) {
|
|
createProject();
|
|
|
|
} else if (SdkCommandLine.VERB_CREATE.equals(verb) &&
|
|
SdkCommandLine.OBJECT_TEST_PROJECT.equals(directObject)) {
|
|
createTestProject();
|
|
|
|
} else if (SdkCommandLine.VERB_UPDATE.equals(verb) &&
|
|
SdkCommandLine.OBJECT_PROJECT.equals(directObject)) {
|
|
updateProject();
|
|
|
|
} else if (SdkCommandLine.VERB_UPDATE.equals(verb) &&
|
|
SdkCommandLine.OBJECT_TEST_PROJECT.equals(directObject)) {
|
|
updateTestProject();
|
|
|
|
} else if (verb == null && directObject == null) {
|
|
showMainWindow(false /*autoUpdate*/);
|
|
|
|
} else if (SdkCommandLine.VERB_UPDATE.equals(verb) &&
|
|
SdkCommandLine.OBJECT_SDK.equals(directObject)) {
|
|
showMainWindow(true /*autoUpdate*/);
|
|
|
|
} else if (SdkCommandLine.VERB_UPDATE.equals(verb) &&
|
|
SdkCommandLine.OBJECT_ADB.equals(directObject)) {
|
|
updateAdb();
|
|
|
|
} else {
|
|
mSdkCommandLine.printHelpAndExit(null);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Display the main SdkManager app window
|
|
*/
|
|
private void showMainWindow(boolean autoUpdate) {
|
|
try {
|
|
// display a message talking about the command line version
|
|
System.out.printf("No command line parameters provided, launching UI.\n" +
|
|
"See 'android --help' for operations from the command line.\n");
|
|
UpdaterWindow window = new UpdaterWindow(
|
|
null /* parentShell */,
|
|
mSdkLog,
|
|
mOsSdkFolder,
|
|
false /*userCanChangeSdkRoot*/);
|
|
window.registerPage("Settings", SettingsPage.class);
|
|
window.registerPage("About", AboutPage.class);
|
|
if (autoUpdate) {
|
|
window.setInitialPage(LocalPackagesPage.class);
|
|
window.setRequestAutoUpdate(true);
|
|
}
|
|
window.open();
|
|
} catch (Exception e) {
|
|
e.printStackTrace();
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Creates a new Android project based on command-line parameters
|
|
*/
|
|
private void createProject() {
|
|
// 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 = new ProjectCreator(mOsSdkFolder,
|
|
mSdkCommandLine.isVerbose() ? OutputLevel.VERBOSE :
|
|
mSdkCommandLine.isSilent() ? OutputLevel.SILENT :
|
|
OutputLevel.NORMAL,
|
|
mSdkLog);
|
|
|
|
String projectDir = getProjectLocation(mSdkCommandLine.getParamLocationPath());
|
|
|
|
String projectName = mSdkCommandLine.getParamName();
|
|
String packageName = mSdkCommandLine.getParamProjectPackage();
|
|
String 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,
|
|
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.printf("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.printf("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.DEFAULT);
|
|
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.printf("Found main project target: %1$s\n", target.getFullName());
|
|
|
|
ProjectCreator creator = new ProjectCreator(mOsSdkFolder,
|
|
mSdkCommandLine.isVerbose() ? OutputLevel.VERBOSE :
|
|
mSdkCommandLine.isSilent() ? OutputLevel.SILENT :
|
|
OutputLevel.NORMAL,
|
|
mSdkLog);
|
|
|
|
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,
|
|
pathToMainProject);
|
|
}
|
|
|
|
|
|
/**
|
|
* Updates an existing Android project based on command-line parameters
|
|
*/
|
|
private void updateProject() {
|
|
// 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 = new ProjectCreator(mOsSdkFolder,
|
|
mSdkCommandLine.isVerbose() ? OutputLevel.VERBOSE :
|
|
mSdkCommandLine.isSilent() ? OutputLevel.SILENT :
|
|
OutputLevel.NORMAL,
|
|
mSdkLog);
|
|
|
|
String projectDir = getProjectLocation(mSdkCommandLine.getParamLocationPath());
|
|
|
|
creator.updateProject(projectDir,
|
|
target,
|
|
mSdkCommandLine.getParamName());
|
|
|
|
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());
|
|
} else {
|
|
couldHaveDone = true;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if (couldHaveDone) {
|
|
mSdkLog.printf("It seems that there are sub-projects. If you want to update them\nplease use the --%1$s parameter.",
|
|
SdkCommandLine.KEY_SUBPROJECTS);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Updates an existing test project with a new path to the main project.
|
|
*/
|
|
private void updateTestProject() {
|
|
ProjectCreator creator = new ProjectCreator(mOsSdkFolder,
|
|
mSdkCommandLine.isVerbose() ? OutputLevel.VERBOSE :
|
|
mSdkCommandLine.isSilent() ? OutputLevel.SILENT :
|
|
OutputLevel.NORMAL,
|
|
mSdkLog);
|
|
|
|
String projectDir = getProjectLocation(mSdkCommandLine.getParamLocationPath());
|
|
|
|
creator.updateTestProject(projectDir, mSdkCommandLine.getParamTestProjectMain());
|
|
}
|
|
|
|
/**
|
|
* 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)
|
|
*/
|
|
private void displayTargetList() {
|
|
mSdkLog.printf("Available Android targets:\n");
|
|
|
|
int index = 1;
|
|
for (IAndroidTarget target : mSdkManager.getTargets()) {
|
|
mSdkLog.printf("id: %1$d or \"%2$s\"\n", index, target.hashString());
|
|
mSdkLog.printf(" Name: %s\n", target.getName());
|
|
if (target.isPlatform()) {
|
|
mSdkLog.printf(" Type: Platform\n");
|
|
mSdkLog.printf(" API level: %s\n", target.getVersion().getApiString());
|
|
mSdkLog.printf(" Revision: %d\n", target.getRevision());
|
|
} else {
|
|
mSdkLog.printf(" Type: Add-On\n");
|
|
mSdkLog.printf(" Vendor: %s\n", target.getVendor());
|
|
mSdkLog.printf(" Revision: %d\n", target.getRevision());
|
|
if (target.getDescription() != null) {
|
|
mSdkLog.printf(" Description: %s\n", target.getDescription());
|
|
}
|
|
mSdkLog.printf(" 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.printf(" Libraries:\n");
|
|
for (IOptionalLibrary library : libraries) {
|
|
mSdkLog.printf(" * %1$s (%2$s)\n",
|
|
library.getName(), library.getJarName());
|
|
mSdkLog.printf(String.format(
|
|
" %1$s\n", library.getDescription()));
|
|
}
|
|
}
|
|
}
|
|
|
|
// get the target skins
|
|
displaySkinList(target, " Skins: ");
|
|
|
|
if (target.getUsbVendorId() != IAndroidTarget.NO_USB_ID) {
|
|
mSdkLog.printf(" Adds USB support for devices (Vendor: 0x%04X)\n",
|
|
target.getUsbVendorId());
|
|
}
|
|
|
|
index++;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Displays the skins valid for the given target.
|
|
*/
|
|
private void displaySkinList(IAndroidTarget target, String message) {
|
|
String[] skins = target.getSkins();
|
|
String defaultSkin = target.getDefaultSkin();
|
|
mSdkLog.printf(message);
|
|
if (skins != null) {
|
|
boolean first = true;
|
|
for (String skin : skins) {
|
|
if (first == false) {
|
|
mSdkLog.printf(", ");
|
|
} else {
|
|
first = false;
|
|
}
|
|
mSdkLog.printf(skin);
|
|
|
|
if (skin.equals(defaultSkin)) {
|
|
mSdkLog.printf(" (default)");
|
|
}
|
|
}
|
|
mSdkLog.printf("\n");
|
|
} else {
|
|
mSdkLog.printf("no skins.\n");
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Displays the list of available AVDs.
|
|
*/
|
|
private void displayAvdList() {
|
|
try {
|
|
AvdManager avdManager = new AvdManager(mSdkManager, mSdkLog);
|
|
|
|
mSdkLog.printf("Available Android Virtual Devices:\n");
|
|
|
|
AvdInfo[] avds = avdManager.getValidAvds();
|
|
for (int index = 0 ; index < avds.length ; index++) {
|
|
AvdInfo info = avds[index];
|
|
if (index > 0) {
|
|
mSdkLog.printf("---------\n");
|
|
}
|
|
mSdkLog.printf(" Name: %s\n", info.getName());
|
|
mSdkLog.printf(" Path: %s\n", info.getPath());
|
|
|
|
// get the target of the AVD
|
|
IAndroidTarget target = info.getTarget();
|
|
if (target.isPlatform()) {
|
|
mSdkLog.printf(" Target: %s (API level %s)\n", target.getName(),
|
|
target.getVersion().getApiString());
|
|
} else {
|
|
mSdkLog.printf(" Target: %s (%s)\n", target.getName(), target
|
|
.getVendor());
|
|
mSdkLog.printf(" Based on Android %s (API level %s)\n",
|
|
target.getVersionName(), target.getVersion().getApiString());
|
|
}
|
|
|
|
// 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.printf(" 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.printf(" Sdcard: %s\n", sdcard);
|
|
}
|
|
}
|
|
}
|
|
|
|
// Are there some unused AVDs?
|
|
AvdInfo[] badAvds = avdManager.getBrokenAvds();
|
|
|
|
if (badAvds.length == 0) {
|
|
return;
|
|
}
|
|
|
|
mSdkLog.printf("\nThe following Android Virtual Devices could not be loaded:\n");
|
|
boolean needSeparator = false;
|
|
for (AvdInfo info : badAvds) {
|
|
if (needSeparator) {
|
|
mSdkLog.printf("---------\n");
|
|
}
|
|
mSdkLog.printf(" Name: %s\n", info.getName() == null ? "--" : info.getName());
|
|
mSdkLog.printf(" Path: %s\n", info.getPath() == null ? "--" : info.getPath());
|
|
|
|
String error = info.getErrorMessage();
|
|
mSdkLog.printf(" Error: %s\n", error == null ? "Uknown error" : error);
|
|
needSeparator = true;
|
|
}
|
|
} 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 = new AvdManager(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 = new File(AndroidLocation.getFolder() + AndroidLocation.FOLDER_AVD,
|
|
avdName + AvdManager.AVD_FOLDER_EXTENSION);
|
|
}
|
|
|
|
// 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);
|
|
File skinHardwareFile = new File(skinFolder, AvdManager.HARDWARE_INI);
|
|
if (skinHardwareFile.isFile()) {
|
|
skinHardwareConfig = SdkManager.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;
|
|
}
|
|
}
|
|
|
|
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,
|
|
skin,
|
|
mSdkCommandLine.getParamSdCard(),
|
|
hardwareConfig,
|
|
removePrevious,
|
|
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 = new AvdManager(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 = new AvdManager(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.getPath()).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 (originalFolder.equals(info.getPath())) {
|
|
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.getIniFile(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 = new AvdManager(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.printf(
|
|
"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.printf("%s is a basic Android platform.\n", createTarget.getName());
|
|
mSdkLog.printf("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.printf("\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.printf("%s: %s\n", property.getAbstract(), description);
|
|
} else {
|
|
mSdkLog.printf("%s\n", property.getAbstract());
|
|
}
|
|
|
|
String defaultValue = property.getDefault();
|
|
String defaultFromSkin = skinHardwareConfig != null ? skinHardwareConfig.get(
|
|
property.getName()) : null;
|
|
|
|
if (defaultFromSkin != null) {
|
|
mSdkLog.printf("%s [%s (from skin)]:", property.getName(), defaultFromSkin);
|
|
} else if (defaultValue != null) {
|
|
mSdkLog.printf("%s [%s]:", property.getName(), defaultValue);
|
|
} else {
|
|
mSdkLog.printf("%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.printf("\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.printf("\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.printf("\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.printf("\n"); // empty line
|
|
}
|
|
|
|
return map;
|
|
}
|
|
|
|
/**
|
|
* Reads the 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);
|
|
}
|
|
|
|
/**
|
|
* 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;
|
|
}
|
|
}
|