eclair snapshot

This commit is contained in:
Jean-Baptiste Queru
2009-11-12 18:45:17 -08:00
parent 50992e805e
commit 2c8ead32c7
3250 changed files with 153970 additions and 72935 deletions

View File

@@ -7,5 +7,6 @@
<classpathentry combineaccessrules="false" kind="src" path="/AndroidPrefs"/>
<classpathentry combineaccessrules="false" kind="src" path="/SdkUiLib"/>
<classpathentry kind="con" path="org.eclipse.jdt.junit.JUNIT_CONTAINER/3"/>
<classpathentry kind="con" path="org.eclipse.jdt.USER_LIBRARY/ANDROID_SWT"/>
<classpathentry kind="output" path="bin"/>
</classpath>

View File

@@ -62,14 +62,17 @@ else
java_debug=
fi
java_cmd="java"
# Mac OS X needs an additional arg, or you get an "illegal thread" complaint.
if [ `uname` = "Darwin" ]; then
os_opts="-XstartOnFirstThread"
#because Java 1.6 is 64 bits only and SWT doesn't support this, we force the usage of java 1.5
java_cmd="/System/Library/Frameworks/JavaVM.framework/Versions/1.5/Commands/java"
else
os_opts=
java_cmd="java"
fi
if [ `uname` = "Linux" ]; then
export GDK_NATIVE_WINDOWS=true
fi
if [ "$OSTYPE" = "cygwin" ] ; then
@@ -87,7 +90,7 @@ swtpath=""
if [ -n "$ANDROID_SWT" ]; then
swtpath="$ANDROID_SWT"
else
vmarch=`java -jar "${frameworkdir}"/archquery.jar`
vmarch=`${java_cmd} -jar "${frameworkdir}"/archquery.jar`
if [ -n "$ANDROID_BUILD_TOP" ]; then
osname=`uname -s | tr A-Z a-z`
swtpath="${ANDROID_BUILD_TOP}/prebuilt/${osname}-${vmarch}/swt"
@@ -105,6 +108,10 @@ else
exit 1
fi
if [ -z "$1" ]; then
echo "Starting Android SDK and AVD Manager"
fi
# need to use "java.ext.dirs" because "-jar" causes classpath to be ignored
# might need more memory, e.g. -Xmx128M
exec "$java_cmd" -Xmx256M $os_opts $java_debug -Djava.ext.dirs="$frameworkdir" -Dcom.android.sdkmanager.toolsdir="$progdir" -jar "$jarpath" "$@"

View File

@@ -32,8 +32,10 @@ set jarpath=lib\sdkmanager.jar
rem Set SWT.Jar path based on current architecture (x86 or x86_64)
for /f %%a in ('java -jar lib\archquery.jar') do set swt_path=lib\%%a
if "%1 %2"=="update sdk" goto StartUi
if not "%1"=="" goto EndTempCopy
echo Starting Android SDK Updater
:StartUi
echo Starting Android SDK and AVD Manager
rem We're now going to create a temp dir to hold all the Jar files needed
rem to run the android tool, copy them in the temp dir and finally execute
@@ -42,7 +44,7 @@ if not "%1"=="" goto EndTempCopy
rem update the tools directory where the updater itself is located.
set tmpdir=%TEMP%\temp-android-tool
xcopy lib\x86 %tmpdir%\lib\x86 /I /E /C /G /R /O /Y /Q > nul
xcopy lib\x86 %tmpdir%\lib\x86 /I /E /C /G /R /Y /Q > nul
copy /B /D /Y lib\androidprefs.jar %tmpdir%\lib\ > nul
copy /B /D /Y lib\org.eclipse.* %tmpdir%\lib\ > nul
copy /B /D /Y lib\sdk* %tmpdir%\lib\ > nul

View File

@@ -34,7 +34,17 @@ import java.util.Map.Entry;
* To use, call {@link #parseArgs(String[])} and then
* call {@link #getValue(String, String, String)}.
*/
public class CommandLineProcessor {
class CommandLineProcessor {
/*
* Steps needed to add a new action:
* - Each action is defined as a "verb object" followed by parameters.
* - Either reuse a VERB_ constant or define a new one.
* - Either reuse an OBJECT_ constant or define a new one.
* - Add a new entry to mAction with a one-line help summary.
* - In the constructor, add a define() call for each parameter (either mandatory
* or optional) for the given action.
*/
/** Internal verb name for internally hidden flags. */
public final static String GLOBAL_FLAG_VERB = "@@internal@@";
@@ -57,10 +67,14 @@ public class CommandLineProcessor {
/**
* Action definitions.
* <p/>
* This list serves two purposes: first it is used to know which verb/object
* actions are acceptable on the command-line; second it provides a summary
* for each action that is printed in the help.
* <p/>
* Each entry is a string array with:
* <ul>
* <li> the verb.
* <li> a direct object (use #NO_VERB_OBJECT if there's no object).
* <li> a direct object (use {@link #NO_VERB_OBJECT} if there's no object).
* <li> a description.
* <li> an alternate form for the object (e.g. plural).
* </ul>
@@ -81,6 +95,15 @@ public class CommandLineProcessor {
/** Logger */
private final ISdkLog mLog;
/**
* Constructs a new command-line processor.
*
* @param logger An SDK logger object. Must not be null.
* @param actions The list of actions recognized on the command-line.
* See the javadoc of {@link #mActions} for more details.
*
* @see #mActions
*/
public CommandLineProcessor(ISdkLog logger, String[][] actions) {
mLog = logger;
mActions = actions;
@@ -460,7 +483,7 @@ public class CommandLineProcessor {
stdout("\nValid actions are composed of a verb and an optional direct object:");
for (String[] action : mActions) {
stdout("- %1$6s %2$-7s: %3$s",
stdout("- %1$6s %2$-12s: %3$s",
action[ACTION_VERB_INDEX],
action[ACTION_OBJECT_INDEX],
action[ACTION_DESC_INDEX]);

View File

@@ -28,28 +28,41 @@ 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.List;
import java.util.Map;
import javax.xml.xpath.XPath;
import javax.xml.xpath.XPathExpressionException;
/**
* Main class for the 'android' application.
*/
class Main {
public class Main {
/** Java property that defines the location of the sdk/tools directory. */
private final static String TOOLSDIR = "com.android.sdkmanager.toolsdir";
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" };
@@ -218,12 +231,24 @@ class Main {
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();
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)) {
@@ -237,7 +262,7 @@ class Main {
/**
* Display the main SdkManager app window
*/
private void showMainWindow() {
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" +
@@ -249,6 +274,10 @@ class Main {
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();
@@ -260,13 +289,13 @@ class Main {
*/
private void createProject() {
// get the target and try to resolve it.
int targetId = mSdkCommandLine.getParamTargetId();
int targetId = resolveTargetName(mSdkCommandLine.getParamTargetId());
IAndroidTarget[] targets = mSdkManager.getTargets();
if (targetId < 1 || targetId > targets.length) {
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];
IAndroidTarget target = targets[targetId - 1]; // target id is 1-based
ProjectCreator creator = new ProjectCreator(mOsSdkFolder,
mSdkCommandLine.isVerbose() ? OutputLevel.VERBOSE :
@@ -311,23 +340,140 @@ class Main {
packageName,
activityName,
target,
false /* isTestProject*/);
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;
int targetId = mSdkCommandLine.getParamTargetId();
if (targetId >= 0) {
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();
if (targetId < 1 || targetId > targets.length) {
errorAndExit("Target id is not valid. Use '%s list targets' to get the target ids.",
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 = targets[targetId - 1]; // target id is 1-based
}
ProjectCreator creator = new ProjectCreator(mOsSdkFolder,
@@ -370,6 +516,21 @@ class Main {
}
}
/**
* 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.
@@ -412,7 +573,7 @@ class Main {
int index = 1;
for (IAndroidTarget target : mSdkManager.getTargets()) {
mSdkLog.printf("id: %d\n", index);
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");
@@ -557,16 +718,16 @@ class Main {
*/
private void createAvd() {
// find a matching target
int targetId = mSdkCommandLine.getParamTargetId();
IAndroidTarget target = null;
int targetId = resolveTargetName(mSdkCommandLine.getParamTargetId());
IAndroidTarget[] targets = mSdkManager.getTargets();
if (targetId >= 1 && targetId <= mSdkManager.getTargets().length) {
target = mSdkManager.getTargets()[targetId-1]; // target it is 1-based
} else {
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);
@@ -587,7 +748,9 @@ class Main {
"Android Virtual Device '%s' already exists and will be replaced.",
avdName);
} else {
errorAndExit("Android Virtual Device '%s' already exists.", avdName);
errorAndExit("Android Virtual Device '%s' already exists.\n" +
"Use --force if you want to replace it.",
avdName);
return;
}
}
@@ -607,7 +770,8 @@ class Main {
if (skin != null && skin.length() == 0) {
skin = null;
}
if (skin != null) {
if (skin != null && target != null) {
boolean valid = false;
// Is it a know skin name for this target?
for (String s : target.getSkins()) {
@@ -639,7 +803,7 @@ class Main {
}
Map<String, String> hardwareConfig = null;
if (target.isPlatform()) {
if (target != null && target.isPlatform()) {
try {
hardwareConfig = promptForHardware(target, skinHardwareConfig);
} catch (IOException e) {
@@ -647,11 +811,13 @@ class Main {
}
}
@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,
@@ -845,13 +1011,16 @@ class Main {
// get the list of possible hardware properties
File hardwareDefs = new File (mOsSdkFolder + File.separator +
SdkConstants.OS_SDK_TOOLS_LIB_FOLDER, SdkConstants.FN_HARDWARE_INI);
List<HardwareProperty> list = HardwareProperties.parseHardwareDefinitions(hardwareDefs,
null /*sdkLog*/);
Map<String, HardwareProperty> hwMap = HardwareProperties.parseHardwareDefinitions(
hardwareDefs, null /*sdkLog*/);
HashMap<String, String> map = new HashMap<String, String>();
for (int i = 0 ; i < list.size() ;) {
HardwareProperty property = list.get(i);
// 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) {
@@ -978,4 +1147,43 @@ class Main {
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;
}
}

View File

@@ -23,7 +23,17 @@ import com.android.sdklib.SdkManager;
/**
* Specific command-line flags for the {@link SdkManager}.
*/
public class SdkCommandLine extends CommandLineProcessor {
class SdkCommandLine extends CommandLineProcessor {
/*
* Steps needed to add a new action:
* - Each action is defined as a "verb object" followed by parameters.
* - Either reuse a VERB_ constant or define a new one.
* - Either reuse an OBJECT_ constant or define a new one.
* - Add a new entry to mAction with a one-line help summary.
* - In the constructor, add a define() call for each parameter (either mandatory
* or optional) for the given action.
*/
public final static String VERB_LIST = "list";
public final static String VERB_CREATE = "create";
@@ -31,32 +41,39 @@ public class SdkCommandLine extends CommandLineProcessor {
public final static String VERB_DELETE = "delete";
public final static String VERB_UPDATE = "update";
public static final String OBJECT_AVD = "avd";
public static final String OBJECT_AVDS = "avds";
public static final String OBJECT_TARGET = "target";
public static final String OBJECT_TARGETS = "targets";
public static final String OBJECT_PROJECT = "project";
public static final String OBJECT_ADB = "adb";
public static final String OBJECT_SDK = "sdk";
public static final String OBJECT_AVD = "avd";
public static final String OBJECT_AVDS = "avds";
public static final String OBJECT_TARGET = "target";
public static final String OBJECT_TARGETS = "targets";
public static final String OBJECT_PROJECT = "project";
public static final String OBJECT_TEST_PROJECT = "test-project";
public static final String OBJECT_ADB = "adb";
public static final String ARG_ALIAS = "alias";
public static final String ARG_ACTIVITY = "activity";
public static final String ARG_ALIAS = "alias";
public static final String ARG_ACTIVITY = "activity";
public static final String KEY_ACTIVITY = ARG_ACTIVITY;
public static final String KEY_PACKAGE = "package";
public static final String KEY_MODE = "mode";
public static final String KEY_TARGET_ID = OBJECT_TARGET;
public static final String KEY_NAME = "name";
public static final String KEY_PATH = "path";
public static final String KEY_FILTER = "filter";
public static final String KEY_SKIN = "skin";
public static final String KEY_SDCARD = "sdcard";
public static final String KEY_FORCE = "force";
public static final String KEY_RENAME = "rename";
public static final String KEY_SUBPROJECTS = "subprojects";
public static final String KEY_ACTIVITY = ARG_ACTIVITY;
public static final String KEY_PACKAGE = "package";
public static final String KEY_MODE = "mode";
public static final String KEY_TARGET_ID = OBJECT_TARGET;
public static final String KEY_NAME = "name";
public static final String KEY_PATH = "path";
public static final String KEY_FILTER = "filter";
public static final String KEY_SKIN = "skin";
public static final String KEY_SDCARD = "sdcard";
public static final String KEY_FORCE = "force";
public static final String KEY_RENAME = "rename";
public static final String KEY_SUBPROJECTS = "subprojects";
public static final String KEY_MAIN_PROJECT = "main";
/**
* Action definitions for SdkManager command line.
* <p/>
* This list serves two purposes: first it is used to know which verb/object
* actions are acceptable on the command-line; second it provides a summary
* for each action that is printed in the help.
* <p/>
* Each entry is a string array with:
* <ul>
* <li> the verb.
@@ -89,13 +106,23 @@ public class SdkCommandLine extends CommandLineProcessor {
{ VERB_UPDATE, OBJECT_PROJECT,
"Updates an Android Project (must have an AndroidManifest.xml)." },
{ VERB_CREATE, OBJECT_TEST_PROJECT,
"Creates a new Android Test Project." },
{ VERB_UPDATE, OBJECT_TEST_PROJECT,
"Updates an Android Test Project (must have an AndroidManifest.xml)." },
{ VERB_UPDATE, OBJECT_ADB,
"Updates adb to support the USB devices declared in the SDK add-ons." },
{ VERB_UPDATE, OBJECT_SDK,
"Updates the SDK by suggesting new platforms to install if available." }
};
public SdkCommandLine(ISdkLog logger) {
super(logger, ACTIONS);
// The following defines the parameters of the actions defined in mAction.
// --- create avd ---
define(Mode.STRING, false,
@@ -104,7 +131,7 @@ public class SdkCommandLine extends CommandLineProcessor {
define(Mode.STRING, true,
VERB_CREATE, OBJECT_AVD, "n", KEY_NAME,
"Name of the new AVD", null);
define(Mode.INTEGER, true,
define(Mode.STRING, true,
VERB_CREATE, OBJECT_AVD, "t", KEY_TARGET_ID,
"Target id of the new AVD", null);
define(Mode.STRING, false,
@@ -154,7 +181,7 @@ public class SdkCommandLine extends CommandLineProcessor {
VERB_CREATE, OBJECT_PROJECT,
"p", KEY_PATH,
"Location path of new project", null);
define(Mode.INTEGER, true,
define(Mode.STRING, true,
VERB_CREATE, OBJECT_PROJECT, "t", KEY_TARGET_ID,
"Target id of the new project", null);
define(Mode.STRING, true,
@@ -167,16 +194,29 @@ public class SdkCommandLine extends CommandLineProcessor {
VERB_CREATE, OBJECT_PROJECT, "n", KEY_NAME,
"Project name", null);
// --- create test-project ---
define(Mode.STRING, true,
VERB_CREATE, OBJECT_TEST_PROJECT,
"p", KEY_PATH,
"Location path of new project", null);
define(Mode.STRING, false,
VERB_CREATE, OBJECT_TEST_PROJECT, "n", KEY_NAME,
"Project name", null);
define(Mode.STRING, true,
VERB_CREATE, OBJECT_TEST_PROJECT, "m", KEY_MAIN_PROJECT,
"Location path of the project to test, relative to the new project", null);
// --- update project ---
define(Mode.STRING, true,
VERB_UPDATE, OBJECT_PROJECT,
"p", KEY_PATH,
"Location path of the project", null);
define(Mode.INTEGER, true,
define(Mode.STRING, false,
VERB_UPDATE, OBJECT_PROJECT,
"t", KEY_TARGET_ID,
"Target id to set for the project", -1);
"Target id to set for the project", null);
define(Mode.STRING, false,
VERB_UPDATE, OBJECT_PROJECT,
"n", KEY_NAME,
@@ -185,6 +225,17 @@ public class SdkCommandLine extends CommandLineProcessor {
VERB_UPDATE, OBJECT_PROJECT,
"s", KEY_SUBPROJECTS,
"Also update any projects in sub-folders, such as test projects.", false);
// --- update test project ---
define(Mode.STRING, true,
VERB_UPDATE, OBJECT_TEST_PROJECT,
"p", KEY_PATH,
"Location path of the project", null);
define(Mode.STRING, true,
VERB_UPDATE, OBJECT_TEST_PROJECT,
"m", KEY_MAIN_PROJECT,
"Location path of the project to test, relative to the new project", null);
}
@Override
@@ -196,27 +247,34 @@ public class SdkCommandLine extends CommandLineProcessor {
/** Helper to retrieve the --path value. */
public String getParamLocationPath() {
return ((String) getValue(null, null, KEY_PATH));
return (String) getValue(null, null, KEY_PATH);
}
/** Helper to retrieve the --target id value. */
public int getParamTargetId() {
return ((Integer) getValue(null, null, KEY_TARGET_ID)).intValue();
/**
* Helper to retrieve the --target id value.
* The id is a string. It can be one of:
* - an integer, in which case it's the index of the target (cf "android list targets")
* - a symbolic name such as android-N for platforn API N
* - a symbolic add-on name such as written in the avd/*.ini files,
* e.g. "Google Inc.:Google APIs:3"
*/
public String getParamTargetId() {
return (String) getValue(null, null, KEY_TARGET_ID);
}
/** Helper to retrieve the --name value. */
public String getParamName() {
return ((String) getValue(null, null, KEY_NAME));
return (String) getValue(null, null, KEY_NAME);
}
/** Helper to retrieve the --skin value. */
public String getParamSkin() {
return ((String) getValue(null, null, KEY_SKIN));
return (String) getValue(null, null, KEY_SKIN);
}
/** Helper to retrieve the --sdcard value. */
public String getParamSdCard() {
return ((String) getValue(null, null, KEY_SDCARD));
return (String) getValue(null, null, KEY_SDCARD);
}
/** Helper to retrieve the --force flag. */
@@ -228,7 +286,7 @@ public class SdkCommandLine extends CommandLineProcessor {
/** Helper to retrieve the --rename value for a move verb. */
public String getParamMoveNewName() {
return ((String) getValue(VERB_MOVE, null, KEY_RENAME));
return (String) getValue(VERB_MOVE, null, KEY_RENAME);
}
@@ -248,4 +306,11 @@ public class SdkCommandLine extends CommandLineProcessor {
public boolean getParamSubProject() {
return ((Boolean) getValue(null, OBJECT_PROJECT, KEY_SUBPROJECTS)).booleanValue();
}
// -- some helpers for test-project action flags
/** Helper to retrieve the --main value. */
public String getParamTestProjectMain() {
return ((String) getValue(null, null, KEY_MAIN_PROJECT));
}
}

View File

@@ -17,16 +17,20 @@
package com.android.sdkmanager.internal.repository;
import com.android.sdkmanager.*;
import org.eclipse.swt.SWT;
import org.eclipse.swt.graphics.Image;
import org.eclipse.swt.layout.GridData;
import org.eclipse.swt.layout.GridLayout;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Label;
/*
* TODO list
* - Change version to be a constant pulled from somewhere.
*/
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.util.Properties;
public class AboutPage extends Composite {
@@ -45,11 +49,21 @@ public class AboutPage extends Composite {
}
private void createContents(Composite parent) {
parent.setLayout(new GridLayout(1, false));
parent.setLayout(new GridLayout(2, false));
Label logo = new Label(parent, SWT.NONE);
InputStream imageStream = this.getClass().getResourceAsStream("logo.png");
if (imageStream != null) {
Image img = new Image(parent.getShell().getDisplay(), imageStream);
logo.setImage(img);
}
mLabel = new Label(parent, SWT.NONE);
mLabel.setLayoutData(new GridData(SWT.CENTER, SWT.CENTER, true, true, 1, 1));
mLabel.setText("Android SDK Updater.\n\nVersion 0.1.\n\nCopyright (C) 2009 The Android Open Source Project.");
mLabel.setLayoutData(new GridData(SWT.CENTER, SWT.CENTER, false, false, 1, 1));
mLabel.setText(String.format(
"Android SDK Updater.\nRevision %1$s\nCopyright (C) 2009 The Android Open Source Project.",
getRevision()));
}
@Override
@@ -69,4 +83,28 @@ public class AboutPage extends Composite {
// End of hiding from SWT Designer
//$hide<<$
private String getRevision() {
Properties p = new Properties();
try{
String toolsdir = System.getProperty(Main.TOOLSDIR);
File sourceProp;
if (toolsdir == null || toolsdir.length() == 0) {
sourceProp = new File("source.properties"); //$NON-NLS-1$
} else {
sourceProp = new File(toolsdir, "source.properties"); //$NON-NLS-1$
}
p.load(new FileInputStream(sourceProp));
String revision = p.getProperty("Pkg.Revision"); //$NON-NLS-1$
if (revision != null) {
return revision;
}
} catch (FileNotFoundException e) {
// couldn't find the file? don't ping.
} catch (IOException e) {
// couldn't find the file? don't ping.
}
return "?";
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.3 KiB

View File

@@ -40,13 +40,14 @@ import java.util.Properties;
public class AndroidVersion {
private static final String PROP_API_LEVEL = "AndroidVersion.ApiLevel"; //$NON-NLS-1$
private static final String PROP_CODENAME = "AndroidVersion.CodeName"; //$NON-NLS-1$
private static final String PROP_CODENAME = "AndroidVersion.CodeName"; //$NON-NLS-1$
private final int mApiLevel;
private final String mCodename;
/**
* Creates an {@link AndroidVersion} with the given api level and codename.
* Codename should be null for a release version, otherwise it's a preview codename.
*/
public AndroidVersion(int apiLevel, String codename) {
mApiLevel = apiLevel;

View File

@@ -78,6 +78,8 @@ public final class SdkConstants {
public final static String FN_PLUGIN_PROP = "plugin.prop";
/** add-on manifest file */
public final static String FN_MANIFEST_INI = "manifest.ini";
/** add-on layout device XML file. */
public final static String FN_DEVICES_XML = "devices.xml";
/** hardware properties definition file */
public final static String FN_HARDWARE_INI = "hardware-properties.ini";

View File

@@ -88,6 +88,16 @@ public final class SdkManager {
private final String mSdkLocation;
private IAndroidTarget[] mTargets;
/**
* Create a new {@link SdkManager} instance.
* External users should use {@link #createManager(String, ISdkLog)}.
*
* @param sdkLocation the location of the SDK.
*/
private SdkManager(String sdkLocation) {
mSdkLocation = sdkLocation;
}
/**
* Creates an {@link SdkManager} for a given sdk location.
* @param sdkLocation the location of the SDK.
@@ -125,11 +135,23 @@ public final class SdkManager {
/**
* Returns the targets that are available in the SDK.
* <p/>
* The array can be empty but not null.
*/
public IAndroidTarget[] getTargets() {
return mTargets;
}
/**
* Sets the targets that are available in the SDK.
* <p/>
* The array can be empty but not null.
*/
private void setTargets(IAndroidTarget[] targets) {
assert targets != null;
mTargets = targets;
}
/**
* Returns a target from a hash that was generated by {@link IAndroidTarget#hashString()}.
*
@@ -202,14 +224,6 @@ public final class SdkManager {
setTargets(list.toArray(new IAndroidTarget[list.size()]));
}
private SdkManager(String sdkLocation) {
mSdkLocation = sdkLocation;
}
private void setTargets(IAndroidTarget[] targets) {
mTargets = targets;
}
/**
* Loads the Platforms from the SDK.
* @param location Location of the SDK
@@ -591,8 +605,10 @@ public final class SdkManager {
if (m.matches()) {
map.put(m.group(1), m.group(2));
} else {
log.warning("Error parsing '%1$s': \"%2$s\" is not a valid syntax",
buildProp.getAbsolutePath(), line);
if (log != null) {
log.warning("Error parsing '%1$s': \"%2$s\" is not a valid syntax",
buildProp.getAbsolutePath(), line);
}
return null;
}
}

View File

@@ -110,7 +110,7 @@ public final class AvdManager {
/**
* Pattern to match pixel-sized skin "names", e.g. "320x480".
*/
public final static Pattern NUMERIC_SKIN_SIZE = Pattern.compile("[0-9]{2,}x[0-9]{2,}"); //$NON-NLS-1$
public final static Pattern NUMERIC_SKIN_SIZE = Pattern.compile("([0-9]{2,})x([0-9]{2,})"); //$NON-NLS-1$
private final static String USERDATA_IMG = "userdata.img"; //$NON-NLS-1$
private final static String CONFIG_INI = "config.ini"; //$NON-NLS-1$
@@ -1044,7 +1044,13 @@ public final class AvdManager {
}
if (configIniFile != null) {
properties = SdkManager.parsePropertyFile(configIniFile, log);
if (!configIniFile.isFile()) {
if (log != null) {
log.warning("Missing file '%1$s'.", configIniFile.getPath());
}
} else {
properties = SdkManager.parsePropertyFile(configIniFile, log);
}
}
// get name

View File

@@ -24,43 +24,52 @@ import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStreamReader;
import java.util.ArrayList;
import java.util.List;
import java.util.HashMap;
import java.util.Map;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
public class HardwareProperties {
private final static Pattern PATTERN_PROP = Pattern.compile(
"^([a-zA-Z0-9._-]+)\\s*=\\s*(.*)\\s*$");
private final static String HW_PROP_NAME = "name";
private final static String HW_PROP_TYPE = "type";
private final static String HW_PROP_DEFAULT = "default";
private final static String HW_PROP_ABSTRACT = "abstract";
private final static String HW_PROP_DESC = "description";
private final static String BOOLEAN_YES = "yes";
private final static String BOOLEAN_NO = "no";
public final static String[] BOOLEAN_VALUES = new String[] { BOOLEAN_YES, BOOLEAN_NO };
public final static Pattern DISKSIZE_PATTERN = Pattern.compile("\\d+[MK]B");
public enum ValueType {
INTEGER("integer"),
BOOLEAN("boolean"),
DISKSIZE("diskSize");
private String mValue;
ValueType(String value) {
mValue = value;
}
public String getValue() {
return mValue;
}
public static ValueType getEnum(String value) {
for (ValueType type : values()) {
if (type.mValue.equals(value)) {
return type;
}
}
return null;
}
}
public static final class HardwareProperty {
private String mName;
private ValueType mType;
@@ -68,40 +77,40 @@ public class HardwareProperties {
private String mDefault;
private String mAbstract;
private String mDescription;
public String getName() {
return mName;
}
public ValueType getType() {
return mType;
}
public String getDefault() {
return mDefault;
}
public String getAbstract() {
return mAbstract;
}
public String getDescription() {
return mDescription;
}
}
/**
* Parses the hardware definition file.
* @param file the property file to parse
* @param log the ISdkLog object receiving warning/error from the parsing.
* @return the map of (key,value) pairs, or null if the parsing failed.
*/
public static List<HardwareProperty> parseHardwareDefinitions(File file, ISdkLog log) {
public static Map<String, HardwareProperty> parseHardwareDefinitions(File file, ISdkLog log) {
try {
FileInputStream fis = new FileInputStream(file);
BufferedReader reader = new BufferedReader(new InputStreamReader(fis));
List<HardwareProperty> map = new ArrayList<HardwareProperty>();
Map<String, HardwareProperty> map = new HashMap<String, HardwareProperty>();
String line = null;
HardwareProperty prop = null;
@@ -115,15 +124,15 @@ public class HardwareProperties {
if (HW_PROP_NAME.equals(valueName)) {
prop = new HardwareProperty();
prop.mName = value;
map.add(prop);
map.put(prop.mName, prop);
}
if (prop == null) {
log.warning("Error parsing '%1$s': missing '%2$s'",
file.getAbsolutePath(), HW_PROP_NAME);
return null;
}
if (HW_PROP_TYPE.equals(valueName)) {
prop.mType = ValueType.getEnum(value);
} else if (HW_PROP_DEFAULT.equals(valueName)) {
@@ -140,7 +149,7 @@ public class HardwareProperties {
}
}
}
return map;
} catch (FileNotFoundException e) {
// this should not happen since we usually test the file existence before
@@ -156,4 +165,16 @@ public class HardwareProperties {
return null;
}
/**
* Returns the index of <var>value</var> in {@link #BOOLEAN_VALUES}.
*/
public static int getBooleanValueIndex(String value) {
if (BOOLEAN_YES.equals(value)) {
return 0;
} else if (BOOLEAN_NO.equals(value)) {
return 1;
}
return -1;
}
}

View File

@@ -16,91 +16,33 @@
package com.android.sdklib.internal.project;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;
import java.util.Map.Entry;
/**
* Helper class to read and write Apk Configuration into a {@link ProjectProperties} file.
*/
public class ApkConfigurationHelper {
/** Prefix for property names for config definition. This prevents having config named
* after other valid properties such as "target". */
final static String CONFIG_PREFIX = "apk-config-";
/**
* Reads the Apk Configurations from a {@link ProjectProperties} file and returns them as a map.
* <p/>If there are no defined configurations, the returned map will be empty.
* @return a map of apk configurations. The map contains (name, filter) where name is
* the name of the configuration (a-zA-Z0-9 only), and filter is the comma separated list of
* resource configuration to include in the apk (see aapt -c)
* Reads the project settings from a {@link ProjectProperties} file and returns them as a
* {@link ApkSettings} object.
*/
public static Map<String, String> getConfigs(ProjectProperties properties) {
HashMap<String, String> configMap = new HashMap<String, String>();
public static ApkSettings getSettings(ProjectProperties properties) {
ApkSettings apkSettings = new ApkSettings();
// get the list of configs.
String configList = properties.getProperty(ProjectProperties.PROPERTY_APK_CONFIGS);
if (configList != null) {
// this is a comma separated list
String[] configs = configList.split(","); //$NON-NLS-1$
// read the value of each config and store it in a map
for (String config : configs) {
config = config.trim();
String configValue = properties.getProperty(CONFIG_PREFIX + config);
if (configValue != null) {
configMap.put(config, configValue);
}
}
}
return configMap;
boolean splitByDensity = Boolean.parseBoolean(properties.getProperty(
ProjectProperties.PROPERTY_SPLIT_BY_DENSITY));
apkSettings.setSplitByDensity(splitByDensity);
return apkSettings;
}
/**
* Writes the Apk Configurations from a given map into a {@link ProjectProperties}.
* @param properties the {@link ProjectProperties} in which to store the apk configurations.
* @param configMap a map of apk configurations. The map contains (name, filter) where name is
* the name of the configuration (a-zA-Z0-9 only), and filter is the comma separated list of
* resource configuration to include in the apk (see aapt -c)
* @return true if the {@link ProjectProperties} contained Apk Configuration that were not
* present in the map.
* Sets the content of a {@link ApkSettings} into a {@link ProjectProperties}.
* @param properties the {@link ProjectProperties} in which to store the settings.
* @param settings the project settings to store.
*/
public static boolean setConfigs(ProjectProperties properties, Map<String, String> configMap) {
// load the current configs, in order to remove the value properties for each of them
// in case a config was removed.
// get the list of configs.
String configList = properties.getProperty(ProjectProperties.PROPERTY_APK_CONFIGS);
boolean hasRemovedConfig = false;
if (configList != null) {
// this is a comma separated list
String[] configs = configList.split(","); //$NON-NLS-1$
for (String config : configs) {
config = config.trim();
if (configMap.containsKey(config) == false) {
hasRemovedConfig = true;
properties.removeProperty(CONFIG_PREFIX + config);
}
}
}
// now add the properties.
Set<Entry<String, String>> entrySet = configMap.entrySet();
StringBuilder sb = new StringBuilder();
for (Entry<String, String> entry : entrySet) {
if (sb.length() > 0) {
sb.append(",");
}
sb.append(entry.getKey());
properties.setProperty(CONFIG_PREFIX + entry.getKey(), entry.getValue());
}
properties.setProperty(ProjectProperties.PROPERTY_APK_CONFIGS, sb.toString());
return hasRemovedConfig;
public static void setProperties(ProjectProperties properties, ApkSettings settings) {
properties.setProperty(ProjectProperties.PROPERTY_SPLIT_BY_DENSITY,
Boolean.toString(settings.isSplitByDpi()));
}
}

View File

@@ -0,0 +1,58 @@
/*
* Copyright (C) 2009 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.sdklib.internal.project;
import java.util.HashMap;
import java.util.Map;
/**
* Settings for multiple APK generation.
*/
public class ApkSettings {
private boolean mSplitByDpi = false;
public ApkSettings() {
}
/**
* Returns a map of configuration filters to be used by the -c option of aapt.
* <p/>The map stores (key, value) pairs where the keys is a filename modifier and the value
* is the parameter to pass to aapt through the -c option.
* @return a map, always. It can however be empty.
*/
public Map<String, String> getResourceFilters() {
Map<String, String> map = new HashMap<String, String>();
if (mSplitByDpi) {
map.put("hdpi", "hdpi,nodpi");
map.put("mdpi", "mdpi,nodpi");
map.put("ldpi", "ldpi,nodpi");
}
return map;
}
/**
* Indicates whether APKs should be generate for each dpi level.
*/
public boolean isSplitByDpi() {
return mSplitByDpi;
}
public void setSplitByDensity(boolean split) {
mSplitByDpi = split;
}
}

View File

@@ -20,6 +20,8 @@ import com.android.sdklib.IAndroidTarget;
import com.android.sdklib.ISdkLog;
import com.android.sdklib.SdkConstants;
import com.android.sdklib.internal.project.ProjectProperties.PropertyType;
import com.android.sdklib.xml.AndroidManifest;
import com.android.sdklib.xml.AndroidXPathFactory;
import org.w3c.dom.NodeList;
import org.xml.sax.InputSource;
@@ -27,25 +29,22 @@ import org.xml.sax.InputSource;
import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.FileReader;
import java.io.FileWriter;
import java.io.IOException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import java.util.regex.Pattern;
import javax.xml.XMLConstants;
import javax.xml.namespace.NamespaceContext;
import javax.xml.xpath.XPath;
import javax.xml.xpath.XPathConstants;
import javax.xml.xpath.XPathExpressionException;
import javax.xml.xpath.XPathFactory;
/**
* Creates the basic files needed to get an Android project up and running. Also
* allows creation of IntelliJ project files.
* Creates the basic files needed to get an Android project up and running.
*
* @hide
*/
@@ -55,12 +54,23 @@ public class ProjectCreator {
private final static String PH_JAVA_FOLDER = "PACKAGE_PATH";
/** Package name substitution string used in template files, i.e. "PACKAGE" */
private final static String PH_PACKAGE = "PACKAGE";
/** Activity name substitution string used in template files, i.e. "ACTIVITY_NAME". */
/** Activity name substitution string used in template files, i.e. "ACTIVITY_NAME".
* @deprecated This is only used for older templates. For new ones see
* {@link #PH_ACTIVITY_ENTRY_NAME}, and {@link #PH_ACTIVITY_CLASS_NAME}. */
private final static String PH_ACTIVITY_NAME = "ACTIVITY_NAME";
/** Activity name substitution string used in manifest templates, i.e. "ACTIVITY_ENTRY_NAME".*/
private final static String PH_ACTIVITY_ENTRY_NAME = "ACTIVITY_ENTRY_NAME";
/** Activity name substitution string used in class templates, i.e. "ACTIVITY_CLASS_NAME".*/
private final static String PH_ACTIVITY_CLASS_NAME = "ACTIVITY_CLASS_NAME";
/** Activity FQ-name substitution string used in class templates, i.e. "ACTIVITY_FQ_NAME".*/
private final static String PH_ACTIVITY_FQ_NAME = "ACTIVITY_FQ_NAME";
/** Original Activity class name substitution string used in class templates, i.e.
* "ACTIVITY_TESTED_CLASS_NAME".*/
private final static String PH_ACTIVITY_TESTED_CLASS_NAME = "ACTIVITY_TESTED_CLASS_NAME";
/** Project name substitution string used in template files, i.e. "PROJECT_NAME". */
private final static String PH_PROJECT_NAME = "PROJECT_NAME";
private final static String FOLDER_TESTS = "tests";
/** Application icon substitution string used in the manifest template */
private final static String PH_ICON = "ICON";
/** Pattern for characters accepted in a project name. Since this will be used as a
* directory name, we're being a bit conservative on purpose: dot and space cannot be used. */
@@ -101,6 +111,7 @@ public class ProjectCreator {
/** default UID. This will not be serialized anyway. */
private static final long serialVersionUID = 1L;
@SuppressWarnings("unused")
ProjectCreateException(String message) {
super(message);
}
@@ -135,17 +146,16 @@ public class ProjectCreator {
* {@link #RE_PROJECT_NAME} regex.
* @param packageName the package of the project. The name must match the
* {@link #RE_PACKAGE_NAME} regex.
* @param activityName the activity of the project as it will appear in the manifest. Can be
* @param activityEntry the activity of the project as it will appear in the manifest. Can be
* null if no activity should be created. The name must match the
* {@link #RE_ACTIVITY_NAME} regex.
* @param target the project target.
* @param isTestProject whether the project to create is a test project. Caller should
* initially call this will false. The method will call itself back to create
* a test project as needed.
* @param pathToMainProject if non-null the project will be setup to test a main project
* located at the given path.
*/
public void createProject(String folderPath, String projectName,
String packageName, String activityName, IAndroidTarget target,
boolean isTestProject) {
String packageName, String activityEntry, IAndroidTarget target,
String pathToMainProject) {
// create project folder if it does not exist
File projectFolder = new File(folderPath);
@@ -185,6 +195,8 @@ public class ProjectCreator {
}
try {
boolean isTestProject = pathToMainProject != null;
// first create the project properties.
// location of the SDK goes in localProperty
@@ -202,7 +214,18 @@ public class ProjectCreator {
// create a build.properties file with just the application package
ProjectProperties buildProperties = ProjectProperties.create(folderPath,
PropertyType.BUILD);
buildProperties.setProperty(ProjectProperties.PROPERTY_APP_PACKAGE, packageName);
// only put application.package for older target where the rules file didn't.
// grab it through xpath
if (target.getVersion().getApiLevel() < 4) {
buildProperties.setProperty(ProjectProperties.PROPERTY_APP_PACKAGE, packageName);
}
if (isTestProject) {
buildProperties.setProperty(ProjectProperties.PROPERTY_TESTED_PROJECT,
pathToMainProject);
}
buildProperties.save();
// create the map for place-holders of values to replace in the templates
@@ -217,19 +240,76 @@ public class ProjectCreator {
// put this path in the place-holder map for project files that needs to list
// files manually.
keywords.put(PH_JAVA_FOLDER, packagePath);
keywords.put(PH_PACKAGE, packageName);
if (activityName != null) {
keywords.put(PH_ACTIVITY_NAME, activityName);
// compute some activity related information
String fqActivityName = null, activityPath = null, activityClassName = null;
String originalActivityEntry = activityEntry;
String originalActivityClassName = null;
if (activityEntry != null) {
if (isTestProject) {
// append Test so that it doesn't collide with the main project activity.
activityEntry += "Test";
// get the classname from the original activity entry.
int pos = originalActivityEntry.lastIndexOf('.');
if (pos != -1) {
originalActivityClassName = originalActivityEntry.substring(pos + 1);
} else {
originalActivityClassName = originalActivityEntry;
}
}
// get the fully qualified name of the activity
fqActivityName = AndroidManifest.combinePackageAndClassName(packageName,
activityEntry);
// get the activity path (replace the . to /)
activityPath = stripString(fqActivityName.replace(".", File.separator),
File.separatorChar);
// remove the last segment, so that we only have the path to the activity, but
// not the activity filename itself.
activityPath = activityPath.substring(0,
activityPath.lastIndexOf(File.separatorChar));
// finally, get the class name for the activity
activityClassName = fqActivityName.substring(fqActivityName.lastIndexOf('.') + 1);
}
// at this point we have the following for the activity:
// activityEntry: this is the manifest entry. For instance .MyActivity
// fqActivityName: full-qualified class name: com.foo.MyActivity
// activityClassName: only the classname: MyActivity
// originalActivityClassName: the classname of the activity being tested (if applicable)
// Add whatever activity info is needed in the place-holder map.
// Older templates only expect ACTIVITY_NAME to be the same (and unmodified for tests).
if (target.getVersion().getApiLevel() < 4) { // legacy
if (originalActivityEntry != null) {
keywords.put(PH_ACTIVITY_NAME, originalActivityEntry);
}
} else {
// newer templates make a difference between the manifest entries, classnames,
// as well as the main and test classes.
if (activityEntry != null) {
keywords.put(PH_ACTIVITY_ENTRY_NAME, activityEntry);
keywords.put(PH_ACTIVITY_CLASS_NAME, activityClassName);
keywords.put(PH_ACTIVITY_FQ_NAME, fqActivityName);
if (originalActivityClassName != null) {
keywords.put(PH_ACTIVITY_TESTED_CLASS_NAME, originalActivityClassName);
}
}
}
// Take the project name from the command line if there's one
if (projectName != null) {
keywords.put(PH_PROJECT_NAME, projectName);
} else {
if (activityName != null) {
// Use the activity as project name
keywords.put(PH_PROJECT_NAME, activityName);
if (activityClassName != null) {
// Use the activity class name as project name
keywords.put(PH_PROJECT_NAME, activityClassName);
} else {
// We need a project name. Just pick up the basename of the project
// directory.
@@ -238,36 +318,43 @@ public class ProjectCreator {
}
}
// create the source folder and the java package folders.
String srcFolderPath = SdkConstants.FD_SOURCES + File.separator + packagePath;
File sourceFolder = createDirs(projectFolder, srcFolderPath);
String javaTemplate = "java_file.template";
String activityFileName = activityName + ".java";
if (isTestProject) {
javaTemplate = "java_tests_file.template";
activityFileName = activityName + "Test.java";
}
installTemplate(javaTemplate, new File(sourceFolder, activityFileName),
keywords, target);
// create the source folder for the activity
if (activityClassName != null) {
String srcActivityFolderPath = SdkConstants.FD_SOURCES + File.separator + activityPath;
File sourceFolder = createDirs(projectFolder, srcActivityFolderPath);
// create the generate source folder
srcFolderPath = SdkConstants.FD_GEN_SOURCES + File.separator + packagePath;
sourceFolder = createDirs(projectFolder, srcFolderPath);
String javaTemplate = isTestProject ? "java_tests_file.template"
: "java_file.template";
String activityFileName = activityClassName + ".java";
installTemplate(javaTemplate, new File(sourceFolder, activityFileName),
keywords, target);
} else {
// we should at least create 'src'
createDirs(projectFolder, SdkConstants.FD_SOURCES);
}
// create other useful folders
File resourceFodler = createDirs(projectFolder, SdkConstants.FD_RESOURCES);
File resourceFolder = createDirs(projectFolder, SdkConstants.FD_RESOURCES);
createDirs(projectFolder, SdkConstants.FD_OUTPUT);
createDirs(projectFolder, SdkConstants.FD_NATIVE_LIBS);
if (isTestProject == false) {
/* Make res files only for non test projects */
File valueFolder = createDirs(resourceFodler, SdkConstants.FD_VALUES);
File valueFolder = createDirs(resourceFolder, SdkConstants.FD_VALUES);
installTemplate("strings.template", new File(valueFolder, "strings.xml"),
keywords, target);
File layoutFolder = createDirs(resourceFodler, SdkConstants.FD_LAYOUT);
File layoutFolder = createDirs(resourceFolder, SdkConstants.FD_LAYOUT);
installTemplate("layout.template", new File(layoutFolder, "main.xml"),
keywords, target);
// create the icons
if (installIcons(resourceFolder, target)) {
keywords.put(PH_ICON, "android:icon=\"@drawable/icon\"");
} else {
keywords.put(PH_ICON, "");
}
}
/* Make AndroidManifest.xml and build.xml files */
@@ -283,16 +370,6 @@ public class ProjectCreator {
installTemplate("build.template",
new File(projectFolder, SdkConstants.FN_BUILD_XML),
keywords);
// if this is not a test project, then we create one.
if (isTestProject == false) {
// create the test project folder.
createDirs(projectFolder, FOLDER_TESTS);
File testProjectFolder = new File(folderPath, FOLDER_TESTS);
createProject(testProjectFolder.getAbsolutePath(), projectName, packageName,
activityName, target, true /*isTestProject*/);
}
} catch (ProjectCreateException e) {
mLog.error(e, null);
} catch (IOException e) {
@@ -317,23 +394,14 @@ public class ProjectCreator {
* @param projectName The project name from --name. Can be null.
*/
public void updateProject(String folderPath, IAndroidTarget target, String projectName) {
// project folder must exist and be a directory, since this is an update
File projectFolder = new File(folderPath);
if (!projectFolder.isDirectory()) {
mLog.error(null, "Project folder '%1$s' is not a valid directory, this is not an Android project you can update.",
projectFolder);
// since this is an update, check the folder does point to a project
File androidManifest = checkProjectFolder(folderPath);
if (androidManifest == null) {
return;
}
// Check AndroidManifest.xml is present
File androidManifest = new File(projectFolder, SdkConstants.FN_ANDROID_MANIFEST_XML);
if (!androidManifest.isFile()) {
mLog.error(null,
"%1$s not found in '%2$s', this is not an Android project you can update.",
SdkConstants.FN_ANDROID_MANIFEST_XML,
folderPath);
return;
}
// get the parent File.
File projectFolder = androidManifest.getParentFile();
// Check there's a default.properties with a target *or* --target was specified
ProjectProperties props = ProjectProperties.load(folderPath, PropertyType.DEFAULT);
@@ -415,9 +483,16 @@ public class ProjectCreator {
keywords.put(PH_PROJECT_NAME, projectName);
} else {
extractPackageFromManifest(androidManifest, keywords);
if (keywords.containsKey(PH_ACTIVITY_NAME)) {
if (keywords.containsKey(PH_ACTIVITY_ENTRY_NAME)) {
String activity = keywords.get(PH_ACTIVITY_ENTRY_NAME);
// keep only the last segment if applicable
int pos = activity.lastIndexOf('.');
if (pos != -1) {
activity = activity.substring(pos + 1);
}
// Use the activity as project name
keywords.put(PH_PROJECT_NAME, keywords.get(PH_ACTIVITY_NAME));
keywords.put(PH_PROJECT_NAME, activity);
} else {
// We need a project name. Just pick up the basename of the project
// directory.
@@ -442,6 +517,84 @@ public class ProjectCreator {
}
}
/**
* Updates a test project with a new path to the main (tested) project.
* @param folderPath the path of the test project.
* @param pathToMainProject the path to the main project, relative to the test project.
*/
public void updateTestProject(final String folderPath, final String pathToMainProject) {
// since this is an update, check the folder does point to a project
if (checkProjectFolder(folderPath) == null) {
return;
}
// check the path to the main project is valid.
File mainProject = new File(pathToMainProject);
String resolvedPath;
if (mainProject.isAbsolute() == false) {
mainProject = new File(folderPath, pathToMainProject);
try {
resolvedPath = mainProject.getCanonicalPath();
} catch (IOException e) {
mLog.error(e, "Unable to resolve path to main project: %1$s", pathToMainProject);
return;
}
} else {
resolvedPath = mainProject.getAbsolutePath();
}
println("Resolved location of main project to: %1$s", resolvedPath);
// check the main project exists
if (checkProjectFolder(resolvedPath) == null) {
return;
}
ProjectProperties props = ProjectProperties.create(folderPath, PropertyType.BUILD);
// set or replace the path to the main project
props.setProperty(ProjectProperties.PROPERTY_TESTED_PROJECT, pathToMainProject);
try {
props.save();
println("Updated %1$s", PropertyType.BUILD.getFilename());
} catch (IOException e) {
mLog.error(e, "Failed to write %1$s file in '%2$s'",
PropertyType.BUILD.getFilename(),
folderPath);
return;
}
}
/**
* Checks whether the give <var>folderPath</var> is a valid project folder, and returns
* a {@link File} to the AndroidManifest.xml file.
* <p/>This checks that the folder exists and contains an AndroidManifest.xml file in it.
* <p/>Any error are output using {@link #mLog}.
* @param folderPath the folder to check
* @return a {@link File} to the AndroidManifest.xml file, or null otherwise.
*/
private File checkProjectFolder(String folderPath) {
// project folder must exist and be a directory, since this is an update
File projectFolder = new File(folderPath);
if (!projectFolder.isDirectory()) {
mLog.error(null, "Project folder '%1$s' is not a valid directory, this is not an Android project you can update.",
projectFolder);
return null;
}
// Check AndroidManifest.xml is present
File androidManifest = new File(projectFolder, SdkConstants.FN_ANDROID_MANIFEST_XML);
if (!androidManifest.isFile()) {
mLog.error(null,
"%1$s not found in '%2$s', this is not an Android project you can update.",
SdkConstants.FN_ANDROID_MANIFEST_XML,
folderPath);
return null;
}
return androidManifest;
}
/**
* Returns true if any line of the input file contains the requested regexp.
*/
@@ -470,7 +623,7 @@ public class ProjectCreator {
* Extracts a "full" package & activity name from an AndroidManifest.xml.
* <p/>
* The keywords dictionary is always filed the package name under the key {@link #PH_PACKAGE}.
* If an activity name can be found, it is filed under the key {@link #PH_ACTIVITY_NAME}.
* If an activity name can be found, it is filed under the key {@link #PH_ACTIVITY_ENTRY_NAME}.
* When no activity is found, this key is not created.
*
* @param manifestFile The AndroidManifest.xml file
@@ -480,37 +633,7 @@ public class ProjectCreator {
private boolean extractPackageFromManifest(File manifestFile,
Map<String, String> outKeywords) {
try {
final String nsPrefix = "android";
final String nsURI = SdkConstants.NS_RESOURCES;
XPath xpath = XPathFactory.newInstance().newXPath();
xpath.setNamespaceContext(new NamespaceContext() {
public String getNamespaceURI(String prefix) {
if (nsPrefix.equals(prefix)) {
return nsURI;
}
return XMLConstants.NULL_NS_URI;
}
public String getPrefix(String namespaceURI) {
if (nsURI.equals(namespaceURI)) {
return nsPrefix;
}
return null;
}
@SuppressWarnings("unchecked")
public Iterator getPrefixes(String namespaceURI) {
if (nsURI.equals(namespaceURI)) {
ArrayList<String> list = new ArrayList<String>();
list.add(nsPrefix);
return list.iterator();
}
return null;
}
});
XPath xpath = AndroidXPathFactory.newXPath();
InputSource source = new InputSource(new FileReader(manifestFile));
String packageName = xpath.evaluate("/manifest/@package", source);
@@ -524,7 +647,7 @@ public class ProjectCreator {
String expression = String.format("/manifest/application/activity" +
"[intent-filter/action/@%1$s:name='android.intent.action.MAIN' and " +
"intent-filter/category/@%1$s:name='android.intent.category.LAUNCHER']" +
"/@%1$s:name", nsPrefix);
"/@%1$s:name", AndroidXPathFactory.DEFAULT_NS_PREFIX);
NodeList activityNames = (NodeList) xpath.evaluate(expression, source,
XPathConstants.NODESET);
@@ -564,9 +687,9 @@ public class ProjectCreator {
if (activityName.length() == 0) {
mLog.warning("Missing <activity %1$s:name=\"...\"> in '%2$s'.\n" +
"No activity will be generated.",
nsPrefix, manifestFile.getName());
AndroidXPathFactory.DEFAULT_NS_PREFIX, manifestFile.getName());
} else {
outKeywords.put(PH_ACTIVITY_NAME, activityName);
outKeywords.put(PH_ACTIVITY_ENTRY_NAME, activityName);
}
outKeywords.put(PH_PACKAGE, packageName);
@@ -666,8 +789,10 @@ public class ProjectCreator {
String line;
while ((line = in.readLine()) != null) {
for (String key : placeholderMap.keySet()) {
line = line.replace(key, placeholderMap.get(key));
if (placeholderMap != null) {
for (String key : placeholderMap.keySet()) {
line = line.replace(key, placeholderMap.get(key));
}
}
out.write(line);
@@ -686,6 +811,85 @@ public class ProjectCreator {
destFile);
}
/**
* Installs the project icons.
* @param resourceFolder the resource folder
* @param target the target of the project.
* @return true if any icon was installed.
*/
private boolean installIcons(File resourceFolder, IAndroidTarget target)
throws ProjectCreateException {
// query the target for its template directory
String templateFolder = target.getPath(IAndroidTarget.TEMPLATES);
boolean installedIcon = false;
installedIcon |= installIcon(templateFolder, "icon_hdpi.png", resourceFolder, "drawable-hdpi");
installedIcon |= installIcon(templateFolder, "icon_mdpi.png", resourceFolder, "drawable-mdpi");
installedIcon |= installIcon(templateFolder, "icon_ldpi.png", resourceFolder, "drawable-ldpi");
return installedIcon;
}
/**
* Installs an Icon in the project.
* @return true if the icon was installed.
*/
private boolean installIcon(String templateFolder, String iconName, File resourceFolder,
String folderName) throws ProjectCreateException {
File icon = new File(templateFolder, iconName);
if (icon.exists()) {
File drawable = createDirs(resourceFolder, folderName);
installBinaryFile(icon, new File(drawable, "icon.png"));
return true;
}
return false;
}
/**
* Installs a binary file
* @param source the source file to copy
* @param destination the destination file to write
*/
private void installBinaryFile(File source, File destination) {
byte[] buffer = new byte[8192];
FileInputStream fis = null;
FileOutputStream fos = null;
try {
fis = new FileInputStream(source);
fos = new FileOutputStream(destination);
int read;
while ((read = fis.read(buffer)) != -1) {
fos.write(buffer, 0, read);
}
} catch (FileNotFoundException e) {
// shouldn't happen since we check before.
} catch (IOException e) {
new ProjectCreateException(e, "Failed to read binary file: %1$s",
source.getAbsolutePath());
} finally {
if (fis != null) {
try {
fis.close();
} catch (IOException e) {
// ignore
}
}
if (fos != null) {
try {
fos.close();
} catch (IOException e) {
// ignore
}
}
}
}
/**
* Prints a message unless silence is enabled.
* <p/>

View File

@@ -35,9 +35,18 @@ import java.util.Map.Entry;
public final class ProjectProperties {
/** The property name for the project target */
public final static String PROPERTY_TARGET = "target";
public final static String PROPERTY_APK_CONFIGS = "apk-configurations";
public final static String PROPERTY_SDK = "sdk-location";
public final static String PROPERTY_APP_PACKAGE = "application-package";
public final static String PROPERTY_SDK = "sdk.dir";
// LEGACY - compatibility with 1.6 and before
public final static String PROPERTY_SDK_LEGACY = "sdk-location";
public final static String PROPERTY_APP_PACKAGE = "application.package";
// LEGACY - compatibility with 1.6 and before
public final static String PROPERTY_APP_PACKAGE_LEGACY = "application-package";
public final static String PROPERTY_SPLIT_BY_DENSITY = "split.density";
public final static String PROPERTY_TESTED_PROJECT = "tested.project.dir";
public static enum PropertyType {
BUILD("build.properties", BUILD_HEADER),
@@ -88,8 +97,8 @@ public final class ProjectProperties {
"# This file is only used by the Ant script.\n" +
"\n" +
"# You can use this to override default values such as\n" +
"# 'source-folder' for the location of your java source folder and\n" +
"# 'out-folder' for the location of your output folder.\n" +
"# 'source.dir' for the location of your java source folder and\n" +
"# 'out.dir' for the location of your output folder.\n" +
"\n" +
"# You can also use it define how the release builds are signed by declaring\n" +
"# the following properties:\n" +
@@ -103,17 +112,8 @@ public final class ProjectProperties {
// 1-------10--------20--------30--------40--------50--------60--------70--------80
COMMENT_MAP.put(PROPERTY_TARGET,
"# Project target.\n");
COMMENT_MAP.put(PROPERTY_APK_CONFIGS,
"# apk configurations. This property allows creation of APK files with limited\n" +
"# resources. For example, if your application contains many locales and\n" +
"# you wish to release multiple smaller apks instead of a large one, you can\n" +
"# define configuration to create apks with limited language sets.\n" +
"# Format is a comma separated list of configuration names. For each\n" +
"# configuration, a property will declare the resource configurations to\n" +
"# include. Example:\n" +
"# " + PROPERTY_APK_CONFIGS +"=european,northamerica\n" +
"# " + ApkConfigurationHelper.CONFIG_PREFIX + "european=en,fr,it,de,es\n" +
"# " + ApkConfigurationHelper.CONFIG_PREFIX + "northamerica=en,es\n");
COMMENT_MAP.put(PROPERTY_SPLIT_BY_DENSITY,
"# Indicates whether an apk should be generated for each density.\n");
COMMENT_MAP.put(PROPERTY_SDK,
"# location of the SDK. This is only used by Ant\n" +
"# For customization when using a Version Control System, please read the\n" +
@@ -251,8 +251,10 @@ public final class ProjectProperties {
writer.write(comment);
}
String value = entry.getValue();
value = value.replaceAll("\\\\", "\\\\\\\\");
writer.write(String.format("%s=%s\n", entry.getKey(), value));
if (value != null) {
value = value.replaceAll("\\\\", "\\\\\\\\");
writer.write(String.format("%s=%s\n", entry.getKey(), value));
}
}
// close the file to flush

View File

@@ -35,7 +35,7 @@ import java.util.Properties;
/**
* Represents an add-on XML node in an SDK repository.
*/
public class AddonPackage extends Package {
public class AddonPackage extends Package implements IPackageVersion {
private static final String PROP_NAME = "Addon.Name"; //$NON-NLS-1$
private static final String PROP_VENDOR = "Addon.Vendor"; //$NON-NLS-1$
@@ -89,6 +89,8 @@ public class AddonPackage extends Package {
* {@link IAndroidTarget#isPlatform()} false) from the {@link SdkManager}.
* This is used to list local SDK folders in which case there is one archive which
* URL is the actual target location.
* <p/>
* By design, this creates a package with one and only one archive.
*/
AddonPackage(IAndroidTarget target, Properties props) {
super( null, //source
@@ -187,18 +189,33 @@ public class AddonPackage extends Package {
/** Returns a short description for an {@link IDescription}. */
@Override
public String getShortDescription() {
return String.format("%1$s by %2$s for Android API %3$s",
return String.format("%1$s by %2$s, Android API %3$s, revision %4$s",
getName(),
getVendor(),
mVersion.getApiString());
mVersion.getApiString(),
getRevision());
}
/** Returns a long description for an {@link IDescription}. */
/**
* Returns a long description for an {@link IDescription}.
*
* The long description is whatever the XML contains for the &lt;description&gt; field,
* or the short description if the former is empty.
*/
@Override
public String getLongDescription() {
return String.format("%1$s,\nRevision %2$d.",
getShortDescription(),
getRevision());
String s = getDescription();
if (s == null || s.length() == 0) {
s = getShortDescription();
}
if (s.indexOf("revision") == -1) {
s += String.format("\nRevision %1$d", getRevision());
}
s += String.format("\nRequires SDK Platform Android API %1$s",
mVersion.getApiString());
return s;
}
/**

View File

@@ -23,6 +23,7 @@ import org.apache.commons.compress.archivers.zip.ZipArchiveEntry;
import org.apache.commons.compress.archivers.zip.ZipFile;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
@@ -369,42 +370,38 @@ public class Archive implements IDescription {
Package pkg = getParentPackage();
File archiveFile = null;
try {
String name = pkg.getShortDescription();
String name = pkg.getShortDescription();
if (pkg instanceof ExtraPackage && !((ExtraPackage) pkg).isPathValid()) {
monitor.setResult("Skipping %1$s: %2$s is not a valid install path.",
name,
((ExtraPackage) pkg).getPath());
return false;
if (pkg instanceof ExtraPackage && !((ExtraPackage) pkg).isPathValid()) {
monitor.setResult("Skipping %1$s: %2$s is not a valid install path.",
name,
((ExtraPackage) pkg).getPath());
return false;
}
if (isLocal()) {
// This should never happen.
monitor.setResult("Skipping already installed archive: %1$s for %2$s",
name,
getOsDescription());
return false;
}
if (!isCompatible()) {
monitor.setResult("Skipping incompatible archive: %1$s for %2$s",
name,
getOsDescription());
return false;
}
archiveFile = downloadFile(osSdkRoot, monitor, forceHttp);
if (archiveFile != null) {
if (unarchive(osSdkRoot, archiveFile, sdkManager, monitor)) {
monitor.setResult("Installed %1$s", name);
// Delete the temp archive if it exists, only on success
deleteFileOrFolder(archiveFile);
return true;
}
if (isLocal()) {
// This should never happen.
monitor.setResult("Skipping already installed archive: %1$s for %2$s",
name,
getOsDescription());
return false;
}
if (!isCompatible()) {
monitor.setResult("Skipping incompatible archive: %1$s for %2$s",
name,
getOsDescription());
return false;
}
archiveFile = downloadFile(monitor, forceHttp);
if (archiveFile != null) {
if (unarchive(osSdkRoot, archiveFile, sdkManager, monitor)) {
monitor.setResult("Installed: %1$s", name);
return true;
}
}
} finally {
// Delete the temp archive if it exists
deleteFileOrFolder(archiveFile);
}
return false;
@@ -414,56 +411,134 @@ public class Archive implements IDescription {
* Downloads an archive and returns the temp file with it.
* Caller is responsible with deleting the temp file when done.
*/
private File downloadFile(ITaskMonitor monitor, boolean forceHttp) {
private File downloadFile(String osSdkRoot, ITaskMonitor monitor, boolean forceHttp) {
File tmpFileToDelete = null;
try {
File tmpFile = File.createTempFile("sdkupload", ".bin"); //$NON-NLS-1$ //$NON-NLS-2$
tmpFileToDelete = tmpFile;
String name = getParentPackage().getShortDescription();
String desc = String.format("Downloading %1$s", name);
monitor.setDescription(desc);
monitor.setResult(desc);
String name = getParentPackage().getShortDescription();
String desc = String.format("Downloading %1$s", name);
monitor.setDescription(desc);
String link = getUrl();
if (!link.startsWith("http://") //$NON-NLS-1$
&& !link.startsWith("https://") //$NON-NLS-1$
&& !link.startsWith("ftp://")) { //$NON-NLS-1$
// Make the URL absolute by prepending the source
Package pkg = getParentPackage();
RepoSource src = pkg.getParentSource();
if (src == null) {
monitor.setResult("Internal error: no source for archive %1$s", name);
return null;
}
// take the URL to the repository.xml and remove the last component
// to get the base
String repoXml = src.getUrl();
int pos = repoXml.lastIndexOf('/');
String base = repoXml.substring(0, pos + 1);
link = base + link;
String link = getUrl();
if (!link.startsWith("http://") //$NON-NLS-1$
&& !link.startsWith("https://") //$NON-NLS-1$
&& !link.startsWith("ftp://")) { //$NON-NLS-1$
// Make the URL absolute by prepending the source
Package pkg = getParentPackage();
RepoSource src = pkg.getParentSource();
if (src == null) {
monitor.setResult("Internal error: no source for archive %1$s", name);
return null;
}
if (forceHttp) {
link = link.replaceAll("https://", "http://"); //$NON-NLS-1$ //$NON-NLS-2$
}
// take the URL to the repository.xml and remove the last component
// to get the base
String repoXml = src.getUrl();
int pos = repoXml.lastIndexOf('/');
String base = repoXml.substring(0, pos + 1);
if (fetchUrl(tmpFile, link, desc, monitor)) {
// Fetching was successful, don't delete the temp file here!
tmpFileToDelete = null;
link = base + link;
}
if (forceHttp) {
link = link.replaceAll("https://", "http://"); //$NON-NLS-1$ //$NON-NLS-2$
}
// Get the basename of the file we're downloading, i.e. the last component
// of the URL
int pos = link.lastIndexOf('/');
String base = link.substring(pos + 1);
// Rather than create a real temp file in the system, we simply use our
// temp folder (in the SDK base folder) and use the archive name for the
// download. This allows us to reuse or continue downloads.
File tmpFile = new File(getTempFolder(osSdkRoot), base);
// if the file exists, check if its checksum & size. Use it if complete
if (tmpFile.exists()) {
if (tmpFile.length() == getSize() &&
fileChecksum(tmpFile, monitor).equalsIgnoreCase(getChecksum())) {
// File is good, let's use it.
return tmpFile;
}
} catch (IOException e) {
// Existing file is either of different size or content.
// TODO: continue download when we support continue mode.
// Right now, let's simply remove the file and start over.
deleteFileOrFolder(tmpFile);
}
if (fetchUrl(tmpFile, link, desc, monitor)) {
// Fetching was successful, let's use this file.
return tmpFile;
} else {
// Delete the temp file if we aborted the download
// TODO: disable this when we want to support partial downloads!
deleteFileOrFolder(tmpFile);
return null;
}
}
/**
* Computes the SHA-1 checksum of the content of the given file.
* Returns an empty string on error (rather than null).
*/
private String fileChecksum(File tmpFile, ITaskMonitor monitor) {
InputStream is = null;
try {
is = new FileInputStream(tmpFile);
MessageDigest digester = getChecksumType().getMessageDigest();
byte[] buf = new byte[65536];
int n;
while ((n = is.read(buf)) >= 0) {
if (n > 0) {
digester.update(buf, 0, n);
}
}
return getDigestChecksum(digester);
} catch (FileNotFoundException e) {
// The FNF message is just the URL. Make it a bit more useful.
monitor.setResult("File not found: %1$s", e.getMessage());
} catch (Exception e) {
monitor.setResult(e.getMessage());
} finally {
deleteFileOrFolder(tmpFileToDelete);
if (is != null) {
try {
is.close();
} catch (IOException e) {
// pass
}
}
}
return null;
return ""; //$NON-NLS-1$
}
/**
* Returns the SHA-1 from a {@link MessageDigest} as an hex string
* that can be compared with {@link #getChecksum()}.
*/
private String getDigestChecksum(MessageDigest digester) {
int n;
// Create an hex string from the digest
byte[] digest = digester.digest();
n = digest.length;
String hex = "0123456789abcdef"; //$NON-NLS-1$
char[] hexDigest = new char[n * 2];
for (int i = 0; i < n; i++) {
int b = digest[i] & 0x0FF;
hexDigest[i*2 + 0] = hex.charAt(b >>> 4);
hexDigest[i*2 + 1] = hex.charAt(b & 0x0f);
}
return new String(hexDigest);
}
/**
@@ -553,18 +628,8 @@ public class Archive implements IDescription {
}
// Create an hex string from the digest
byte[] digest = digester.digest();
n = digest.length;
String hex = "0123456789abcdef"; //$NON-NLS-1$
char[] hexDigest = new char[n * 2];
for (int i = 0; i < n; i++) {
int b = digest[i] & 0x0FF;
hexDigest[i*2 + 0] = hex.charAt(b >>> 4);
hexDigest[i*2 + 1] = hex.charAt(b & 0x0f);
}
String actual = getDigestChecksum(digester);
String expected = getChecksum();
String actual = new String(hexDigest);
if (!actual.equalsIgnoreCase(expected)) {
monitor.setResult("Download finished with wrong checksum. Expected %1$s, got %2$s.",
expected, actual);
@@ -611,6 +676,7 @@ public class Archive implements IDescription {
String pkgName = getParentPackage().getShortDescription();
String pkgDesc = String.format("Installing %1$s", pkgName);
monitor.setDescription(pkgDesc);
monitor.setResult(pkgDesc);
// We always unzip in a temp folder which name depends on the package type
// (e.g. addon, tools, etc.) and then move the folder to the destination folder.
@@ -625,7 +691,7 @@ public class Archive implements IDescription {
try {
// Find a new temp folder that doesn't exist yet
unzipDestFolder = findTempFolder(osSdkRoot, pkgKind, "new"); //$NON-NLS-1$
unzipDestFolder = createTempFolder(osSdkRoot, pkgKind, "new"); //$NON-NLS-1$
if (unzipDestFolder == null) {
// this should not seriously happen.
@@ -660,39 +726,72 @@ public class Archive implements IDescription {
}
// Swap the old folder by the new one.
File renameFailedForDir = null;
if (destFolder.isDirectory()) {
renamedDestFolder = findTempFolder(osSdkRoot, pkgKind, "old"); //$NON-NLS-1$
if (renamedDestFolder == null) {
// this should not seriously happen.
monitor.setResult("Failed to find a temp directory in %1$s.", osSdkRoot);
// We have 2 "folder rename" (aka moves) to do.
// They must both succeed in the right order.
boolean move1done = false;
boolean move2done = false;
while (!move1done || !move2done) {
File renameFailedForDir = null;
// Case where the dest dir already exists
if (!move1done) {
if (destFolder.isDirectory()) {
// Create a new temp/old dir
if (renamedDestFolder == null) {
renamedDestFolder = createTempFolder(osSdkRoot, pkgKind, "old"); //$NON-NLS-1$
}
if (renamedDestFolder == null) {
// this should not seriously happen.
monitor.setResult("Failed to find a temp directory in %1$s.", osSdkRoot);
return false;
}
// try to move the current dest dir to the temp/old one
if (!destFolder.renameTo(renamedDestFolder)) {
monitor.setResult("Failed to rename directory %1$s to %2$s.",
destFolder.getPath(), renamedDestFolder.getPath());
renameFailedForDir = destFolder;
}
}
move1done = (renameFailedForDir == null);
}
// Case where there's no dest dir or we successfully moved it to temp/old
// We not try to move the temp/unzip to the dest dir
if (move1done && !move2done) {
if (renameFailedForDir == null && !unzipDestFolder.renameTo(destFolder)) {
monitor.setResult("Failed to rename directory %1$s to %2$s",
unzipDestFolder.getPath(), destFolder.getPath());
renameFailedForDir = unzipDestFolder;
}
move2done = (renameFailedForDir == null);
}
if (renameFailedForDir != null) {
if (SdkConstants.CURRENT_PLATFORM == SdkConstants.PLATFORM_WINDOWS) {
String msg = String.format(
"-= Warning ! =-\n" +
"A folder failed to be renamed or moved. On Windows this " +
"typically means that a program is using that folder (for example " +
"Windows Explorer or your anti-virus software.)\n" +
"Please momentarily deactivate your anti-virus software.\n" +
"Please also close any running programs that may be accessing " +
"the directory '%1$s'.\n" +
"When ready, press YES to try again.",
renameFailedForDir.getPath());
if (monitor.displayPrompt("SDK Manager: failed to install", msg)) {
// loop, trying to rename the temp dir into the destination
continue;
}
}
return false;
}
if (!destFolder.renameTo(renamedDestFolder)) {
monitor.setResult("Failed to rename directory %1$s to %2$s",
destFolder.getPath(), renamedDestFolder.getPath());
renameFailedForDir = destFolder;
}
}
if (renameFailedForDir == null && !unzipDestFolder.renameTo(destFolder)) {
monitor.setResult("Failed to rename directory %1$s to %2$s",
unzipDestFolder.getPath(), destFolder.getPath());
renameFailedForDir = unzipDestFolder;
}
if (renameFailedForDir != null) {
if (SdkConstants.CURRENT_PLATFORM == SdkConstants.PLATFORM_WINDOWS) {
monitor.setResult(
"-= Warning ! =-\n" +
"A folder failed to be renamed or moved. On Windows this " +
"typically means that a program is using that folder (for example " +
"Windows Explorer.) Please close all running programs that may be " +
"locking the directory '%1$s' and try again.",
renameFailedForDir.getPath());
}
return false;
break;
}
unzipDestFolder = null;
@@ -805,8 +904,12 @@ public class Archive implements IDescription {
}
// if needed set the permissions.
if (usingUnixPerm) {
setPermission(destFile, entry.getUnixMode());
if (usingUnixPerm && destFile.isFile()) {
// get the mode and test if it contains the executable bit
int mode = entry.getUnixMode();
if ((mode & 0111) != 0) {
setExecutablePermission(destFile);
}
}
// Increment progress bar to match. We update only between files.
@@ -844,7 +947,7 @@ public class Archive implements IDescription {
}
/**
* Finds a temp folder in the form of osBasePath/temp/prefix.suffixNNN.
* Creates a temp folder in the form of osBasePath/temp/prefix.suffixNNN.
* <p/>
* This operation is not atomic so there's no guarantee the folder can't get
* created in between. This is however unlikely and the caller can assume the
@@ -853,8 +956,8 @@ public class Archive implements IDescription {
* Returns null if no such folder can be found (e.g. if all candidates exist,
* which is rather unlikely) or if the base temp folder cannot be created.
*/
private File findTempFolder(String osBasePath, String prefix, String suffix) {
File baseTempFolder = new File(osBasePath, "temp");
private File createTempFolder(String osBasePath, String prefix, String suffix) {
File baseTempFolder = getTempFolder(osBasePath);
if (!baseTempFolder.isDirectory()) {
if (!baseTempFolder.mkdirs()) {
@@ -872,6 +975,15 @@ public class Archive implements IDescription {
return null;
}
/**
* Returns the temp folder used by the SDK Manager.
* This folder is always at osBasePath/temp.
*/
private File getTempFolder(String osBasePath) {
File baseTempFolder = new File(osBasePath, "temp"); //$NON-NLS-1$
return baseTempFolder;
}
/**
* Deletes a file or a directory.
* Directories are deleted recursively.
@@ -926,19 +1038,14 @@ public class Archive implements IDescription {
}
/**
* Sets the Unix permission on a file or folder.
* Sets the executable Unix permission (0777) on a file or folder.
* @param file The file to set permissions on.
* @param unixMode the permissions as received from {@link ZipArchiveEntry#getUnixMode()}.
* @throws IOException
*/
private void setPermission(File file, int unixMode) throws IOException {
// permissions contains more than user/group/all, and we need the 777 display mode, so we
// convert it in octal string and take the last 3 digits.
String permission = String.format("%o", unixMode);
permission = permission.substring(permission.length() - 3, permission.length());
private void setExecutablePermission(File file) throws IOException {
Runtime.getRuntime().exec(new String[] {
"chmod", permission, file.getAbsolutePath()
"chmod", "777", file.getAbsolutePath()
});
}
}

View File

@@ -32,7 +32,7 @@ import java.util.Properties;
/**
* Represents a doc XML node in an SDK repository.
*/
public class DocPackage extends Package {
public class DocPackage extends Package implements IPackageVersion {
private final AndroidVersion mVersion;
@@ -56,6 +56,8 @@ public class DocPackage extends Package {
* Manually create a new package with one archive and the given attributes.
* This is used to create packages from local directories in which case there must be
* one archive which URL is the actual target location.
* <p/>
* By design, this creates a package with one and only one archive.
*/
DocPackage(RepoSource source,
Properties props,
@@ -101,21 +103,34 @@ public class DocPackage extends Package {
@Override
public String getShortDescription() {
if (mVersion.isPreview()) {
return String.format("Documentation for Android '%1$s' Preview SDK",
mVersion.getCodename());
} else if (mVersion.getApiLevel() != 0) {
return String.format("Documentation for Android SDK, API %1$d", mVersion.getApiLevel());
return String.format("Documentation for Android '%1$s' Preview SDK, revision %2$s",
mVersion.getCodename(),
getRevision());
} else {
return String.format("Documentation for Android SDK");
return String.format("Documentation for Android SDK, API %1$d, revision %2$s",
mVersion.getApiLevel(),
getRevision());
}
}
/** Returns a long description for an {@link IDescription}. */
/**
* Returns a long description for an {@link IDescription}.
*
* The long description is whatever the XML contains for the &lt;description&gt; field,
* or the short description if the former is empty.
*/
@Override
public String getLongDescription() {
return String.format("%1$s,\nRevision %2$d.",
getShortDescription(),
getRevision());
String s = getDescription();
if (s == null || s.length() == 0) {
s = getShortDescription();
}
if (s.indexOf("revision") == -1) {
s += String.format("\nRevision %1$d", getRevision());
}
return s;
}
/**

View File

@@ -31,10 +31,9 @@ import java.util.Properties;
/**
* Represents a extra XML node in an SDK repository.
*/
public class ExtraPackage extends Package {
public class ExtraPackage extends MinToolsPackage {
private static final String PROP_PATH = "Extra.Path"; //$NON-NLS-1$
private static final String PROP_MIN_TOOLS_REV = "Extra.MinToolsRev"; //$NON-NLS-1$
/**
* The install folder name. It must be a single-segment path.
@@ -44,18 +43,6 @@ public class ExtraPackage extends Package {
*/
private final String mPath;
/**
* The minimal revision of the tools package required by this extra package, if > 0,
* or {@link #MIN_TOOLS_REV_NOT_SPECIFIED} if there is no such requirement.
*/
private final int mMinToolsRevision;
/**
* The value of {@link #mMinToolsRevision} when the {@link SdkRepository#NODE_MIN_TOOLS_REV}
* was not specified in the XML source.
*/
public static final int MIN_TOOLS_REV_NOT_SPECIFIED = 0;
/**
* Creates a new tool package from the attributes and elements of the given XML node.
* <p/>
@@ -63,15 +50,16 @@ public class ExtraPackage extends Package {
*/
ExtraPackage(RepoSource source, Node packageNode, Map<String,String> licenses) {
super(source, packageNode, licenses);
mPath = XmlParserUtils.getXmlString(packageNode, SdkRepository.NODE_PATH);
mMinToolsRevision = XmlParserUtils.getXmlInt(packageNode, SdkRepository.NODE_MIN_TOOLS_REV,
MIN_TOOLS_REV_NOT_SPECIFIED);
}
/**
* Manually create a new package with one archive and the given attributes or properties.
* This is used to create packages from local directories in which case there must be
* one archive which URL is the actual target location.
* <p/>
* By design, this creates a package with one and only one archive.
*/
ExtraPackage(RepoSource source,
Properties props,
@@ -92,11 +80,9 @@ public class ExtraPackage extends Package {
archiveOs,
archiveArch,
archiveOsPath);
// The path argument comes before whatever could be in the properties
mPath = path != null ? path : getProperty(props, PROP_PATH, path);
mMinToolsRevision = Integer.parseInt(getProperty(props, PROP_MIN_TOOLS_REV,
Integer.toString(MIN_TOOLS_REV_NOT_SPECIFIED)));
}
/**
@@ -109,8 +95,8 @@ public class ExtraPackage extends Package {
props.setProperty(PROP_PATH, mPath);
if (mMinToolsRevision != MIN_TOOLS_REV_NOT_SPECIFIED) {
props.setProperty(PROP_MIN_TOOLS_REV, Integer.toString(mMinToolsRevision));
if (getMinToolsRevision() != MIN_TOOLS_REV_NOT_SPECIFIED) {
props.setProperty(PROP_MIN_TOOLS_REV, Integer.toString(getMinToolsRevision()));
}
}
@@ -137,14 +123,6 @@ public class ExtraPackage extends Package {
return mPath;
}
/**
* The minimal revision of the tools package required by this extra package, if > 0,
* or {@link #MIN_TOOLS_REV_NOT_SPECIFIED} if there is no such requirement.
*/
public int getMinToolsRevision() {
return mMinToolsRevision;
}
/** Returns a short description for an {@link IDescription}. */
@Override
public String getShortDescription() {
@@ -174,25 +152,29 @@ public class ExtraPackage extends Package {
name,
getRevision());
if (mMinToolsRevision != MIN_TOOLS_REV_NOT_SPECIFIED) {
s += String.format(" (tools rev: %1$d)", mMinToolsRevision);
}
return s;
}
/** Returns a long description for an {@link IDescription}. */
/**
* Returns a long description for an {@link IDescription}.
*
* The long description is whatever the XML contains for the &lt;description&gt; field,
* or the short description if the former is empty.
*/
@Override
public String getLongDescription() {
String s = String.format("Extra %1$s package, revision %2$d",
getPath(),
getRevision());
if (mMinToolsRevision != MIN_TOOLS_REV_NOT_SPECIFIED) {
s += String.format(" (min tools rev.: %1$d)", mMinToolsRevision);
String s = getDescription();
if (s == null || s.length() == 0) {
s = String.format("Extra %1$s package", getPath());
}
s += ".";
if (s.indexOf("revision") == -1) {
s += String.format("\nRevision %1$d", getRevision());
}
if (getMinToolsRevision() != MIN_TOOLS_REV_NOT_SPECIFIED) {
s += String.format("\nRequires tools revision %1$d", getMinToolsRevision());
}
return s;
}

View File

@@ -0,0 +1,31 @@
/*
* Copyright (C) 2009 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.sdklib.internal.repository;
import com.android.sdklib.AndroidVersion;
/**
* Interface for packages that provide an {@link AndroidVersion}.
*/
public interface IPackageVersion {
/**
* Returns the version, for platform, add-on and doc packages.
* Can be 0 if this is a local package of unknown api-level.
*/
public abstract AndroidVersion getVersion();
}

View File

@@ -81,4 +81,17 @@ public interface ITaskMonitor {
* tickCount must be 1 or more.
*/
public ITaskMonitor createSubMonitor(int tickCount);
/**
* Display a yes/no question dialog box.
*
* Implementations MUST allow this to be called from any thread, e.g. by
* making sure the dialog is opened synchronously in the ui thread.
*
* @param title The title of the dialog box
* @param message The error message
* @return true if YES was clicked.
*/
public boolean displayPrompt(final String title, final String message);
}

View File

@@ -0,0 +1,93 @@
/*
* Copyright (C) 2009 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.sdklib.internal.repository;
import com.android.sdklib.internal.repository.Archive.Arch;
import com.android.sdklib.internal.repository.Archive.Os;
import com.android.sdklib.repository.SdkRepository;
import org.w3c.dom.Node;
import java.util.Map;
import java.util.Properties;
/**
* Represents an XML node in an SDK repository that has a min-tools-rev requirement.
* This is either a {@link PlatformPackage} or an {@link ExtraPackage}.
*/
public abstract class MinToolsPackage extends Package {
protected static final String PROP_MIN_TOOLS_REV = "Platform.MinToolsRev"; //$NON-NLS-1$
/**
* The minimal revision of the tools package required by this extra package, if > 0,
* or {@link #MIN_TOOLS_REV_NOT_SPECIFIED} if there is no such requirement.
*/
private final int mMinToolsRevision;
/**
* The value of {@link #mMinToolsRevision} when the {@link SdkRepository#NODE_MIN_TOOLS_REV}
* was not specified in the XML source.
*/
public static final int MIN_TOOLS_REV_NOT_SPECIFIED = 0;
/**
* Creates a new package from the attributes and elements of the given XML node.
* <p/>
* This constructor should throw an exception if the package cannot be created.
*/
MinToolsPackage(RepoSource source, Node packageNode, Map<String,String> licenses) {
super(source, packageNode, licenses);
mMinToolsRevision = XmlParserUtils.getXmlInt(packageNode, SdkRepository.NODE_MIN_TOOLS_REV,
MIN_TOOLS_REV_NOT_SPECIFIED);
}
/**
* Manually create a new package with one archive and the given attributes.
* This is used to create packages from local directories in which case there must be
* one archive which URL is the actual target location.
* <p/>
* Properties from props are used first when possible, e.g. if props is non null.
* <p/>
* By design, this creates a package with one and only one archive.
*/
public MinToolsPackage(
RepoSource source,
Properties props,
int revision,
String license,
String description,
String descUrl,
Os archiveOs,
Arch archiveArch,
String archiveOsPath) {
super(source, props, revision, license, description, descUrl,
archiveOs, archiveArch, archiveOsPath);
mMinToolsRevision = Integer.parseInt(getProperty(props, PROP_MIN_TOOLS_REV,
Integer.toString(MIN_TOOLS_REV_NOT_SPECIFIED)));
}
/**
* The minimal revision of the tools package required by this extra package, if > 0,
* or {@link #MIN_TOOLS_REV_NOT_SPECIFIED} if there is no such requirement.
*/
public int getMinToolsRevision() {
return mMinToolsRevision;
}
}

View File

@@ -96,8 +96,10 @@ public abstract class Package implements IDescription {
* Manually create a new package with one archive and the given attributes.
* This is used to create packages from local directories in which case there must be
* one archive which URL is the actual target location.
*
* <p/>
* Properties from props are used first when possible, e.g. if props is non null.
* <p/>
* By design, this creates a package with one and only one archive.
*/
public Package(
RepoSource source,

View File

@@ -33,10 +33,9 @@ import java.util.Properties;
/**
* Represents a platform XML node in an SDK repository.
*/
public class PlatformPackage extends Package {
public class PlatformPackage extends MinToolsPackage implements IPackageVersion {
private static final String PROP_VERSION = "Platform.Version"; //$NON-NLS-1$
private static final String PROP_MIN_TOOLS_REV = "Platform.MinToolsRev"; //$NON-NLS-1$
protected static final String PROP_VERSION = "Platform.Version"; //$NON-NLS-1$
/** The package version, for platform, add-on and doc packages. */
private final AndroidVersion mVersion;
@@ -44,18 +43,6 @@ public class PlatformPackage extends Package {
/** The version, a string, for platform packages. */
private final String mVersionName;
/**
* The minimal revision of the tools package required by this extra package, if > 0,
* or {@link #MIN_TOOLS_REV_NOT_SPECIFIED} if there is no such requirement.
*/
private final int mMinToolsRevision;
/**
* The value of {@link #mMinToolsRevision} when the {@link SdkRepository#NODE_MIN_TOOLS_REV}
* was not specified in the XML source.
*/
public static final int MIN_TOOLS_REV_NOT_SPECIFIED = 0;
/**
* Creates a new platform package from the attributes and elements of the given XML node.
* <p/>
@@ -63,6 +50,7 @@ public class PlatformPackage extends Package {
*/
PlatformPackage(RepoSource source, Node packageNode, Map<String,String> licenses) {
super(source, packageNode, licenses);
mVersionName = XmlParserUtils.getXmlString(packageNode, SdkRepository.NODE_VERSION);
int apiLevel = XmlParserUtils.getXmlInt (packageNode, SdkRepository.NODE_API_LEVEL, 0);
String codeName = XmlParserUtils.getXmlString(packageNode, SdkRepository.NODE_CODENAME);
@@ -70,9 +58,6 @@ public class PlatformPackage extends Package {
codeName = null;
}
mVersion = new AndroidVersion(apiLevel, codeName);
mMinToolsRevision = XmlParserUtils.getXmlInt(packageNode, SdkRepository.NODE_MIN_TOOLS_REV,
MIN_TOOLS_REV_NOT_SPECIFIED);
}
/**
@@ -80,6 +65,8 @@ public class PlatformPackage extends Package {
* must have {@link IAndroidTarget#isPlatform()} true) from the {@link SdkManager}.
* This is used to list local SDK folders in which case there is one archive which
* URL is the actual target location.
* <p/>
* By design, this creates a package with one and only one archive.
*/
PlatformPackage(IAndroidTarget target, Properties props) {
super( null, //source
@@ -95,9 +82,6 @@ public class PlatformPackage extends Package {
mVersion = target.getVersion();
mVersionName = target.getVersionName();
mMinToolsRevision = Integer.parseInt(getProperty(props, PROP_MIN_TOOLS_REV,
Integer.toString(MIN_TOOLS_REV_NOT_SPECIFIED)));
}
/**
@@ -114,8 +98,8 @@ public class PlatformPackage extends Package {
props.setProperty(PROP_VERSION, mVersionName);
}
if (mMinToolsRevision != MIN_TOOLS_REV_NOT_SPECIFIED) {
props.setProperty(PROP_MIN_TOOLS_REV, Integer.toString(mMinToolsRevision));
if (getMinToolsRevision() != MIN_TOOLS_REV_NOT_SPECIFIED) {
props.setProperty(PROP_MIN_TOOLS_REV, Integer.toString(getMinToolsRevision()));
}
}
@@ -129,38 +113,43 @@ public class PlatformPackage extends Package {
return mVersion;
}
/**
* The minimal revision of the tools package required by this extra package, if > 0,
* or {@link #MIN_TOOLS_REV_NOT_SPECIFIED} if there is no such requirement.
*/
public int getMinToolsRevision() {
return mMinToolsRevision;
}
/** Returns a short description for an {@link IDescription}. */
@Override
public String getShortDescription() {
String s;
if (mVersion.isPreview()) {
s = String.format("SDK Platform Android %1$s (Preview)", getVersionName());
s = String.format("SDK Platform Android %1$s Preview, revision %2$s",
getVersionName(),
getRevision());
} else {
s = String.format("SDK Platform Android %1$s, API %2$d",
s = String.format("SDK Platform Android %1$s, API %2$d, revision %3$s",
getVersionName(),
mVersion.getApiLevel());
}
if (mMinToolsRevision != MIN_TOOLS_REV_NOT_SPECIFIED) {
s += String.format(" (tools rev: %1$d)", mMinToolsRevision);
mVersion.getApiLevel(),
getRevision());
}
return s;
}
/** Returns a long description for an {@link IDescription}. */
/**
* Returns a long description for an {@link IDescription}.
*
* The long description is whatever the XML contains for the &lt;description&gt; field,
* or the short description if the former is empty.
*/
@Override
public String getLongDescription() {
return getShortDescription() + ".";
String s = getDescription();
if (s == null || s.length() == 0) {
s = getShortDescription();
}
if (s.indexOf("revision") == -1) {
s += String.format("\nRevision %1$d", getRevision());
}
return s;
}
/**

View File

@@ -185,23 +185,34 @@ public class RepoSource implements IDescription {
}
}
if (!validated) {
// If any exception was handled during the URL fetch, display it now.
if (exception[0] != null) {
mFetchError = "Failed to fetch URL";
String reason = "Unknown";
if (exception[0] != null) {
if (exception[0] instanceof FileNotFoundException) {
reason = "File not found";
} else if (exception[0] instanceof SSLKeyException) {
reason = "SSL error. You might want to force download through http in the settings.";
} else if (exception[0].getMessage() != null) {
reason = exception[0].getMessage();
}
String reason = null;
if (exception[0] instanceof FileNotFoundException) {
// FNF has no useful getMessage, so we need to special handle it.
reason = "File not found";
mFetchError += ": " + reason;
} else if (exception[0] instanceof SSLKeyException) {
// That's a common error and we have a pref for it.
reason = "HTTPS SSL error. You might want to force download through HTTP in the settings.";
mFetchError += ": HTTPS SSL error";
} else if (exception[0].getMessage() != null) {
reason = exception[0].getMessage();
} else {
// We don't know what's wrong. Let's give the exception class at least.
reason = String.format("Unknown (%1$s)", exception[0].getClass().getName());
}
monitor.setResult("Failed to fetch URL %1$s, reason: %2$s", url, reason);
}
// Stop here if we failed to validate the XML. We don't want to load it.
if (!validated) {
return;
}
monitor.incProgress(1);
if (xml != null) {
@@ -274,7 +285,7 @@ public class RepoSource implements IDescription {
}
}
} catch (IOException e) {
} catch (Exception e) {
outException[0] = e;
}
@@ -289,14 +300,26 @@ public class RepoSource implements IDescription {
try {
Validator validator = getValidator();
if (validator == null) {
monitor.setResult(
"XML verification failed for %1$s.\nNo suitable XML Schema Validator could be found in your Java environment. Please consider updating your version of Java.",
url);
return false;
}
xml.reset();
validator.validate(new StreamSource(xml));
return true;
} catch (Exception e) {
String s = e.getMessage();
if (s == null) {
s = e.getClass().getName();
}
monitor.setResult("XML verification failed for %1$s.\nError: %2$s",
url,
e.getMessage());
s);
}
return false;
@@ -309,10 +332,14 @@ public class RepoSource implements IDescription {
InputStream xsdStream = SdkRepository.getXsdStream();
SchemaFactory factory = SchemaFactory.newInstance(XMLConstants.W3C_XML_SCHEMA_NS_URI);
if (factory == null) {
return null;
}
// This may throw a SAX Exception if the schema itself is not a valid XSD
Schema schema = factory.newSchema(new StreamSource(xsdStream));
Validator validator = schema.newValidator();
Validator validator = schema == null ? null : schema.newValidator();
return validator;
}

View File

@@ -45,6 +45,8 @@ public class ToolPackage extends Package {
* Manually create a new package with one archive and the given attributes or properties.
* This is used to create packages from local directories in which case there must be
* one archive which URL is the actual target location.
* <p/>
* By design, this creates a package with one and only one archive.
*/
ToolPackage(
RepoSource source,
@@ -76,7 +78,16 @@ public class ToolPackage extends Package {
/** Returns a long description for an {@link IDescription}. */
@Override
public String getLongDescription() {
return getShortDescription() + ".";
String s = getDescription();
if (s == null || s.length() == 0) {
s = getShortDescription();
}
if (s.indexOf("revision") == -1) {
s += String.format("\nRevision %1$d", getRevision());
}
return s;
}
/**

View File

@@ -17,10 +17,10 @@
package com.android.sdklib.xml;
/**
* Constants for nodes and attributes of the AndroidManifest.xml file.
* Helper and Constants for the AndroidManifest.xml file.
*
*/
public final class ManifestConstants {
public final class AndroidManifest {
public final static String NODE_MANIFEST = "manifest"; //$NON-NLS-1$
public final static String NODE_APPLICATION = "application"; //$NON-NLS-1$
@@ -42,4 +42,39 @@ public final class ManifestConstants {
public final static String ATTRIBUTE_MIN_SDK_VERSION = "minSdkVersion"; //$NON-NLS-$
public final static String ATTRIBUTE_TARGET_PACKAGE = "targetPackage"; //$NON-NLS-1$
public final static String ATTRIBUTE_EXPORTED = "exported"; //$NON-NLS-1$
/**
* Combines a java package, with a class value from the manifest to make a fully qualified
* class name
* @param javaPackage the java package from the manifest.
* @param className the class name from the manifest.
* @return the fully qualified class name.
*/
public static String combinePackageAndClassName(String javaPackage, String className) {
if (className == null || className.length() == 0) {
return javaPackage;
}
if (javaPackage == null || javaPackage.length() == 0) {
return className;
}
// the class name can be a subpackage (starts with a '.'
// char), a simple class name (no dot), or a full java package
boolean startWithDot = (className.charAt(0) == '.');
boolean hasDot = (className.indexOf('.') != -1);
if (startWithDot || hasDot == false) {
// add the concatenation of the package and class name
if (startWithDot) {
return javaPackage + className;
} else {
return javaPackage + '.' + className;
}
} else {
// just add the class as it should be a fully qualified java name.
return className;
}
}
}

View File

@@ -18,7 +18,9 @@ package com.android.sdklib.xml;
import com.android.sdklib.SdkConstants;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import javax.xml.XMLConstants;
import javax.xml.namespace.NamespaceContext;
@@ -39,7 +41,8 @@ public class AndroidXPathFactory {
private final static AndroidNamespaceContext sThis = new AndroidNamespaceContext(
DEFAULT_NS_PREFIX);
private String mAndroidPrefix;
private final String mAndroidPrefix;
private final List<String> mAndroidPrefixes = new ArrayList<String>();
/**
* Returns the default {@link AndroidNamespaceContext}.
@@ -54,6 +57,7 @@ public class AndroidXPathFactory {
*/
public AndroidNamespaceContext(String androidPrefix) {
mAndroidPrefix = androidPrefix;
mAndroidPrefixes.add(mAndroidPrefix);
}
public String getNamespaceURI(String prefix) {
@@ -67,14 +71,18 @@ public class AndroidXPathFactory {
}
public String getPrefix(String namespaceURI) {
// This isn't necessary for our use.
assert false;
if (SdkConstants.NS_RESOURCES.equals(namespaceURI)) {
return mAndroidPrefix;
}
return null;
}
public Iterator<?> getPrefixes(String namespaceURI) {
// This isn't necessary for our use.
assert false;
if (SdkConstants.NS_RESOURCES.equals(namespaceURI)) {
return mAndroidPrefixes.iterator();
}
return null;
}
}

View File

@@ -0,0 +1,136 @@
/*
* Copyright (C) 2009 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.sdklib.internal.repository;
import com.android.sdklib.AndroidVersion;
import com.android.sdklib.IAndroidTarget;
/**
* A mock {@link AddonPackage} for testing.
*
* By design, this package contains one and only one archive.
*/
public class MockAddonPackage extends AddonPackage {
/**
* Creates a {@link MockAddonTarget} with the requested base platform and addon revision
* and then a {@link MockAddonPackage} wrapping it.
*
* By design, this package contains one and only one archive.
*/
public MockAddonPackage(MockPlatformPackage basePlatform, int revision) {
super(new MockAddonTarget(basePlatform.getTarget(), revision), null /*props*/);
}
/**
* A mock AddonTarget.
* This reimplements the minimum needed from the interface for our limited testing needs.
*/
static class MockAddonTarget implements IAndroidTarget {
private final IAndroidTarget mParentTarget;
private final int mRevision;
public MockAddonTarget(IAndroidTarget parentTarget, int revision) {
mParentTarget = parentTarget;
mRevision = revision;
}
public String getClasspathName() {
return null;
}
public String getDefaultSkin() {
return null;
}
public String getDescription() {
return "mock addon target";
}
public String getFullName() {
return "mock addon target";
}
public String getLocation() {
return "";
}
public String getName() {
return "mock addon target";
}
public IOptionalLibrary[] getOptionalLibraries() {
return null;
}
public IAndroidTarget getParent() {
return mParentTarget;
}
public String getPath(int pathId) {
return null;
}
public String[] getPlatformLibraries() {
return null;
}
public int getRevision() {
return mRevision;
}
public String[] getSkins() {
return null;
}
public int getUsbVendorId() {
return 0;
}
public String getVendor() {
return null;
}
public AndroidVersion getVersion() {
return mParentTarget.getVersion();
}
public String getVersionName() {
return String.format("mock-addon-%1$d", getVersion().getApiLevel());
}
public String hashString() {
return getVersionName();
}
/** Returns false for an addon. */
public boolean isPlatform() {
return false;
}
public boolean isCompatibleBaseFor(IAndroidTarget target) {
throw new UnsupportedOperationException("Implement this as needed for tests");
}
public int compareTo(IAndroidTarget o) {
throw new UnsupportedOperationException("Implement this as needed for tests");
}
}
}

View File

@@ -0,0 +1,169 @@
/*
* Copyright (C) 2009 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.sdklib.internal.repository;
import com.android.sdklib.AndroidVersion;
import com.android.sdklib.IAndroidTarget;
import java.util.Properties;
/**
* A mock {@link PlatformPackage} for testing.
*
* By design, this package contains one and only one archive.
*/
public class MockPlatformPackage extends PlatformPackage {
private final IAndroidTarget mTarget;
/**
* Creates a {@link MockPlatformTarget} with the requested API and revision
* and then a {@link MockPlatformPackage} wrapping it.
*
* By design, this package contains one and only one archive.
*/
public MockPlatformPackage(int apiLevel, int revision) {
this(new MockPlatformTarget(apiLevel, revision), null /*props*/);
}
/**
* Creates a {@link MockPlatformTarget} with the requested API and revision
* and then a {@link MockPlatformPackage} wrapping it.
*
* Also sets the min-tools-rev of the platform.
*
* By design, this package contains one and only one archive.
*/
public MockPlatformPackage(int apiLevel, int revision, int min_tools_rev) {
this(new MockPlatformTarget(apiLevel, revision), createProps(min_tools_rev));
}
/** A little trick to be able to capture the target new after passing it to the super. */
private MockPlatformPackage(IAndroidTarget target, Properties props) {
super(target, props);
mTarget = target;
}
private static Properties createProps(int min_tools_rev) {
Properties props = new Properties();
props.setProperty(PlatformPackage.PROP_MIN_TOOLS_REV, Integer.toString((min_tools_rev)));
return props;
}
public IAndroidTarget getTarget() {
return mTarget;
}
/**
* A mock PlatformTarget.
* This reimplements the minimum needed from the interface for our limited testing needs.
*/
static class MockPlatformTarget implements IAndroidTarget {
private final int mApiLevel;
private final int mRevision;
public MockPlatformTarget(int apiLevel, int revision) {
mApiLevel = apiLevel;
mRevision = revision;
}
public String getClasspathName() {
return null;
}
public String getDefaultSkin() {
return null;
}
public String getDescription() {
return "mock platform target";
}
public String getFullName() {
return "mock platform target";
}
public String getLocation() {
return "";
}
public String getName() {
return "mock platform target";
}
public IOptionalLibrary[] getOptionalLibraries() {
return null;
}
public IAndroidTarget getParent() {
return null;
}
public String getPath(int pathId) {
return null;
}
public String[] getPlatformLibraries() {
return null;
}
public int getRevision() {
return mRevision;
}
public String[] getSkins() {
return null;
}
public int getUsbVendorId() {
return 0;
}
public String getVendor() {
return null;
}
public AndroidVersion getVersion() {
return new AndroidVersion(mApiLevel, null /*codename*/);
}
public String getVersionName() {
return String.format("android-%1$d", mApiLevel);
}
public String hashString() {
return getVersionName();
}
/** Returns true for a platform. */
public boolean isPlatform() {
return true;
}
public boolean isCompatibleBaseFor(IAndroidTarget target) {
throw new UnsupportedOperationException("Implement this as needed for tests");
}
public int compareTo(IAndroidTarget o) {
throw new UnsupportedOperationException("Implement this as needed for tests");
}
}
}

View File

@@ -0,0 +1,48 @@
/*
* Copyright (C) 2009 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.sdklib.internal.repository;
import com.android.sdklib.internal.repository.Archive.Arch;
import com.android.sdklib.internal.repository.Archive.Os;
/**
* A mock {@link ToolPackage} for testing.
*
* By design, this package contains one and only one archive.
*/
public class MockToolPackage extends ToolPackage {
/**
* Creates a {@link MockToolPackage} with the given revision and hardcoded defaults
* for everything else.
* <p/>
* By design, this creates a package with one and only one archive.
*/
public MockToolPackage(int revision) {
super(
null, // source,
null, // props,
revision,
null, // license,
"desc", // description,
"url", // descUrl,
Os.getCurrentOs(), // archiveOs,
Arch.getCurrentArch(), // archiveArch,
"foo" // archiveOsPath
);
}
}

View File

@@ -123,9 +123,9 @@ public class TestSdkRepository extends TestCase {
/** Validate a valid sample using an InputStream */
public void testValidateLocalRepositoryFile() throws Exception {
InputStream xmlStream =
TestSdkRepository.class.getResourceAsStream("repository_sample.xml");
this.getClass().getClassLoader().getResourceAsStream(
"com/android/sdklib/testdata/repository_sample.xml");
Source source = new StreamSource(xmlStream);
CaptureErrorHandler handler = new CaptureErrorHandler();

View File

@@ -1,9 +1,11 @@
<?xml version="1.0" encoding="UTF-8"?>
<classpath>
<classpathentry kind="src" path="src"/>
<classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER"/>
<classpathentry exported="true" kind="con" path="org.eclipse.jdt.USER_LIBRARY/ANDROID_SWT"/>
<classpathentry combineaccessrules="false" kind="src" path="/SdkLib"/>
<classpathentry combineaccessrules="false" kind="src" path="/AndroidPrefs"/>
<classpathentry kind="output" path="bin"/>
</classpath>
<?xml version="1.0" encoding="UTF-8"?>
<classpath>
<classpathentry kind="src" path="src"/>
<classpathentry kind="src" path="tests"/>
<classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER"/>
<classpathentry exported="true" kind="con" path="org.eclipse.jdt.USER_LIBRARY/ANDROID_SWT"/>
<classpathentry combineaccessrules="false" kind="src" path="/SdkLib"/>
<classpathentry combineaccessrules="false" kind="src" path="/AndroidPrefs"/>
<classpathentry kind="con" path="org.eclipse.jdt.junit.JUNIT_CONTAINER/3"/>
<classpathentry kind="output" path="bin"/>
</classpath>

View File

@@ -0,0 +1,141 @@
/*
* Copyright (C) 2009 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.sdkuilib.internal.repository;
import com.android.sdklib.internal.repository.Archive;
import java.util.ArrayList;
import java.util.Collection;
/**
* Represents an archive that we want to install.
* Note that the installer deals with archives whereas the user mostly sees packages
* but as far as we are concerned for installation there's a 1-to-1 mapping.
* <p/>
* A new archive is always a remote archive that needs to be downloaded and then
* installed. It can replace an existing local one. It can also depends on another
* (new or local) archive, which means the dependent archive needs to be successfully
* installed first. Finally this archive can also be a dependency for another one.
*
* @see ArchiveInfo#ArchiveInfo(Archive, Archive, ArchiveInfo)
*/
class ArchiveInfo {
private final Archive mNewArchive;
private final Archive mReplaced;
private final ArchiveInfo mDependsOn;
private final ArrayList<ArchiveInfo> mDependencyFor = new ArrayList<ArchiveInfo>();
private boolean mAccepted;
private boolean mRejected;
/**
*
* @param newArchive A "new archive" to be installed. This is always an archive
* that comes from a remote site. This can not be null.
* @param replaced An optional local archive that the new one will replace.
* Can be null if this archive does not replace anything.
* @param dependsOn An optional new or local dependency, that is an archive that
* <em>this</em> archive depends upon. In other words, we can only install
* this archive if the dependency has been successfully installed. It also
* means we need to install the dependency first.
*/
public ArchiveInfo(Archive newArchive, Archive replaced, ArchiveInfo dependsOn) {
mNewArchive = newArchive;
mReplaced = replaced;
mDependsOn = dependsOn;
}
/**
* Returns the "new archive" to be installed.
* This is always an archive that comes from a remote site.
*/
public Archive getNewArchive() {
return mNewArchive;
}
/**
* Returns an optional local archive that the new one will replace.
* Can be null if this archive does not replace anything.
*/
public Archive getReplaced() {
return mReplaced;
}
/**
* Returns an optional new or local dependency, that is an archive that <em>this</em>
* archive depends upon. In other words, we can only install this archive if the
* dependency has been successfully installed. It also means we need to install the
* dependency first.
*/
public ArchiveInfo getDependsOn() {
return mDependsOn;
}
/**
* Returns true if this new archive is a dependency for <em>another</em> one that we
* want to install.
*/
public boolean isDependencyFor() {
return mDependencyFor.size() > 0;
}
/**
* Adds an {@link ArchiveInfo} for which <em>this</em> package is a dependency.
* This means the package added here depends on this package.
*/
public void addDependencyFor(ArchiveInfo dependencyFor) {
if (!mDependencyFor.contains(dependencyFor)) {
mDependencyFor.add(dependencyFor);
}
}
public Collection<ArchiveInfo> getDependenciesFor() {
return mDependencyFor;
}
/**
* Sets whether this archive was accepted (either manually by the user or
* automatically if it doesn't have a license) for installation.
*/
public void setAccepted(boolean accepted) {
mAccepted = accepted;
}
/**
* Returns whether this archive was accepted (either manually by the user or
* automatically if it doesn't have a license) for installation.
*/
public boolean isAccepted() {
return mAccepted;
}
/**
* Sets whether this archive was rejected manually by the user.
* An archive can neither accepted nor rejected.
*/
public void setRejected(boolean rejected) {
mRejected = rejected;
}
/**
* Returns whether this archive was rejected manually by the user.
* An archive can neither accepted nor rejected.
*/
public boolean isRejected() {
return mRejected;
}
}

View File

@@ -56,6 +56,7 @@ public class AvdManagerPage extends Composite implements ISdkListener {
mUpdaterData.getOsSdkRoot(),
mUpdaterData.getAvdManager(),
DisplayMode.MANAGER);
mAvdSelector.setSettingsController(mUpdaterData.getSettingsController());
}
@Override

View File

@@ -52,6 +52,12 @@ public interface ISettingsPage {
* Default: True.
*/
public static final String KEY_ASK_ADB_RESTART = "sdkman.ask.adb.restart"; //$NON-NLS-1$
/**
* Setting to set the density of the monitor.
* Type: Integer.
* Default: -1
*/
public static final String KEY_MONITOR_DENSITY = "sdkman.monitor.density"; //$NON-NLS-1$
/** Loads settings from the given {@link Properties} container and update the page UI. */
public abstract void loadSettings(Properties in_settings);

View File

@@ -124,7 +124,7 @@ public class LocalPackagesPage extends Composite implements ISdkListener {
mUpdateButton.addSelectionListener(new SelectionAdapter() {
@Override
public void widgetSelected(SelectionEvent e) {
onUpdateInstalledPackage(); //$hide$ (hide from SWT designer)
onUpdateSelected(); //$hide$ (hide from SWT designer)
}
});
@@ -255,7 +255,7 @@ public class LocalPackagesPage extends Composite implements ISdkListener {
mDescriptionLabel.setText(""); //$NON-NLS1-$
}
private void onUpdateInstalledPackage() {
private void onUpdateSelected() {
mUpdaterData.updateOrInstallAll(null /*selectedArchives*/);
}

View File

@@ -103,7 +103,7 @@ public class RemotePackagesPage extends Composite implements ISdkListener {
mColumnSource = new TreeColumn(mTreeSources, SWT.NONE);
mColumnSource.setWidth(289);
mColumnSource.setText("Sources, Packages and Archives");
mColumnSource.setText("Sites, Packages and Archives");
mDescriptionContainer = new Group(parent, SWT.NONE);
mDescriptionContainer.setLayout(new GridLayout(1, false));
@@ -272,7 +272,6 @@ public class RemotePackagesPage extends Composite implements ISdkListener {
}
private void selectCompatibleArchives(Object pkg, ITreeContentProvider provider) {
mTreeViewerSources.setExpandedState(pkg, true);
for (Object archive : provider.getChildren(pkg)) {
if (archive instanceof Archive) {
mTreeViewerSources.setChecked(archive, ((Archive) archive).isCompatible());
@@ -284,7 +283,32 @@ public class RemotePackagesPage extends Composite implements ISdkListener {
SettingsController controller = mUpdaterData.getSettingsController();
controller.setShowUpdateOnly(mUpdateOnlyCheckBox.getSelection());
controller.saveSettings();
// Get the list of selected archives
ArrayList<Archive> archives = new ArrayList<Archive>();
for (Object element : mTreeViewerSources.getCheckedElements()) {
if (element instanceof Archive) {
archives.add((Archive) element);
}
// Deselect them all
mTreeViewerSources.setChecked(element, false);
}
mTreeViewerSources.refresh();
// Now reselect those that still exist in the tree but only if they
// are compatible archives
for (Archive a : archives) {
if (a.isCompatible() && mTreeViewerSources.setChecked(a, true)) {
// If we managed to select the archive, also select the parent package.
// Technically we should only select the parent package if *all* the
// compatible archives children are selected. In practice we'll rarely
// have more than one compatible archive per package.
mTreeViewerSources.setChecked(a.getParentPackage(), true);
}
}
updateButtonsState();
}
private void onInstallSelectedArchives() {

View File

@@ -208,7 +208,7 @@ public class RepoSourcesAdapter {
packages = null;
}
if (packages != null && source.getFetchError() != null) {
if (packages == null && source.getFetchError() != null) {
// Return a dummy entry to display the fetch error
return new Object[] { new RepoSourceError(source) };
}
@@ -252,6 +252,9 @@ public class RepoSourcesAdapter {
} else if (element instanceof Package) {
return ((Package) element).getParentSource();
} else if (element instanceof Archive) {
return ((Archive) element).getParentPackage();
}
return null;
}

View File

@@ -18,9 +18,13 @@ package com.android.sdkuilib.internal.repository;
import com.android.prefs.AndroidLocation;
import com.android.prefs.AndroidLocation.AndroidLocationException;
import com.android.sdklib.ISdkLog;
import org.eclipse.jface.dialogs.MessageDialog;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.util.Properties;
@@ -40,13 +44,16 @@ public class SettingsController {
private ISettingsPage mSettingsPage;
public SettingsController() {
private final UpdaterData mUpdaterData;
public SettingsController(UpdaterData updaterData) {
mUpdaterData = updaterData;
}
//--- Access to settings ------------
/**
* Returns the value of the ISettingsPage#KEY_FORCE_HTTP setting.
* Returns the value of the {@link ISettingsPage#KEY_FORCE_HTTP} setting.
* @see ISettingsPage#KEY_FORCE_HTTP
*/
public boolean getForceHttp() {
@@ -54,7 +61,7 @@ public class SettingsController {
}
/**
* Returns the value of the ISettingsPage#KEY_ASK_ADB_RESTART setting.
* Returns the value of the {@link ISettingsPage#KEY_ASK_ADB_RESTART} setting.
* @see ISettingsPage#KEY_ASK_ADB_RESTART
*/
public boolean getAskBeforeAdbRestart() {
@@ -66,7 +73,7 @@ public class SettingsController {
}
/**
* Returns the value of the ISettingsPage#KEY_SHOW_UPDATE_ONLY setting.
* Returns the value of the {@link ISettingsPage#KEY_SHOW_UPDATE_ONLY} setting.
* @see ISettingsPage#KEY_SHOW_UPDATE_ONLY
*/
public boolean getShowUpdateOnly() {
@@ -78,7 +85,7 @@ public class SettingsController {
}
/**
* Sets the value of the ISettingsPage#KEY_SHOW_UPDATE_ONLY setting.
* Sets the value of the {@link ISettingsPage#KEY_SHOW_UPDATE_ONLY} setting.
* @param enabled True if only compatible update items should be shown.
* @see ISettingsPage#KEY_SHOW_UPDATE_ONLY
*/
@@ -86,6 +93,32 @@ public class SettingsController {
setSetting(ISettingsPage.KEY_SHOW_UPDATE_ONLY, enabled);
}
/**
* Returns the value of the {@link ISettingsPage#KEY_MONITOR_DENSITY} setting
* @see ISettingsPage#KEY_MONITOR_DENSITY
*/
public int getMonitorDensity() {
String value = mProperties.getProperty(ISettingsPage.KEY_MONITOR_DENSITY, null);
if (value == null) {
return -1;
}
try {
return Integer.parseInt(value);
} catch (NumberFormatException e) {
return -1;
}
}
/**
* Sets the value of the {@link ISettingsPage#KEY_MONITOR_DENSITY} setting.
* @param density the density of the monitor
* @see ISettingsPage#KEY_MONITOR_DENSITY
*/
public void setMonitorDensity(int density) {
mProperties.setProperty(ISettingsPage.KEY_MONITOR_DENSITY, Integer.toString(density));
}
/**
* Internal helper to set a boolean setting.
*/
@@ -118,9 +151,11 @@ public class SettingsController {
*/
public void loadSettings() {
FileInputStream fis = null;
String path = null;
try {
String folder = AndroidLocation.getFolder();
File f = new File(folder, SETTINGS_FILENAME);
path = f.getPath();
if (f.exists()) {
fis = new FileInputStream(f);
@@ -131,10 +166,11 @@ public class SettingsController {
setSetting(ISettingsPage.KEY_ASK_ADB_RESTART, getAskBeforeAdbRestart());
}
} catch (AndroidLocationException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
} catch (Exception e) {
ISdkLog log = mUpdaterData.getSdkLog();
if (log != null) {
log.error(e, "Failed to load settings from '%1$s'", path);
}
} finally {
if (fis != null) {
try {
@@ -151,18 +187,42 @@ public class SettingsController {
public void saveSettings() {
FileOutputStream fos = null;
String path = null;
try {
String folder = AndroidLocation.getFolder();
File f = new File(folder, SETTINGS_FILENAME);
path = f.getPath();
fos = new FileOutputStream(f);
mProperties.store( fos, "## Settings for Android Tool"); //$NON-NLS-1$
} catch (AndroidLocationException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
} catch (Exception e) {
ISdkLog log = mUpdaterData.getSdkLog();
if (log != null) {
log.error(e, "Failed to save settings at '%1$s'", path);
}
// This is important enough that we want to really nag the user about it
String reason = null;
if (e instanceof FileNotFoundException) {
reason = "File not found";
} else if (e instanceof AndroidLocationException) {
reason = ".android folder not found, please define ANDROID_SDK_HOME";
} else if (e.getMessage() != null) {
reason = String.format("%1$s: %2$s", e.getClass().getSimpleName(), e.getMessage());
} else {
reason = e.getClass().getName();
}
MessageDialog.openInformation(mUpdaterData.getWindowShell(),
"SDK Manager Settings",
String.format(
"The Android SDK and AVD Manager failed to save its settings (%1$s) at %2$s",
reason, path));
} finally {
if (fos != null) {
try {
@@ -176,7 +236,7 @@ public class SettingsController {
/**
* When settings have changed: retrieve the new settings, apply them and save them.
*
* This updats Java system properties for the HTTP proxy.
* This updates Java system properties for the HTTP proxy.
*/
private void onSettingsChanged() {
if (mSettingsPage == null) {
@@ -186,6 +246,10 @@ public class SettingsController {
mSettingsPage.retrieveSettings(mProperties);
applySettings();
saveSettings();
// In case the HTTP/HTTPS setting change, force sources to be reloaded
// (this only refreshes sources that the user has already tried to open.)
mUpdaterData.refreshSources(false /*forceFetching*/);
}
/**

View File

@@ -16,16 +16,22 @@
package com.android.sdkuilib.internal.repository;
import com.android.sdklib.AndroidVersion;
import com.android.sdklib.SdkConstants;
import com.android.sdklib.internal.repository.Archive;
import com.android.sdklib.internal.repository.IPackageVersion;
import com.android.sdklib.internal.repository.Package;
import com.android.sdkuilib.internal.repository.icons.ImageFactory;
import com.android.sdkuilib.ui.GridDialog;
import org.eclipse.jface.dialogs.IDialogConstants;
import org.eclipse.jface.viewers.ISelection;
import org.eclipse.jface.viewers.IStructuredContentProvider;
import org.eclipse.jface.viewers.IStructuredSelection;
import org.eclipse.jface.viewers.LabelProvider;
import org.eclipse.jface.viewers.TableViewer;
import org.eclipse.jface.viewers.Viewer;
import org.eclipse.jface.window.Window;
import org.eclipse.swt.SWT;
import org.eclipse.swt.custom.SashForm;
import org.eclipse.swt.custom.StyleRange;
@@ -34,8 +40,6 @@ import org.eclipse.swt.events.ControlAdapter;
import org.eclipse.swt.events.ControlEvent;
import org.eclipse.swt.events.SelectionAdapter;
import org.eclipse.swt.events.SelectionEvent;
import org.eclipse.swt.events.ShellAdapter;
import org.eclipse.swt.events.ShellEvent;
import org.eclipse.swt.graphics.Image;
import org.eclipse.swt.graphics.Point;
import org.eclipse.swt.graphics.Rectangle;
@@ -43,8 +47,7 @@ import org.eclipse.swt.layout.GridData;
import org.eclipse.swt.layout.GridLayout;
import org.eclipse.swt.widgets.Button;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Dialog;
import org.eclipse.swt.widgets.Display;
import org.eclipse.swt.widgets.Control;
import org.eclipse.swt.widgets.Group;
import org.eclipse.swt.widgets.Label;
import org.eclipse.swt.widgets.Shell;
@@ -52,40 +55,21 @@ import org.eclipse.swt.widgets.Table;
import org.eclipse.swt.widgets.TableColumn;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Comparator;
import java.util.HashSet;
import java.util.Map;
import java.util.TreeMap;
/**
* Implements an {@link UpdateChooserDialog}.
*/
final class UpdateChooserDialog extends Dialog {
/**
* Min Y location for dialog. Need to deal with the menu bar on mac os.
*/
private final static int MIN_Y = SdkConstants.CURRENT_PLATFORM == SdkConstants.PLATFORM_DARWIN ?
20 : 0;
final class UpdateChooserDialog extends GridDialog {
/** Last dialog size for this session. */
private static Point sLastSize;
private boolean mCompleted;
private final Map<Archive, Archive> mNewToOldArchiveMap;
private boolean mLicenseAcceptAll;
private boolean mInternalLicenseRadioUpdate;
private HashSet<Archive> mAccepted = new HashSet<Archive>();
private HashSet<Archive> mRejected = new HashSet<Archive>();
private ArrayList<Archive> mResult = new ArrayList<Archive>();
// UI fields
private Shell mDialogShell;
private SashForm mSashForm;
private Composite mPackageRootComposite;
private Button mCancelButton;
private Button mInstallButton;
private TableViewer mTableViewPackage;
private Table mTablePackage;
private TableColumn mTableColum;
@@ -96,82 +80,70 @@ final class UpdateChooserDialog extends Dialog {
private Group mPackageTextGroup;
private final UpdaterData mUpdaterData;
private Group mTableGroup;
private Label mErrorLabel;
/**
* List of all archives to be installed with dependency information.
*
* Note: in a lot of cases, we need to find the archive info for a given archive. This
* is currently done using a simple linear search, which is fine since we only have a very
* limited number of archives to deal with (e.g. < 10 now). We might want to revisit
* this later if it becomes an issue. Right now just do the simple thing.
*
* Typically we could add a map Archive=>ArchiveInfo later.
*/
private final ArrayList<ArchiveInfo> mArchives;
/**
* Create the dialog.
* @param parentShell The shell to use, typically updaterData.getWindowShell()
* @param updaterData The updater data
* @param newToOldUpdates The map [new archive => old archive] of potential updates
* @param archives The archives to be installed
*/
public UpdateChooserDialog(Shell parentShell,
UpdaterData updaterData,
Map<Archive, Archive> newToOldUpdates) {
super(parentShell,
SWT.APPLICATION_MODAL);
ArrayList<ArchiveInfo> archives) {
super(parentShell, 3, false/*makeColumnsEqual*/);
mUpdaterData = updaterData;
mArchives = archives;
}
mNewToOldArchiveMap = new TreeMap<Archive, Archive>(new Comparator<Archive>() {
public int compare(Archive a1, Archive a2) {
// The items are archive but what we show are packages so we'll
// sort of packages short descriptions
String desc1 = a1.getParentPackage().getShortDescription();
String desc2 = a2.getParentPackage().getShortDescription();
return desc1.compareTo(desc2);
}
});
mNewToOldArchiveMap.putAll(newToOldUpdates);
@Override
protected boolean isResizable() {
return true;
}
/**
* Returns the results, i.e. the list of selected new archives to install.
* The list is always non null. It is empty when cancel is selected or when
* all potential updates have been refused.
* This is similar to the {@link ArchiveInfo} list instance given to the constructor
* except only accepted archives are present.
*
* An empty list is returned if cancel was choosen.
*/
public Collection<Archive> getResult() {
return mResult;
}
public ArrayList<ArchiveInfo> getResult() {
ArrayList<ArchiveInfo> ais = new ArrayList<ArchiveInfo>();
/**
* Open the dialog and blocks till it gets closed
*/
public void open() {
createContents();
positionShell(); //$hide$ (hide from SWT designer)
mDialogShell.open();
mDialogShell.layout();
postCreate(); //$hide$ (hide from SWT designer)
Display display = getParent().getDisplay();
while (!mDialogShell.isDisposed() && !mCompleted) {
if (!display.readAndDispatch()) {
display.sleep();
if (getReturnCode() == Window.OK) {
for (ArchiveInfo ai : mArchives) {
if (ai.isAccepted()) {
ais.add(ai);
}
}
}
if (!mDialogShell.isDisposed()) {
mDialogShell.close();
}
return ais;
}
/**
* Create contents of the dialog.
* Create the main content of the dialog.
* See also {@link #createButtonBar(Composite)} below.
*/
private void createContents() {
mDialogShell = new Shell(getParent(), SWT.DIALOG_TRIM | SWT.RESIZE | SWT.MIN | SWT.MAX);
mDialogShell.addShellListener(new ShellAdapter() {
@Override
public void shellClosed(ShellEvent e) {
onShellClosed(e);
}
});
mDialogShell.setLayout(new GridLayout(3, false/*makeColumnsEqual*/));
mDialogShell.setSize(600, 400);
mDialogShell.setText("Choose Packages to Install");
@Override
public void createDialogContent(Composite parent) {
// Sash form
mSashForm = new SashForm(mDialogShell, SWT.NONE);
mSashForm = new SashForm(parent, SWT.NONE);
mSashForm.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true, 3, 1));
@@ -214,7 +186,7 @@ final class UpdateChooserDialog extends Dialog {
mPackageText = new StyledText(mPackageTextGroup,
SWT.MULTI | SWT.READ_ONLY | SWT.WRAP | SWT.V_SCROLL);
mPackageText.setBackground(
mPackageText.setBackground(
getParentShell().getDisplay().getSystemColor(SWT.COLOR_WIDGET_BACKGROUND));
mPackageText.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true, 1, 1));
@@ -248,69 +220,82 @@ final class UpdateChooserDialog extends Dialog {
}
});
mSashForm.setWeights(new int[] {200, 300});
}
// Bottom buttons
placeholder = new Label(mDialogShell, SWT.NONE);
/**
* Creates and returns the contents of this dialog's button bar.
* <p/>
* This reimplements most of the code from the base class with a few exceptions:
* <ul>
* <li>Enforces 3 columns.
* <li>Inserts a full-width error label.
* <li>Inserts a help label on the left of the first button.
* <li>Renames the OK button into "Install"
* </ul>
*/
@Override
protected Control createButtonBar(Composite parent) {
// for MacOS, the Cancel button should be left.
if (SdkConstants.currentPlatform() == SdkConstants.PLATFORM_DARWIN) {
mCancelButton = new Button(mDialogShell, SWT.PUSH);
Composite composite = new Composite(parent, SWT.NONE);
GridLayout layout = new GridLayout();
layout.numColumns = 0; // this is incremented by createButton
layout.makeColumnsEqualWidth = false;
layout.marginWidth = convertHorizontalDLUsToPixels(IDialogConstants.HORIZONTAL_MARGIN);
layout.marginHeight = convertVerticalDLUsToPixels(IDialogConstants.VERTICAL_MARGIN);
layout.horizontalSpacing = convertHorizontalDLUsToPixels(IDialogConstants.HORIZONTAL_SPACING);
layout.verticalSpacing = convertVerticalDLUsToPixels(IDialogConstants.VERTICAL_SPACING);
composite.setLayout(layout);
GridData data = new GridData(SWT.FILL, SWT.CENTER, true, false, 3, 1);
composite.setLayoutData(data);
composite.setFont(parent.getFont());
mInstallButton = new Button(mDialogShell, SWT.PUSH);
mInstallButton.setLayoutData(new GridData(SWT.RIGHT, SWT.CENTER, false, false, 1, 1));
mInstallButton.setText("Install Accepted");
mInstallButton.addSelectionListener(new SelectionAdapter() {
@Override
public void widgetSelected(SelectionEvent e) {
onInstallSelected();
}
// Error message area
mErrorLabel = new Label(composite, SWT.NONE);
mErrorLabel.setLayoutData(new GridData(SWT.FILL, SWT.CENTER, true, false, 3, 1));
// if we haven't created the cancel button yet (macos?), create it now.
if (mCancelButton == null) {
mCancelButton = new Button(mDialogShell, SWT.PUSH);
}
mCancelButton.setLayoutData(new GridData(SWT.RIGHT, SWT.CENTER, false, false, 1, 1));
mCancelButton.setText("Cancel");
mCancelButton.addSelectionListener(new SelectionAdapter() {
@Override
public void widgetSelected(SelectionEvent e) {
onCancelSelected();
}
// Label at the left of the install/cancel buttons
Label label = new Label(composite, SWT.NONE);
label.setLayoutData(new GridData(SWT.FILL, SWT.CENTER, true, false, 1, 1));
label.setText("[*] Something depends on this package");
label.setEnabled(false);
layout.numColumns++;
// Add the ok/cancel to the button bar.
createButtonsForButtonBar(composite);
// the ok button should be an "install" button
Button button = getButton(IDialogConstants.OK_ID);
button.setText("Install");
return composite;
}
// -- End of UI, Start of internal logic ----------
// Hide everything down-below from SWT designer
//$hide>>$
/**
* Starts the thread that runs the task.
* This is deferred till the UI is created.
*/
@Override
public void create() {
super.create();
// set window title
getShell().setText("Choose Packages to Install");
setWindowImage();
// Automatically accept those with an empty license
// Automatically accept those with an empty license or no license
for (ArchiveInfo ai : mArchives) {
Archive a = ai.getNewArchive();
assert a != null;
String license = a.getParentPackage().getLicense();
if (license != null) {
license = license.trim();
if (license.length() == 0) {
mAccepted.add(a);
}
} else {
mAccepted.add(a);
String license = a.getParentPackage().getLicense();
ai.setAccepted(license == null || license.trim().length() == 0);
}
// Fill the list with the replacement packages
mTableViewPackage.setLabelProvider(new NewArchivesLabelProvider());
mTableViewPackage.setContentProvider(new NewArchivesContentProvider());
mTableViewPackage.setContentProvider(new NewArchivesContentProvider());
mTableViewPackage.setInput(mArchives);
adjustColumnsWidth();
@@ -331,7 +316,7 @@ final class UpdateChooserDialog extends Dialog {
if (mUpdaterData != null) {
ImageFactory imgFactory = mUpdaterData.getImageFactory();
if (imgFactory != null) {
if (imgFactory != null) {
getShell().setImage(imgFactory.getImageByName(imageName));
}
}
@@ -356,116 +341,179 @@ final class UpdateChooserDialog extends Dialog {
resizer.controlResized(null);
}
/**
* Callback invoked when the shell is closed either by clicking the close button
/**
* Captures the window size before closing this.
* @see #getInitialSize()
*/
private void onShellClosed(ShellEvent e) {
*/
@Override
public boolean close() {
sLastSize = getShell().getSize();
return super.close();
}
/**
/**
* Tries to reuse the last window size during this session.
* <p/>
* Note: the alternative would be to implement {@link #getDialogBoundsSettings()}
* since the default {@link #getDialogBoundsStrategy()} is to persist both location
* and size.
*/
private void positionShell() {
// Centers the dialog in its parent shell
Shell child = mDialogShell;
Shell parent = getParent();
if (child != null && parent != null) {
// get the parent client area with a location relative to the display
Rectangle parentArea = parent.getClientArea();
Point parentLoc = parent.getLocation();
int px = parentLoc.x;
int py = parentLoc.y;
int pw = parentArea.width;
int ph = parentArea.height;
// Reuse the last size if there's one, otherwise use the default
Point childSize = sLastSize != null ? sLastSize : child.getSize();
int cw = childSize.x;
int ch = childSize.y;
int x = px + (pw - cw) / 2;
int y = py + (ph - ch) / 2;
if (x < 0) {
x = 0;
}
if (y < MIN_Y) {
y = MIN_Y;
}
child.setLocation(x, y);
*/
@Override
protected Point getInitialSize() {
if (sLastSize != null) {
return sLastSize;
} else {
// Arbitrary values that look good on my screen and fit on 800x600
return new Point(740, 370);
}
}
/**
* Callback invoked when the Install button is selected. Fills {@link #mResult} and
* completes the dialog.
*/
private void onInstallSelected() {
// get list of accepted items
mResult.addAll(mAccepted);
mCompleted = true;
}
/**
* Callback invoked when the Cancel button is selected.
*/
private void onCancelSelected() {
mCompleted = true;
}
/**
* Callback invoked when a package item is selected in the list.
*/
private void onPackageSelected() {
Archive a = getSelectedArchive();
displayInformation(a);
private void onPackageSelected() {
ArchiveInfo ai = getSelectedArchive();
displayInformation(ai);
displayMissingDependency(ai);
updateLicenceRadios(ai);
}
/** Returns the currently selected Archive or null. */
/** Returns the currently selected {@link ArchiveInfo} or null. */
private ArchiveInfo getSelectedArchive() {
ISelection sel = mTableViewPackage.getSelection();
if (sel instanceof IStructuredSelection) {
Object elem = ((IStructuredSelection) sel).getFirstElement();
if (elem instanceof Archive) {
Object elem = ((IStructuredSelection) sel).getFirstElement();
if (elem instanceof ArchiveInfo) {
return (ArchiveInfo) elem;
}
}
return null;
}
private void displayInformation(Archive a) {
/**
* Updates the package description and license text depending on the selected package.
*/
private void displayInformation(ArchiveInfo ai) {
if (ai == null) {
mPackageText.setText("Please select a package.");
return;
}
Archive aNew = ai.getNewArchive();
Package pNew = aNew.getParentPackage();
mPackageText.setText(""); //$NON-NLS-1$
addSectionTitle("Package Description\n");
addSectionTitle("Package Description\n");
addText(pNew.getLongDescription(), "\n\n"); //$NON-NLS-1$
Archive aold = mNewToOldArchiveMap.get(a);
if (aold != null) {
addText(String.format("This update will replace revision %1$s with revision %2$s.\n\n",
aold.getParentPackage().getRevision(),
Archive aOld = ai.getReplaced();
if (aOld != null) {
Package pOld = aOld.getParentPackage();
int rOld = pOld.getRevision();
int rNew = pNew.getRevision();
boolean showRev = true;
if (pNew instanceof IPackageVersion && pOld instanceof IPackageVersion) {
AndroidVersion vOld = ((IPackageVersion) pOld).getVersion();
AndroidVersion vNew = ((IPackageVersion) pNew).getVersion();
if (!vOld.equals(vNew)) {
// Versions are different, so indicate more than just the revision.
addText(String.format("This update will replace API %1$s revision %2$d with API %3$s revision %4$d.\n\n",
vOld.getApiString(), rOld,
vNew.getApiString(), rNew));
showRev = false;
}
}
if (showRev) {
addText(String.format("This update will replace revision %1$d with revision %2$d.\n\n",
rOld,
rNew));
}
}
ArchiveInfo aDep = ai.getDependsOn();
if (aDep != null || ai.isDependencyFor()) {
addSectionTitle("Dependencies\n");
if (aDep != null) {
addText(String.format("This package depends on %1$s.\n\n",
aDep.getNewArchive().getParentPackage().getShortDescription()));
}
if (ai.isDependencyFor()) {
addText("This package is a dependency for:");
for (ArchiveInfo ai2 : ai.getDependenciesFor()) {
addText("\n- " +
ai2.getNewArchive().getParentPackage().getShortDescription());
}
addText("\n\n");
}
}
addSectionTitle("Archive Description\n");
addSectionTitle("Archive Description\n");
addText(aNew.getLongDescription(), "\n\n"); //$NON-NLS-1$
String license = pNew.getLicense();
if (license != null) {
addSectionTitle("License\n");
addSectionTitle("License\n");
addText(license.trim(), "\n\n"); //$NON-NLS-1$
}
addSectionTitle("Site\n");
addText(pNew.getParentSource().getShortDescription());
}
/**
* Computes and display missing dependency.
* If there's a selected package, check the dependency for that one.
* Otherwise display the first missing dependency.
*/
private void displayMissingDependency(ArchiveInfo ai) {
String error = null;
try {
if (ai != null) {
if (!ai.isAccepted()) {
// Case where this package blocks another one when not accepted
for (ArchiveInfo ai2 : ai.getDependenciesFor()) {
// It only matters if the blocked one is accepted
if (ai2.isAccepted()) {
error = String.format("Package '%1$s' depends on this one.",
ai2.getNewArchive().getParentPackage().getShortDescription());
return;
}
}
} else {
// Case where this package is accepted but blocked by another non-accepted one
ArchiveInfo adep = ai.getDependsOn();
if (adep != null && !adep.isAccepted()) {
error = String.format("This package depends on '%1$s'.",
adep.getNewArchive().getParentPackage().getShortDescription());
return;
}
}
}
// If there's no selection, just find the first missing dependency of any accepted
// package.
for (ArchiveInfo ai2 : mArchives) {
if (ai2.isAccepted()) {
ArchiveInfo adep = ai2.getDependsOn();
if (adep != null && !adep.isAccepted()) {
error = String.format("Package '%1$s' depends on '%2$s'",
ai2.getNewArchive().getParentPackage().getShortDescription(),
adep.getNewArchive().getParentPackage().getShortDescription());
return;
}
}
}
} finally {
mErrorLabel.setText(error == null ? "" : error); //$NON-NLS-1$
}
}
@@ -488,25 +536,42 @@ final class UpdateChooserDialog extends Dialog {
sr.underline = true;
mPackageText.setStyleRange(sr);
}
private void updateLicenceRadios(ArchiveInfo ai) {
if (mInternalLicenseRadioUpdate) {
return;
}
mInternalLicenseRadioUpdate = true;
boolean oneAccepted = false;
if (mLicenseAcceptAll) {
mLicenseRadioAcceptAll.setSelection(true);
mLicenseRadioAccept.setEnabled(true);
mLicenseRadioReject.setEnabled(true);
mLicenseRadioAccept.setSelection(false);
mLicenseRadioReject.setSelection(false);
} else {
mLicenseRadioAcceptAll.setSelection(false);
mLicenseRadioAccept.setSelection(mAccepted.contains(a));
mLicenseRadioAcceptAll.setSelection(false);
oneAccepted = ai != null && ai.isAccepted();
mLicenseRadioAccept.setEnabled(ai != null);
mLicenseRadioReject.setEnabled(ai != null);
mLicenseRadioAccept.setSelection(oneAccepted);
mLicenseRadioReject.setSelection(ai != null && ai.isRejected());
}
// The install button is enabled if there's at least one
// package accepted.
// The install button is enabled if there's at least one package accepted.
// If the current one isn't, look for another one.
boolean missing = mErrorLabel.getText() != null && mErrorLabel.getText().length() > 0;
if (!missing && !oneAccepted) {
for(ArchiveInfo ai2 : mArchives) {
if (ai2.isAccepted()) {
oneAccepted = true;
break;
}
}
}
getButton(IDialogConstants.OK_ID).setEnabled(!missing && oneAccepted);
mInternalLicenseRadioUpdate = false;
@@ -523,26 +588,28 @@ final class UpdateChooserDialog extends Dialog {
return;
}
mInternalLicenseRadioUpdate = true;
ArchiveInfo ai = getSelectedArchive();
boolean needUpdate = true;
if (!mLicenseAcceptAll && mLicenseRadioAcceptAll.getSelection()) {
// Accept all has been switched on. Mark all packages as accepted
mLicenseAcceptAll = true;
mAccepted.addAll(mNewToOldArchiveMap.keySet());
mLicenseAcceptAll = true;
for(ArchiveInfo ai2 : mArchives) {
ai2.setAccepted(true);
ai2.setRejected(false);
}
} else if (mLicenseRadioAccept.getSelection()) {
// Accept only this one
mLicenseAcceptAll = false;
mAccepted.add(a);
mLicenseAcceptAll = false;
ai.setAccepted(true);
ai.setRejected(false);
} else if (mLicenseRadioReject.getSelection()) {
// Reject only this one
mLicenseAcceptAll = false;
mAccepted.remove(a);
mLicenseAcceptAll = false;
ai.setAccepted(false);
ai.setRejected(true);
} else {
@@ -554,9 +621,10 @@ final class UpdateChooserDialog extends Dialog {
if (needUpdate) {
if (mLicenseAcceptAll) {
mTableViewPackage.refresh();
} else {
} else {
mTableViewPackage.refresh(ai);
}
}
displayMissingDependency(ai);
updateLicenceRadios(ai);
}
}
@@ -564,32 +632,29 @@ final class UpdateChooserDialog extends Dialog {
/**
* Callback invoked when a package item is double-clicked in the list.
*/
private void onPackageDoubleClick() {
private void onPackageDoubleClick() {
ArchiveInfo ai = getSelectedArchive();
if (mAccepted.contains(a)) {
// toggle from accepted to rejected
mAccepted.remove(a);
mRejected.add(a);
} else {
// toggle from rejected or unknown to accepted
mAccepted.add(a);
mRejected.remove(a);
boolean wasAccepted = ai.isAccepted();
ai.setAccepted(!wasAccepted);
ai.setRejected(wasAccepted);
// update state
mLicenseAcceptAll = false;
mTableViewPackage.refresh(a);
mLicenseAcceptAll = false;
mTableViewPackage.refresh(ai);
updateLicenceRadios(ai);
}
private class NewArchivesLabelProvider extends LabelProvider {
@Override
public Image getImage(Object element) {
assert element instanceof ArchiveInfo;
ArchiveInfo ai = (ArchiveInfo) element;
ImageFactory imgFactory = mUpdaterData.getImageFactory();
if (imgFactory != null) {
if (imgFactory != null) {
if (ai.isAccepted()) {
return imgFactory.getImageByName("accept_icon16.png");
return imgFactory.getImageByName("accept_icon16.png");
} else if (ai.isRejected()) {
return imgFactory.getImageByName("reject_icon16.png");
}
@@ -599,10 +664,16 @@ final class UpdateChooserDialog extends Dialog {
}
@Override
public String getText(Object element) {
if (element instanceof Archive) {
public String getText(Object element) {
assert element instanceof ArchiveInfo;
ArchiveInfo ai = (ArchiveInfo) element;
String desc = ai.getNewArchive().getParentPackage().getShortDescription();
if (ai.isDependencyFor()) {
desc += " [*]";
}
}
return desc;
}
}
@@ -613,11 +684,11 @@ final class UpdateChooserDialog extends Dialog {
// pass
}
public void inputChanged(Viewer viewer, Object oldInput, Object newInput) {
public void inputChanged(Viewer viewer, Object oldInput, Object newInput) {
// Ignore. The input is always mArchives
}
public Object[] getElements(Object inputElement) {
public Object[] getElements(Object inputElement) {
return mArchives.toArray();
}
}

View File

@@ -30,7 +30,6 @@ import com.android.sdklib.internal.repository.Package;
import com.android.sdklib.internal.repository.RepoSource;
import com.android.sdklib.internal.repository.RepoSources;
import com.android.sdklib.internal.repository.ToolPackage;
import com.android.sdklib.internal.repository.Package.UpdateInfo;
import com.android.sdkuilib.internal.repository.icons.ImageFactory;
import com.android.sdkuilib.repository.UpdaterWindow.ISdkListener;
@@ -42,8 +41,7 @@ import java.io.ByteArrayOutputStream;
import java.io.PrintStream;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.Map;
import java.util.HashSet;
/**
* Data shared between {@link UpdaterWindowImpl} and its pages.
@@ -66,7 +64,7 @@ class UpdaterData {
private ImageFactory mImageFactory;
private final SettingsController mSettingsController = new SettingsController();
private final SettingsController mSettingsController;
private final ArrayList<ISdkListener> mListeners = new ArrayList<ISdkListener>();
@@ -76,6 +74,8 @@ class UpdaterData {
mOsSdkRoot = osSdkRoot;
mSdkLog = sdkLog;
mSettingsController = new SettingsController(this);
initSdk();
}
@@ -148,12 +148,14 @@ class UpdaterData {
return mSettingsController;
}
/** Adds a listener ({@link ISdkListener}) that is notified when the SDK is reloaded. */
public void addListeners(ISdkListener listener) {
if (mListeners.contains(listener) == false) {
mListeners.add(listener);
}
}
/** Removes a listener ({@link ISdkListener}) that is notified when the SDK is reloaded. */
public void removeListener(ISdkListener listener) {
mListeners.remove(listener);
}
@@ -268,9 +270,9 @@ class UpdaterData {
* Install the list of given {@link Archive}s. This is invoked by the user selecting some
* packages in the remote page and then clicking "install selected".
*
* @param archives The archives to install. Incompatible ones will be skipped.
* @param result The archives to install. Incompatible ones will be skipped.
*/
public void installArchives(final Collection<Archive> archives) {
public void installArchives(final ArrayList<ArchiveInfo> result) {
if (mTaskFactory == null) {
throw new IllegalArgumentException("Task Factory is null");
}
@@ -281,14 +283,23 @@ class UpdaterData {
public void run(ITaskMonitor monitor) {
final int progressPerArchive = 2 * Archive.NUM_MONITOR_INC;
monitor.setProgressMax(archives.size() * progressPerArchive);
monitor.setProgressMax(result.size() * progressPerArchive);
monitor.setDescription("Preparing to install archives");
boolean installedAddon = false;
boolean installedTools = false;
// Mark all current local archives as already installed.
HashSet<Archive> installedArchives = new HashSet<Archive>();
for (Package p : getInstalledPackage()) {
for (Archive a : p.getArchives()) {
installedArchives.add(a);
}
}
int numInstalled = 0;
for (Archive archive : archives) {
for (ArchiveInfo ai : result) {
Archive archive = ai.getNewArchive();
int nextProgress = monitor.getProgress() + progressPerArchive;
try {
@@ -296,9 +307,24 @@ class UpdaterData {
break;
}
ArchiveInfo adep = ai.getDependsOn();
if (adep != null && !installedArchives.contains(adep.getNewArchive())) {
// This archive depends on another one that was not installed.
// Skip it.
monitor.setResult("Skipping '%1$s'; it depends on '%2$s' which was not installed.",
archive.getParentPackage().getShortDescription(),
adep.getNewArchive().getParentPackage().getShortDescription());
}
if (archive.install(mOsSdkRoot, forceHttp, mSdkManager, monitor)) {
// We installed this archive.
installedArchives.add(archive);
numInstalled++;
// If this package was replacing an existing one, the old one
// is no longer installed.
installedArchives.remove(ai.getReplaced());
// Check if we successfully installed a tool or add-on package.
if (archive.getParentPackage() instanceof AddonPackage) {
installedAddon = true;
@@ -411,8 +437,8 @@ class UpdaterData {
public void run() {
MessageDialog.openInformation(mWindowShell,
"Android Tools Updated",
"The Android SDK tool that you are currently using has been updated. " +
"It is recommended that you now close the Android SDK window and re-open it. " +
"The Android SDK and AVD Manager that you are currently using has been updated. " +
"It is recommended that you now close the manager window and re-open it. " +
"If you started this window from Eclipse, please check if the Android " +
"plug-in needs to be updated.");
}
@@ -422,9 +448,15 @@ class UpdaterData {
/**
* Tries to update all the *existing* local packages.
* This first refreshes all sources, then compares the available remote packages with
* the current local ones and suggest updates to be done to the user. Finally all
* selected updates are installed.
* <p/>
* There are two modes of operation:
* <ul>
* <li>If selectedArchives is null, refreshes all sources, compares the available remote
* packages with the current local ones and suggest updates to be done to the user (including
* new platforms that the users doesn't have yet).
* <li>If selectedArchives is not null, this represents a list of archives/packages that
* the user wants to install or update, so just process these.
* </ul>
*
* @param selectedArchives The list of remote archive to consider for the update.
* This can be null, in which case a list of remote archive is fetched from all
@@ -435,28 +467,27 @@ class UpdaterData {
refreshSources(true);
}
final Map<Archive, Archive> updates = findUpdates(selectedArchives);
UpdaterLogic ul = new UpdaterLogic();
ArrayList<ArchiveInfo> archives = ul.computeUpdates(
selectedArchives,
getSources(),
getLocalSdkParser().getPackages());
if (selectedArchives != null) {
// Not only we want to perform updates but we also want to install the
// selected archives. If they do not match an update, list them anyway
// except they map themselves to null (no "old" archive)
for (Archive a : selectedArchives) {
if (!updates.containsKey(a)) {
updates.put(a, null);
}
}
if (selectedArchives == null) {
ul.addNewPlatforms(archives, getSources(), getLocalSdkParser().getPackages());
}
UpdateChooserDialog dialog = new UpdateChooserDialog(getWindowShell(), this, updates);
// TODO if selectedArchives is null and archives.len==0, find if there's
// any new platform we can suggest to install instead.
UpdateChooserDialog dialog = new UpdateChooserDialog(getWindowShell(), this, archives);
dialog.open();
Collection<Archive> result = dialog.getResult();
ArrayList<ArchiveInfo> result = dialog.getResult();
if (result != null && result.size() > 0) {
installArchives(result);
}
}
/**
* Refresh all sources. This is invoked either internally (reusing an existing monitor)
* or as a UI callback on the remote page "Refresh" button (in which case the monitor is
@@ -485,108 +516,4 @@ class UpdaterData {
}
});
}
/**
* Check the local archives vs the remote available packages to find potential updates.
* Return a map [remote archive => local archive] of suitable update candidates.
* Returns null if there's an unexpected error. Otherwise returns a map that can be
* empty but not null.
*
* @param selectedArchives The list of remote archive to consider for the update.
* This can be null, in which case a list of remote archive is fetched from all
* available sources.
*/
private Map<Archive, Archive> findUpdates(Collection<Archive> selectedArchives) {
// Map [remote archive => local archive] of suitable update candidates
Map<Archive, Archive> result = new HashMap<Archive, Archive>();
// First go thru all sources and make a list of all available remote archives
// sorted by package class.
HashMap<Class<? extends Package>, ArrayList<Archive>> availablePkgs =
new HashMap<Class<? extends Package>, ArrayList<Archive>>();
if (selectedArchives != null) {
// Only consider the archives given
for (Archive a : selectedArchives) {
// Only add compatible archives
if (a.isCompatible()) {
Class<? extends Package> clazz = a.getParentPackage().getClass();
ArrayList<Archive> list = availablePkgs.get(clazz);
if (list == null) {
availablePkgs.put(clazz, list = new ArrayList<Archive>());
}
list.add(a);
}
}
} else {
// Get all the available archives from all loaded sources
RepoSource[] remoteSources = getSources().getSources();
for (RepoSource remoteSrc : remoteSources) {
Package[] remotePkgs = remoteSrc.getPackages();
if (remotePkgs != null) {
for (Package remotePkg : remotePkgs) {
Class<? extends Package> clazz = remotePkg.getClass();
ArrayList<Archive> list = availablePkgs.get(clazz);
if (list == null) {
availablePkgs.put(clazz, list = new ArrayList<Archive>());
}
for (Archive a : remotePkg.getArchives()) {
// Only add compatible archives
if (a.isCompatible()) {
list.add(a);
}
}
}
}
}
}
Package[] localPkgs = getLocalSdkParser().getPackages();
if (localPkgs == null) {
// This is unexpected. The local sdk parser should have been called first.
return null;
}
for (Package localPkg : localPkgs) {
// get the available archive list for this package type
ArrayList<Archive> list = availablePkgs.get(localPkg.getClass());
// if this list is empty, we'll never find anything that matches
if (list == null || list.size() == 0) {
continue;
}
// local packages should have one archive at most
Archive[] localArchives = localPkg.getArchives();
if (localArchives != null && localArchives.length > 0) {
Archive localArchive = localArchives[0];
// only consider archives compatible with the current platform
if (localArchive != null && localArchive.isCompatible()) {
// We checked all this archive stuff because that's what eventually gets
// installed, but the "update" mechanism really works on packages. So now
// the real question: is there a remote package that can update this
// local package?
for (Archive availArchive : list) {
UpdateInfo info = localPkg.canBeUpdatedBy(availArchive.getParentPackage());
if (info == UpdateInfo.UPDATE) {
// Found one!
result.put(availArchive, localArchive);
break;
}
}
}
}
}
return result;
}
}

View File

@@ -0,0 +1,490 @@
/*
* Copyright (C) 2009 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.sdkuilib.internal.repository;
import com.android.sdklib.AndroidVersion;
import com.android.sdklib.internal.repository.AddonPackage;
import com.android.sdklib.internal.repository.Archive;
import com.android.sdklib.internal.repository.DocPackage;
import com.android.sdklib.internal.repository.ExtraPackage;
import com.android.sdklib.internal.repository.IPackageVersion;
import com.android.sdklib.internal.repository.MinToolsPackage;
import com.android.sdklib.internal.repository.Package;
import com.android.sdklib.internal.repository.PlatformPackage;
import com.android.sdklib.internal.repository.RepoSource;
import com.android.sdklib.internal.repository.RepoSources;
import com.android.sdklib.internal.repository.ToolPackage;
import com.android.sdklib.internal.repository.Package.UpdateInfo;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
/**
* The logic to compute which packages to install, based on the choices
* made by the user. This adds dependent packages as needed.
* <p/>
* When the user doesn't provide a selection, looks at local package to find
* those that can be updated and compute dependencies too.
*/
class UpdaterLogic {
/**
* Compute which packages to install by taking the user selection
* and adding dependent packages as needed.
*
* When the user doesn't provide a selection, looks at local package to find
* those that can be updated and compute dependencies too.
*/
public ArrayList<ArchiveInfo> computeUpdates(
Collection<Archive> selectedArchives,
RepoSources sources,
Package[] localPkgs) {
ArrayList<ArchiveInfo> archives = new ArrayList<ArchiveInfo>();
ArrayList<Package> remotePkgs = new ArrayList<Package>();
RepoSource[] remoteSources = sources.getSources();
if (selectedArchives == null) {
selectedArchives = findUpdates(localPkgs, remotePkgs, remoteSources);
}
for (Archive a : selectedArchives) {
insertArchive(a,
archives,
selectedArchives,
remotePkgs,
remoteSources,
localPkgs,
false /*automated*/);
}
return archives;
}
/**
* Finds new platforms that the user does not have in his/her local SDK
* and adds them to the list of archives to install.
*/
public void addNewPlatforms(ArrayList<ArchiveInfo> archives,
RepoSources sources,
Package[] localPkgs) {
// Find the highest platform installed
float currentPlatformScore = 0;
float currentAddonScore = 0;
float currentDocScore = 0;
HashMap<String, Float> currentExtraScore = new HashMap<String, Float>();
for (Package p : localPkgs) {
int rev = p.getRevision();
int api = 0;
boolean isPreview = false;
if (p instanceof IPackageVersion) {
AndroidVersion vers = ((IPackageVersion) p).getVersion();
api = vers.getApiLevel();
isPreview = vers.isPreview();
}
// The score is 10*api + (1 if preview) + rev/100
// This allows previews to rank above a non-preview and
// allows revisions to rank appropriately.
float score = api * 10 + (isPreview ? 1 : 0) + rev/100.f;
if (p instanceof PlatformPackage) {
currentPlatformScore = Math.max(currentPlatformScore, score);
} else if (p instanceof AddonPackage) {
currentAddonScore = Math.max(currentAddonScore, score);
} else if (p instanceof ExtraPackage) {
currentExtraScore.put(((ExtraPackage) p).getPath(), score);
} else if (p instanceof DocPackage) {
currentDocScore = Math.max(currentDocScore, score);
}
}
RepoSource[] remoteSources = sources.getSources();
ArrayList<Package> remotePkgs = new ArrayList<Package>();
fetchRemotePackages(remotePkgs, remoteSources);
Package suggestedDoc = null;
for (Package p : remotePkgs) {
int rev = p.getRevision();
int api = 0;
boolean isPreview = false;
if (p instanceof IPackageVersion) {
AndroidVersion vers = ((IPackageVersion) p).getVersion();
api = vers.getApiLevel();
isPreview = vers.isPreview();
}
float score = api * 10 + (isPreview ? 1 : 0) + rev/100.f;
boolean shouldAdd = false;
if (p instanceof PlatformPackage) {
shouldAdd = score > currentPlatformScore;
} else if (p instanceof AddonPackage) {
shouldAdd = score > currentAddonScore;
} else if (p instanceof ExtraPackage) {
String key = ((ExtraPackage) p).getPath();
shouldAdd = !currentExtraScore.containsKey(key) ||
score > currentExtraScore.get(key).floatValue();
} else if (p instanceof DocPackage) {
// We don't want all the doc, only the most recent one
if (score > currentDocScore) {
suggestedDoc = p;
currentDocScore = score;
}
}
if (shouldAdd) {
// We should suggest this package for installation.
for (Archive a : p.getArchives()) {
if (a.isCompatible()) {
insertArchive(a,
archives,
null /*selectedArchives*/,
remotePkgs,
remoteSources,
localPkgs,
true /*automated*/);
}
}
}
}
if (suggestedDoc != null) {
// We should suggest this package for installation.
for (Archive a : suggestedDoc.getArchives()) {
if (a.isCompatible()) {
insertArchive(a,
archives,
null /*selectedArchives*/,
remotePkgs,
remoteSources,
localPkgs,
true /*automated*/);
}
}
}
}
/**
* Find suitable updates to all current local packages.
*/
private Collection<Archive> findUpdates(Package[] localPkgs,
ArrayList<Package> remotePkgs,
RepoSource[] remoteSources) {
ArrayList<Archive> updates = new ArrayList<Archive>();
fetchRemotePackages(remotePkgs, remoteSources);
for (Package localPkg : localPkgs) {
for (Package remotePkg : remotePkgs) {
if (localPkg.canBeUpdatedBy(remotePkg) == UpdateInfo.UPDATE) {
// Found a suitable update. Only accept the remote package
// if it provides at least one compatible archive.
for (Archive a : remotePkg.getArchives()) {
if (a.isCompatible()) {
updates.add(a);
break;
}
}
}
}
}
return updates;
}
private ArchiveInfo insertArchive(Archive archive,
ArrayList<ArchiveInfo> outArchives,
Collection<Archive> selectedArchives,
ArrayList<Package> remotePkgs,
RepoSource[] remoteSources,
Package[] localPkgs,
boolean automated) {
Package p = archive.getParentPackage();
// Is this an update?
Archive updatedArchive = null;
for (Package lp : localPkgs) {
assert lp.getArchives().length == 1;
if (lp.getArchives().length > 0 && lp.canBeUpdatedBy(p) == UpdateInfo.UPDATE) {
updatedArchive = lp.getArchives()[0];
}
}
// find dependencies
ArchiveInfo dep = findDependency(p,
outArchives,
selectedArchives,
remotePkgs,
remoteSources,
localPkgs);
// Make sure it's not a dup
ArchiveInfo ai = null;
for (ArchiveInfo ai2 : outArchives) {
if (ai2.getNewArchive().getParentPackage().sameItemAs(archive.getParentPackage())) {
ai = ai2;
break;
}
}
if (ai == null) {
ai = new ArchiveInfo(
archive, //newArchive
updatedArchive, //replaced
dep //dependsOn
);
outArchives.add(ai);
}
if (dep != null) {
dep.addDependencyFor(ai);
}
return ai;
}
private ArchiveInfo findDependency(Package pkg,
ArrayList<ArchiveInfo> outArchives,
Collection<Archive> selectedArchives,
ArrayList<Package> remotePkgs,
RepoSource[] remoteSources,
Package[] localPkgs) {
// Current dependencies can be:
// - addon: *always* depends on platform of same API level
// - platform: *might* depends on tools of rev >= min-tools-rev
if (pkg instanceof AddonPackage) {
AddonPackage addon = (AddonPackage) pkg;
return findPlatformDependency(
addon,
outArchives,
selectedArchives,
remotePkgs,
remoteSources,
localPkgs);
} else if (pkg instanceof MinToolsPackage) {
MinToolsPackage platformOrExtra = (MinToolsPackage) pkg;
return findToolsDependency(
platformOrExtra,
outArchives,
selectedArchives,
remotePkgs,
remoteSources,
localPkgs);
}
return null;
}
/**
* Resolves dependencies on tools.
*
* A platform or an extra package can both have a min-tools-rev, in which case it
* depends on having a tools package of the requested revision.
* Finds the tools dependency. If found, add it to the list of things to install.
* Returns the archive info dependency, if any.
*/
protected ArchiveInfo findToolsDependency(MinToolsPackage platformOrExtra,
ArrayList<ArchiveInfo> outArchives,
Collection<Archive> selectedArchives,
ArrayList<Package> remotePkgs,
RepoSource[] remoteSources,
Package[] localPkgs) {
// This is the requirement to match.
int rev = platformOrExtra.getMinToolsRevision();
if (rev == MinToolsPackage.MIN_TOOLS_REV_NOT_SPECIFIED) {
// Well actually there's no requirement.
return null;
}
// First look in local packages.
for (Package p : localPkgs) {
if (p instanceof ToolPackage) {
if (((ToolPackage) p).getRevision() >= rev) {
// We found one already installed. We don't report this dependency
// as the UI only cares about resolving "newly added dependencies".
return null;
}
}
}
// Look in archives already scheduled for install
for (ArchiveInfo ai : outArchives) {
Package p = ai.getNewArchive().getParentPackage();
if (p instanceof ToolPackage) {
if (((ToolPackage) p).getRevision() >= rev) {
// The dependency is already scheduled for install, nothing else to do.
return ai;
}
}
}
// Otherwise look in the selected archives.
if (selectedArchives != null) {
for (Archive a : selectedArchives) {
Package p = a.getParentPackage();
if (p instanceof ToolPackage) {
if (((ToolPackage) p).getRevision() >= rev) {
// It's not already in the list of things to install, so add it now
return insertArchive(a, outArchives,
selectedArchives, remotePkgs, remoteSources, localPkgs,
true /*automated*/);
}
}
}
}
// Finally nothing matched, so let's look at all available remote packages
fetchRemotePackages(remotePkgs, remoteSources);
for (Package p : remotePkgs) {
if (p instanceof ToolPackage) {
if (((ToolPackage) p).getRevision() >= rev) {
// It's not already in the list of things to install, so add the
// first compatible archive we can find.
for (Archive a : p.getArchives()) {
if (a.isCompatible()) {
return insertArchive(a, outArchives,
selectedArchives, remotePkgs, remoteSources, localPkgs,
true /*automated*/);
}
}
}
}
}
// We end up here if nothing matches. We don't have a good tools to match.
// Seriously, that can't happens unless we totally screwed our repo manifest.
// We'll let this one go through anyway.
return null;
}
/**
* Resolves dependencies on platform.
*
* An addon depends on having a platform with the same API version.
* Finds the platform dependency. If found, add it to the list of things to install.
* Returns the archive info dependency, if any.
*/
protected ArchiveInfo findPlatformDependency(AddonPackage addon,
ArrayList<ArchiveInfo> outArchives,
Collection<Archive> selectedArchives,
ArrayList<Package> remotePkgs,
RepoSource[] remoteSources,
Package[] localPkgs) {
// This is the requirement to match.
AndroidVersion v = addon.getVersion();
// Find a platform that would satisfy the requirement.
// First look in local packages.
for (Package p : localPkgs) {
if (p instanceof PlatformPackage) {
if (v.equals(((PlatformPackage) p).getVersion())) {
// We found one already installed. We don't report this dependency
// as the UI only cares about resolving "newly added dependencies".
return null;
}
}
}
// Look in archives already scheduled for install
for (ArchiveInfo ai : outArchives) {
Package p = ai.getNewArchive().getParentPackage();
if (p instanceof PlatformPackage) {
if (v.equals(((PlatformPackage) p).getVersion())) {
// The dependency is already scheduled for install, nothing else to do.
return ai;
}
}
}
// Otherwise look in the selected archives.
if (selectedArchives != null) {
for (Archive a : selectedArchives) {
Package p = a.getParentPackage();
if (p instanceof PlatformPackage) {
if (v.equals(((PlatformPackage) p).getVersion())) {
// It's not already in the list of things to install, so add it now
return insertArchive(a, outArchives,
selectedArchives, remotePkgs, remoteSources, localPkgs,
true /*automated*/);
}
}
}
}
// Finally nothing matched, so let's look at all available remote packages
fetchRemotePackages(remotePkgs, remoteSources);
for (Package p : remotePkgs) {
if (p instanceof PlatformPackage) {
if (v.equals(((PlatformPackage) p).getVersion())) {
// It's not already in the list of things to install, so add the
// first compatible archive we can find.
for (Archive a : p.getArchives()) {
if (a.isCompatible()) {
return insertArchive(a, outArchives,
selectedArchives, remotePkgs, remoteSources, localPkgs,
true /*automated*/);
}
}
}
}
}
// We end up here if nothing matches. We don't have a good platform to match.
// Seriously, that can't happens unless the repository contains a bogus addon
// entry that does not match any existing platform API level.
// It's conceivable that a 3rd part addon repo might have error, in which case
// we'll let this one go through anyway.
return null;
}
/** Fetch all remote packages only if really needed. */
protected void fetchRemotePackages(ArrayList<Package> remotePkgs, RepoSource[] remoteSources) {
if (remotePkgs.size() > 0) {
return;
}
for (RepoSource remoteSrc : remoteSources) {
Package[] pkgs = remoteSrc.getPackages();
if (pkgs != null) {
nextPackage: for (Package pkg : pkgs) {
for (Archive a : pkg.getArchives()) {
// Only add a package if it contains at least one compatible archive
if (a.isCompatible()) {
remotePkgs.add(pkg);
continue nextPackage;
}
}
}
}
}
}
}

View File

@@ -60,7 +60,11 @@ public class UpdaterWindowImpl {
private ArrayList<Object[]> mExtraPages;
/** A factory to create progress task dialogs. */
private ProgressTaskFactory mTaskFactory;
/** The initial page to display. If null or not a know class, the first page will be displayed.
* Must be set before the first call to {@link #open()}. */
private Class<? extends Composite> mInitialPage;
/** Sets whether the auto-update wizard will be shown when opening the window. */
private boolean mRequestAutoUpdate;
// --- UI members ---
@@ -121,7 +125,7 @@ public class UpdaterWindowImpl {
fl.marginHeight = fl.marginWidth = 5;
mAndroidSdkUpdater.setMinimumSize(new Point(200, 50));
mAndroidSdkUpdater.setSize(745, 433);
mAndroidSdkUpdater.setText("Android SDK");
mAndroidSdkUpdater.setText("Android SDK and AVD Manager");
mSashForm = new SashForm(mAndroidSdkUpdater, SWT.NONE);
@@ -148,7 +152,7 @@ public class UpdaterWindowImpl {
// Hide everything down-below from SWT designer
//$hide>>$
// --- UI Callbacks -----------
// --- Public API -----------
/**
@@ -169,14 +173,42 @@ public class UpdaterWindowImpl {
mExtraPages.add(new Object[]{ title, pageClass });
}
/**
* Indicate the initial page that should be selected when the window opens.
* This must be called before the call to {@link #open()}.
* If null or if the page class is not found, the first page will be selected.
*/
public void setInitialPage(Class<? extends Composite> pageClass) {
mInitialPage = pageClass;
}
/**
* Sets whether the auto-update wizard will be shown when opening the window.
* <p/>
* This must be called before the call to {@link #open()}.
*/
public void setRequestAutoUpdate(boolean requestAutoUpdate) {
mRequestAutoUpdate = requestAutoUpdate;
}
/**
* Adds a new listener to be notified when a change is made to the content of the SDK.
*/
public void addListeners(ISdkListener listener) {
mUpdaterData.addListeners(listener);
}
/**
* Removes a new listener to be notified anymore when a change is made to the content of
* the SDK.
*/
public void removeListener(ISdkListener listener) {
mUpdaterData.removeListener(listener);
}
// --- Internals & UI Callbacks -----------
/**
* Helper to return the SWT shell.
*/
@@ -230,12 +262,25 @@ public class UpdaterWindowImpl {
addPage(mRemotePackagesPage, "Available Packages");
addExtraPages();
displayPage(0);
mPageList.setSelection(0);
int pageIndex = 0;
int i = 0;
for (Composite p : mPages) {
if (p.getClass().equals(mInitialPage)) {
pageIndex = i;
break;
}
i++;
}
displayPage(pageIndex);
mPageList.setSelection(pageIndex);
setupSources();
initializeSettings();
mUpdaterData.notifyListeners();
if (mRequestAutoUpdate) {
mUpdaterData.updateOrInstallAll(null /*selectedArchives*/);
}
}
/**

View File

@@ -19,6 +19,8 @@ package com.android.sdkuilib.internal.tasks;
import com.android.sdklib.internal.repository.ITask;
import com.android.sdklib.internal.repository.ITaskMonitor;
import org.eclipse.jface.dialogs.MessageDialog;
import org.eclipse.swt.widgets.Display;
import org.eclipse.swt.widgets.ProgressBar;
import org.eclipse.swt.widgets.Shell;
@@ -142,6 +144,30 @@ public final class ProgressTask implements ITaskMonitor {
return null;
}
/**
* Display a yes/no question dialog box.
*
* This implementation allow this to be called from any thread, it
* makes sure the dialog is opened synchronously in the ui thread.
*
* @param title The title of the dialog box
* @param message The error message
* @return true if YES was clicked.
*/
public boolean displayPrompt(final String title, final String message) {
final Shell shell = mDialog.getParent();
Display display = shell.getDisplay();
// we need to ask the user what he wants to do.
final boolean[] result = new boolean[] { false };
display.syncExec(new Runnable() {
public void run() {
result[0] = MessageDialog.openQuestion(shell, title, message);
}
});
return result[0];
}
/**
* Creates a sub-monitor that will use up to tickCount on the progress bar.
* tickCount must be 1 or more.
@@ -222,6 +248,10 @@ public final class ProgressTask implements ITaskMonitor {
}
}
public boolean displayPrompt(String title, String message) {
return mRoot.displayPrompt(title, message);
}
public ITaskMonitor createSubMonitor(int tickCount) {
assert mSubCoef > 0;
assert tickCount > 0;

View File

@@ -1,177 +0,0 @@
/*
* Copyright (C) 2009 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.sdkuilib.internal.widgets;
import org.eclipse.jface.dialogs.Dialog;
import org.eclipse.jface.dialogs.IDialogConstants;
import org.eclipse.jface.window.Window;
import org.eclipse.swt.SWT;
import org.eclipse.swt.events.ModifyEvent;
import org.eclipse.swt.events.ModifyListener;
import org.eclipse.swt.events.VerifyEvent;
import org.eclipse.swt.events.VerifyListener;
import org.eclipse.swt.layout.GridData;
import org.eclipse.swt.layout.GridLayout;
import org.eclipse.swt.widgets.Button;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Control;
import org.eclipse.swt.widgets.Label;
import org.eclipse.swt.widgets.Shell;
import org.eclipse.swt.widgets.Text;
/**
* Edit dialog to create/edit APK configuration. The dialog displays 2 text fields for the config
* name and its filter.
*/
class ApkConfigEditDialog extends Dialog implements ModifyListener, VerifyListener {
private String mName;
private String mFilter;
private Text mNameField;
private Text mFilterField;
private Button mOkButton;
/**
* Creates an edit dialog with optional initial values for the name and filter.
* @param name optional value for the name. Can be null.
* @param filter optional value for the filter. Can be null.
* @param parentShell the parent shell.
*/
protected ApkConfigEditDialog(String name, String filter, Shell parentShell) {
super(parentShell);
mName = name;
mFilter = filter;
}
/**
* Returns the name of the config. This is only valid if the user clicked OK and {@link #open()}
* returned {@link Window#OK}
*/
public String getName() {
return mName;
}
/**
* Returns the filter for the config. This is only valid if the user clicked OK and
* {@link #open()} returned {@link Window#OK}
*/
public String getFilter() {
return mFilter;
}
@Override
protected Control createContents(Composite parent) {
Control control = super.createContents(parent);
mOkButton = getButton(IDialogConstants.OK_ID);
validateButtons();
return control;
}
@Override
protected Control createDialogArea(Composite parent) {
Composite composite = new Composite(parent, SWT.NONE);
GridLayout layout;
composite.setLayout(layout = new GridLayout(2, false));
layout.marginHeight = convertVerticalDLUsToPixels(IDialogConstants.VERTICAL_MARGIN);
layout.marginWidth = convertHorizontalDLUsToPixels(IDialogConstants.HORIZONTAL_MARGIN);
layout.verticalSpacing = convertVerticalDLUsToPixels(IDialogConstants.VERTICAL_SPACING);
layout.horizontalSpacing = convertHorizontalDLUsToPixels(
IDialogConstants.HORIZONTAL_SPACING);
composite.setLayoutData(new GridData(GridData.FILL_BOTH));
Label l = new Label(composite, SWT.NONE);
l.setText("Name");
mNameField = new Text(composite, SWT.BORDER);
mNameField.setLayoutData(new GridData(GridData.FILL_HORIZONTAL));
mNameField.addVerifyListener(this);
if (mName != null) {
mNameField.setText(mName);
}
mNameField.addModifyListener(this);
l = new Label(composite, SWT.NONE);
l.setText("Filter");
mFilterField = new Text(composite, SWT.BORDER);
mFilterField.setLayoutData(new GridData(GridData.FILL_HORIZONTAL));
if (mFilter != null) {
mFilterField.setText(mFilter);
}
mFilterField.addVerifyListener(this);
mFilterField.addModifyListener(this);
applyDialogFont(composite);
return composite;
}
/**
* Validates the OK button based on the content of the 2 text fields.
*/
private void validateButtons() {
mOkButton.setEnabled(mNameField.getText().trim().length() > 0 &&
mFilterField.getText().trim().length() > 0);
}
@Override
protected void okPressed() {
mName = mNameField.getText();
mFilter = mFilterField.getText().trim();
super.okPressed();
}
/**
* Callback for text modification in the 2 text fields.
*/
public void modifyText(ModifyEvent e) {
validateButtons();
}
/**
* Callback to ensure the content of the text field are proper.
*/
public void verifyText(VerifyEvent e) {
Text source = ((Text)e.getSource());
if (source == mNameField) {
// check for a-zA-Z0-9.
final String text = e.text;
final int len = text.length();
for (int i = 0 ; i < len; i++) {
char letter = text.charAt(i);
if (letter > 255 || Character.isLetterOrDigit(letter) == false) {
e.doit = false;
return;
}
}
} else if (source == mFilterField) {
// we can't validate the content as its typed, but we can at least ensure the characters
// are valid. Same as mNameFiled + the comma.
final String text = e.text;
final int len = text.length();
for (int i = 0 ; i < len; i++) {
char letter = text.charAt(i);
if (letter > 255 || (Character.isLetterOrDigit(letter) == false && letter != ',')) {
e.doit = false;
return;
}
}
}
}
}

View File

@@ -1,211 +0,0 @@
/*
* Copyright (C) 2009 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.sdkuilib.internal.widgets;
import org.eclipse.jface.dialogs.Dialog;
import org.eclipse.jface.dialogs.MessageDialog;
import org.eclipse.swt.SWT;
import org.eclipse.swt.events.ControlAdapter;
import org.eclipse.swt.events.ControlEvent;
import org.eclipse.swt.events.SelectionAdapter;
import org.eclipse.swt.events.SelectionEvent;
import org.eclipse.swt.graphics.Rectangle;
import org.eclipse.swt.layout.GridData;
import org.eclipse.swt.layout.GridLayout;
import org.eclipse.swt.widgets.Button;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Table;
import org.eclipse.swt.widgets.TableColumn;
import org.eclipse.swt.widgets.TableItem;
import java.util.Arrays;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;
/**
* The APK Configuration widget is a table that is added to the given parent composite.
* <p/>
* To use, create it using {@link #ApkConfigWidget(Composite)} then
* call {@link #fillTable(Map)} to set the initial list of configurations.
*/
public class ApkConfigWidget {
private final static int INDEX_NAME = 0;
private final static int INDEX_FILTER = 1;
private Table mApkConfigTable;
private Button mEditButton;
private Button mDelButton;
public ApkConfigWidget(final Composite parent) {
final Composite apkConfigComp = new Composite(parent, SWT.NONE);
apkConfigComp.setLayoutData(new GridData(GridData.FILL_BOTH));
apkConfigComp.setLayout(new GridLayout(2, false));
mApkConfigTable = new Table(apkConfigComp, SWT.FULL_SELECTION | SWT.SINGLE | SWT.BORDER);
mApkConfigTable.setHeaderVisible(true);
mApkConfigTable.setLinesVisible(true);
GridData data = new GridData();
data.grabExcessVerticalSpace = true;
data.grabExcessHorizontalSpace = true;
data.horizontalAlignment = GridData.FILL;
data.verticalAlignment = GridData.FILL;
mApkConfigTable.setLayoutData(data);
// create the table columns
final TableColumn column0 = new TableColumn(mApkConfigTable, SWT.NONE);
column0.setText("Name");
column0.setWidth(100);
final TableColumn column1 = new TableColumn(mApkConfigTable, SWT.NONE);
column1.setText("Configuration");
column1.setWidth(100);
Composite buttonComp = new Composite(apkConfigComp, SWT.NONE);
buttonComp.setLayoutData(new GridData(GridData.FILL_VERTICAL));
GridLayout gl;
buttonComp.setLayout(gl = new GridLayout(1, false));
gl.marginHeight = gl.marginWidth = 0;
Button newButton = new Button(buttonComp, SWT.PUSH | SWT.FLAT);
newButton.setText("New...");
newButton.setLayoutData(new GridData(GridData.FILL_HORIZONTAL));
mEditButton = new Button(buttonComp, SWT.PUSH | SWT.FLAT);
mEditButton.setText("Edit...");
mEditButton.setLayoutData(new GridData(GridData.FILL_HORIZONTAL));
mDelButton = new Button(buttonComp, SWT.PUSH | SWT.FLAT);
mDelButton.setText("Delete");
mDelButton.setLayoutData(new GridData(GridData.FILL_HORIZONTAL));
newButton.addSelectionListener(new SelectionAdapter() {
@Override
public void widgetSelected(SelectionEvent e) {
ApkConfigEditDialog dlg = new ApkConfigEditDialog(null /*name*/, null /*filter*/,
apkConfigComp.getShell());
if (dlg.open() == Dialog.OK) {
TableItem item = new TableItem(mApkConfigTable, SWT.NONE);
item.setText(INDEX_NAME, dlg.getName());
item.setText(INDEX_FILTER, dlg.getFilter());
onSelectionChanged();
}
}
});
mEditButton.addSelectionListener(new SelectionAdapter() {
@Override
public void widgetSelected(SelectionEvent e) {
// get the current selection (single mode so we don't care about any item beyond
// index 0).
TableItem[] items = mApkConfigTable.getSelection();
if (items.length != 0) {
ApkConfigEditDialog dlg = new ApkConfigEditDialog(
items[0].getText(INDEX_NAME), items[0].getText(INDEX_FILTER),
apkConfigComp.getShell());
if (dlg.open() == Dialog.OK) {
items[0].setText(INDEX_NAME, dlg.getName());
items[0].setText(INDEX_FILTER, dlg.getFilter());
}
}
}
});
mDelButton.addSelectionListener(new SelectionAdapter() {
@Override
public void widgetSelected(SelectionEvent e) {
// get the current selection (single mode so we don't care about any item beyond
// index 0).
int[] indices = mApkConfigTable.getSelectionIndices();
if (indices.length != 0) {
TableItem item = mApkConfigTable.getItem(indices[0]);
if (MessageDialog.openQuestion(parent.getShell(),
"Apk Configuration deletion",
String.format(
"Are you sure you want to delete configuration '%1$s'?",
item.getText(INDEX_NAME)))) {
// delete the item.
mApkConfigTable.remove(indices[0]);
onSelectionChanged();
}
}
}
});
// Add a listener to resize the column to the full width of the table
mApkConfigTable.addControlListener(new ControlAdapter() {
@Override
public void controlResized(ControlEvent e) {
Rectangle r = mApkConfigTable.getClientArea();
column0.setWidth(r.width * 30 / 100); // 30%
column1.setWidth(r.width * 70 / 100); // 70%
}
});
// add a selection listener on the table, to enable/disable buttons.
mApkConfigTable.addSelectionListener(new SelectionAdapter() {
@Override
public void widgetSelected(SelectionEvent e) {
onSelectionChanged();
}
});
}
public void fillTable(Map<String, String> apkConfigMap) {
// get the names in a list so that we can sort them.
if (apkConfigMap != null) {
Set<String> keys = apkConfigMap.keySet();
String[] keyArray = keys.toArray(new String[keys.size()]);
Arrays.sort(keyArray);
for (String key : keyArray) {
TableItem item = new TableItem(mApkConfigTable, SWT.NONE);
item.setText(INDEX_NAME, key);
item.setText(INDEX_FILTER, apkConfigMap.get(key));
}
}
onSelectionChanged();
}
public Map<String, String> getApkConfigs() {
// go through all the items from the table and fill a new map
HashMap<String, String> map = new HashMap<String, String>();
TableItem[] items = mApkConfigTable.getItems();
for (TableItem item : items) {
map.put(item.getText(INDEX_NAME), item.getText(INDEX_FILTER));
}
return map;
}
/**
* Handles table selection changes.
*/
private void onSelectionChanged() {
if (mApkConfigTable.getSelectionCount() > 0) {
mEditButton.setEnabled(true);
mDelButton.setEnabled(true);
} else {
mEditButton.setEnabled(false);
mDelButton.setEnabled(false);
}
}
}

View File

@@ -19,19 +19,39 @@ package com.android.sdkuilib.internal.widgets;
import com.android.prefs.AndroidLocation;
import com.android.prefs.AndroidLocation.AndroidLocationException;
import com.android.sdklib.IAndroidTarget;
import com.android.sdklib.SdkConstants;
import com.android.sdklib.SdkManager;
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.sdkuilib.internal.repository.icons.ImageFactory;
import com.android.sdkuilib.internal.widgets.AvdSelector.SdkLog;
import com.android.sdkuilib.ui.GridDialog;
import org.eclipse.jface.dialogs.Dialog;
import org.eclipse.jface.dialogs.IDialogConstants;
import org.eclipse.jface.viewers.CellEditor;
import org.eclipse.jface.viewers.CellLabelProvider;
import org.eclipse.jface.viewers.ComboBoxCellEditor;
import org.eclipse.jface.viewers.EditingSupport;
import org.eclipse.jface.viewers.ISelection;
import org.eclipse.jface.viewers.ISelectionChangedListener;
import org.eclipse.jface.viewers.IStructuredContentProvider;
import org.eclipse.jface.viewers.IStructuredSelection;
import org.eclipse.jface.viewers.SelectionChangedEvent;
import org.eclipse.jface.viewers.TableViewer;
import org.eclipse.jface.viewers.TableViewerColumn;
import org.eclipse.jface.viewers.TextCellEditor;
import org.eclipse.jface.viewers.Viewer;
import org.eclipse.jface.viewers.ViewerCell;
import org.eclipse.jface.window.Window;
import org.eclipse.swt.SWT;
import org.eclipse.swt.events.ModifyEvent;
import org.eclipse.swt.events.ModifyListener;
import org.eclipse.swt.events.SelectionAdapter;
import org.eclipse.swt.events.SelectionEvent;
import org.eclipse.swt.events.VerifyEvent;
import org.eclipse.swt.events.VerifyListener;
import org.eclipse.swt.graphics.Point;
import org.eclipse.swt.layout.GridData;
import org.eclipse.swt.layout.GridLayout;
@@ -40,35 +60,60 @@ import org.eclipse.swt.widgets.Combo;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Control;
import org.eclipse.swt.widgets.FileDialog;
import org.eclipse.swt.widgets.Group;
import org.eclipse.swt.widgets.Label;
import org.eclipse.swt.widgets.Shell;
import org.eclipse.swt.widgets.Table;
import org.eclipse.swt.widgets.TableColumn;
import org.eclipse.swt.widgets.Text;
import java.io.File;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Map;
import java.util.TreeMap;
import java.util.Map.Entry;
/**
* AVD creator dialog.
*
* TODO:
* - support custom hardware properties
* - use SdkTargetSelector instead of Combo
* - Better UI for the sdcard (radio button for K or M, info about what is valid value)
* - Support for ###x### skins
* - tooltips on widgets.
*
*/
final class AvdCreationDialog extends Dialog {
final class AvdCreationDialog extends GridDialog {
private final AvdManager mAvdManager;
private final TreeMap<String, IAndroidTarget> mCurrentTargets =
new TreeMap<String, IAndroidTarget>();
private final Map<String, HardwareProperty> mHardwareMap;
private final Map<String, String> mProperties = new HashMap<String, String>();
// a list of user-edited properties.
private final ArrayList<String> mEditedProperties = new ArrayList<String>();
private Text mAvdName;
private Combo mTargetCombo;
private Text mSdCard;
private Button mSdCardSizeRadio;
private Text mSdCardSize;
private Combo mSdCardSizeCombo;
private Text mSdCardFile;
private Button mBrowseSdCard;
private Button mSdCardFileRadio;
private Button mSkinListRadio;
private Combo mSkinCombo;
private Button mSkinSizeRadio;
private Text mSkinSizeWidth;
private Text mSkinSizeHeight;
private TableViewer mHardwareViewer;
private Button mDeleteHardwareProp;
private Button mForceCreation;
private Button mOkButton;
private Label mStatusIcon;
@@ -76,12 +121,27 @@ final class AvdCreationDialog extends Dialog {
private Composite mStatusComposite;
private final ImageFactory mImageFactory;
/**
* {@link VerifyListener} for {@link Text} widgets that should only contains numbers.
*/
private final VerifyListener mDigitVerifier = new VerifyListener() {
public void verifyText(VerifyEvent event) {
int count = event.text.length();
for (int i = 0 ; i < count ; i++) {
char c = event.text.charAt(i);
if (c < '0' || c > '9') {
event.doit = false;
return;
}
}
}
};
/**
* Callback when the AVD name is changed.
* Enables the force checkbox if the name is a duplicate.
*/
private class CreateNameModifyListener implements ModifyListener {
public void modifyText(ModifyEvent e) {
String name = mAvdName.getText().trim();
AvdInfo avdMatch = mAvdManager.getAvd(name, false /*validAvdOnly*/);
@@ -96,8 +156,10 @@ final class AvdCreationDialog extends Dialog {
}
}
/**
* {@link ModifyListener} used for live-validation of the fields content.
*/
private class ValidateListener extends SelectionAdapter implements ModifyListener {
public void modifyText(ModifyEvent e) {
validatePage();
}
@@ -111,9 +173,14 @@ final class AvdCreationDialog extends Dialog {
protected AvdCreationDialog(Shell parentShell, AvdManager avdManager,
ImageFactory imageFactory) {
super(parentShell);
super(parentShell, 2, false);
mAvdManager = avdManager;
mImageFactory = imageFactory;
File hardwareDefs = new File (avdManager.getSdkManager().getLocation() + File.separator +
SdkConstants.OS_SDK_TOOLS_LIB_FOLDER, SdkConstants.FN_HARDWARE_INI);
mHardwareMap = HardwareProperties.parseHardwareDefinitions(
hardwareDefs, null /*sdkLog*/);
}
@Override
@@ -139,25 +206,22 @@ final class AvdCreationDialog extends Dialog {
}
@Override
protected Control createDialogArea(Composite parent) {
Composite top = new Composite(parent, SWT.NONE);
top.setLayoutData(new GridData(GridData.FILL_BOTH));
top.setLayout(new GridLayout(3, false));
public void createDialogContent(final Composite parent) {
GridData gd;
GridLayout gl;
Label label = new Label(top, SWT.NONE);
Label label = new Label(parent, SWT.NONE);
label.setText("Name:");
mAvdName = new Text(top, SWT.BORDER);
mAvdName.setLayoutData(new GridData(GridData.FILL, GridData.CENTER,
true, false, 2, 1));
mAvdName = new Text(parent, SWT.BORDER);
mAvdName.setLayoutData(new GridData(GridData.FILL_HORIZONTAL));
mAvdName.addModifyListener(new CreateNameModifyListener());
label = new Label(top, SWT.NONE);
label = new Label(parent, SWT.NONE);
label.setText("Target:");
mTargetCombo = new Combo(top, SWT.READ_ONLY | SWT.DROP_DOWN);
mTargetCombo.setLayoutData(new GridData(GridData.FILL, GridData.CENTER,
true, false, 2, 1));
mTargetCombo = new Combo(parent, SWT.READ_ONLY | SWT.DROP_DOWN);
mTargetCombo.setLayoutData(new GridData(GridData.FILL_HORIZONTAL));
mTargetCombo.addSelectionListener(new SelectionAdapter() {
@Override
public void widgetSelected(SelectionEvent e) {
@@ -167,18 +231,46 @@ final class AvdCreationDialog extends Dialog {
}
});
label = new Label(top, SWT.NONE);
// --- sd card group
label = new Label(parent, SWT.NONE);
label.setText("SD Card:");
label.setToolTipText("Either a path to an existing SD card image\n" +
"or an image size in K or M (e.g. 512K, 10M).");
label.setLayoutData(new GridData(GridData.BEGINNING, GridData.BEGINNING,
false, false));
final Group sdCardGroup = new Group(parent, SWT.NONE);
sdCardGroup.setLayoutData(new GridData(GridData.FILL_HORIZONTAL));
sdCardGroup.setLayout(new GridLayout(3, false));
mSdCardSizeRadio = new Button(sdCardGroup, SWT.RADIO);
mSdCardSizeRadio.setText("Size:");
mSdCardSizeRadio.addSelectionListener(new SelectionAdapter() {
@Override
public void widgetSelected(SelectionEvent arg0) {
boolean sizeMode = mSdCardSizeRadio.getSelection();
enableSdCardWidgets(sizeMode);
validatePage();
}
});
ValidateListener validateListener = new ValidateListener();
mSdCard = new Text(top, SWT.BORDER);
mSdCard.setLayoutData(new GridData(GridData.FILL_HORIZONTAL));
mSdCard.addModifyListener(validateListener);
mSdCardSize = new Text(sdCardGroup, SWT.BORDER);
mSdCardSize.setLayoutData(new GridData(GridData.FILL_HORIZONTAL));
mSdCardSize.addVerifyListener(mDigitVerifier);
mBrowseSdCard = new Button(top, SWT.PUSH);
mSdCardSizeCombo = new Combo(sdCardGroup, SWT.DROP_DOWN | SWT.READ_ONLY);
mSdCardSizeCombo.add("KiB");
mSdCardSizeCombo.add("MiB");
mSdCardSizeCombo.select(1);
mSdCardFileRadio = new Button(sdCardGroup, SWT.RADIO);
mSdCardFileRadio.setText("File:");
mSdCardFile = new Text(sdCardGroup, SWT.BORDER);
mSdCardFile.setLayoutData(new GridData(GridData.FILL_HORIZONTAL));
mSdCardFile.addModifyListener(validateListener);
mBrowseSdCard = new Button(sdCardGroup, SWT.PUSH);
mBrowseSdCard.setText("Browse...");
mBrowseSdCard.addSelectionListener(new SelectionAdapter() {
@Override
@@ -188,32 +280,128 @@ final class AvdCreationDialog extends Dialog {
}
});
label = new Label(top, SWT.NONE);
label.setText("Skin");
mSdCardSizeRadio.setSelection(true);
enableSdCardWidgets(true);
mSkinCombo = new Combo(top, SWT.READ_ONLY | SWT.DROP_DOWN);
mSkinCombo.setLayoutData(new GridData(GridData.FILL, GridData.CENTER,
true, false, 2, 1));
// --- skin group
label = new Label(parent, SWT.NONE);
label.setText("Skin:");
label.setLayoutData(new GridData(GridData.BEGINNING, GridData.BEGINNING,
false, false));
// dummies for alignment
label = new Label(top, SWT.NONE);
final Group skinGroup = new Group(parent, SWT.NONE);
skinGroup.setLayoutData(new GridData(GridData.FILL_HORIZONTAL));
skinGroup.setLayout(new GridLayout(4, false));
mForceCreation = new Button(top, SWT.CHECK);
mForceCreation.setText("Force");
mForceCreation.setLayoutData(new GridData(GridData.FILL, GridData.CENTER,
mSkinListRadio = new Button(skinGroup, SWT.RADIO);
mSkinListRadio.setText("Built-in:");
mSkinListRadio.addSelectionListener(new SelectionAdapter() {
@Override
public void widgetSelected(SelectionEvent arg0) {
boolean listMode = mSkinListRadio.getSelection();
enableSkinWidgets(listMode);
validatePage();
}
});
mSkinCombo = new Combo(skinGroup, SWT.READ_ONLY | SWT.DROP_DOWN);
mSkinCombo.setLayoutData(gd = new GridData(GridData.FILL_HORIZONTAL));
gd.horizontalSpan = 3;
mSkinCombo.addSelectionListener(new SelectionAdapter() {
@Override
public void widgetSelected(SelectionEvent arg0) {
// get the skin info
loadSkin();
}
});
mSkinSizeRadio = new Button(skinGroup, SWT.RADIO);
mSkinSizeRadio.setText("Resolution:");
mSkinSizeWidth = new Text(skinGroup, SWT.BORDER);
mSkinSizeWidth.setLayoutData(new GridData(GridData.FILL_HORIZONTAL));
mSkinSizeWidth.addVerifyListener(mDigitVerifier);
mSkinSizeWidth.addModifyListener(validateListener);
new Label(skinGroup, SWT.NONE).setText("x");
mSkinSizeHeight = new Text(skinGroup, SWT.BORDER);
mSkinSizeHeight.setLayoutData(new GridData(GridData.FILL_HORIZONTAL));
mSkinSizeHeight.addVerifyListener(mDigitVerifier);
mSkinSizeHeight.addModifyListener(validateListener);
mSkinListRadio.setSelection(true);
enableSkinWidgets(true);
// --- hardware group
label = new Label(parent, SWT.NONE);
label.setText("Hardware:");
label.setLayoutData(new GridData(GridData.BEGINNING, GridData.BEGINNING,
false, false));
final Group hwGroup = new Group(parent, SWT.NONE);
hwGroup.setLayoutData(new GridData(GridData.FILL_HORIZONTAL));
hwGroup.setLayout(new GridLayout(2, false));
createHardwareTable(hwGroup);
// composite for the side buttons
Composite hwButtons = new Composite(hwGroup, SWT.NONE);
hwButtons.setLayoutData(new GridData(GridData.FILL_VERTICAL));
hwButtons.setLayout(gl = new GridLayout(1, false));
gl.marginHeight = gl.marginWidth = 0;
Button b = new Button(hwButtons, SWT.PUSH | SWT.FLAT);
b.setText("New...");
b.setLayoutData(new GridData(GridData.FILL_HORIZONTAL));
b.addSelectionListener(new SelectionAdapter() {
@Override
public void widgetSelected(SelectionEvent event) {
HardwarePropertyChooser dialog = new HardwarePropertyChooser(parent.getShell(),
mHardwareMap, mProperties.keySet());
if (dialog.open() == Window.OK) {
HardwareProperty choice = dialog.getProperty();
if (choice != null) {
mProperties.put(choice.getName(), choice.getDefault());
mHardwareViewer.refresh();
}
}
}
});
mDeleteHardwareProp = new Button(hwButtons, SWT.PUSH | SWT.FLAT);
mDeleteHardwareProp.setText("Delete");
mDeleteHardwareProp.setLayoutData(new GridData(GridData.FILL_HORIZONTAL));
mDeleteHardwareProp.addSelectionListener(new SelectionAdapter() {
@Override
public void widgetSelected(SelectionEvent arg0) {
ISelection selection = mHardwareViewer.getSelection();
if (selection instanceof IStructuredSelection) {
String hwName = (String)((IStructuredSelection)selection).getFirstElement();
mProperties.remove(hwName);
mHardwareViewer.refresh();
}
}
});
mDeleteHardwareProp.setEnabled(false);
// --- end hardware group
mForceCreation = new Button(parent, SWT.CHECK);
mForceCreation.setText("Force create");
mForceCreation.setToolTipText("Select this to override any existing AVD");
mForceCreation.setLayoutData(new GridData(GridData.END, GridData.CENTER,
true, false, 2, 1));
mForceCreation.setEnabled(false);
mForceCreation.addSelectionListener(validateListener);
// add a separator to separate from the ok/cancel button
label = new Label(top, SWT.SEPARATOR | SWT.HORIZONTAL);
label = new Label(parent, SWT.SEPARATOR | SWT.HORIZONTAL);
label.setLayoutData(new GridData(GridData.FILL, GridData.CENTER, true, false, 3, 1));
// add stuff for the error display
mStatusComposite = new Composite(top, SWT.NONE);
mStatusComposite = new Composite(parent, SWT.NONE);
mStatusComposite.setLayoutData(new GridData(GridData.FILL, GridData.CENTER,
true, false, 3, 1));
GridLayout gl;
mStatusComposite.setLayout(gl = new GridLayout(2, false));
gl.marginHeight = gl.marginWidth = 0;
@@ -225,8 +413,141 @@ final class AvdCreationDialog extends Dialog {
mStatusLabel.setText(" \n "); //$NON-NLS-1$
reloadTargetCombo();
}
return top;
/**
* Creates the UI for the hardware properties table.
* This creates the {@link Table}, and several viewers ({@link TableViewer},
* {@link TableViewerColumn}) and adds edit support for the 2nd column
*/
private void createHardwareTable(Composite parent) {
final Table hardwareTable = new Table(parent, SWT.SINGLE | SWT.FULL_SELECTION);
GridData gd = new GridData(GridData.FILL_HORIZONTAL | GridData.FILL_VERTICAL);
gd.widthHint = 200;
gd.heightHint = 100;
hardwareTable.setLayoutData(gd);
hardwareTable.setHeaderVisible(true);
hardwareTable.setLinesVisible(true);
// -- Table viewer
mHardwareViewer = new TableViewer(hardwareTable);
mHardwareViewer.addSelectionChangedListener(new ISelectionChangedListener() {
public void selectionChanged(SelectionChangedEvent event) {
// it's a single selection mode, we can just access the selection index
// from the table directly.
mDeleteHardwareProp.setEnabled(hardwareTable.getSelectionIndex() != -1);
}
});
// only a content provider. Use viewers per column below (for editing support)
mHardwareViewer.setContentProvider(new IStructuredContentProvider() {
public void inputChanged(Viewer viewer, Object oldInput, Object newInput) {
// we can just ignore this. we just use mProperties directly.
}
public Object[] getElements(Object arg0) {
return mProperties.keySet().toArray();
}
public void dispose() {
// pass
}
});
// -- column 1: prop abstract name
TableColumn col1 = new TableColumn(hardwareTable, SWT.LEFT);
col1.setText("Property");
col1.setWidth(150);
TableViewerColumn tvc1 = new TableViewerColumn(mHardwareViewer, col1);
tvc1.setLabelProvider(new CellLabelProvider() {
@Override
public void update(ViewerCell cell) {
HardwareProperty prop = mHardwareMap.get(cell.getElement());
cell.setText(prop != null ? prop.getAbstract() : "");
}
});
// -- column 2: prop value
TableColumn col2 = new TableColumn(hardwareTable, SWT.LEFT);
col2.setText("Value");
col2.setWidth(50);
TableViewerColumn tvc2 = new TableViewerColumn(mHardwareViewer, col2);
tvc2.setLabelProvider(new CellLabelProvider() {
@Override
public void update(ViewerCell cell) {
String value = mProperties.get(cell.getElement());
cell.setText(value != null ? value : "");
}
});
// add editing support to the 2nd column
tvc2.setEditingSupport(new EditingSupport(mHardwareViewer) {
@Override
protected void setValue(Object element, Object value) {
String hardwareName = (String)element;
HardwareProperty property = mHardwareMap.get(hardwareName);
switch (property.getType()) {
case INTEGER:
mProperties.put((String)element, (String)value);
break;
case DISKSIZE:
if (HardwareProperties.DISKSIZE_PATTERN.matcher((String)value).matches()) {
mProperties.put((String)element, (String)value);
}
break;
case BOOLEAN:
int index = (Integer)value;
mProperties.put((String)element, HardwareProperties.BOOLEAN_VALUES[index]);
break;
}
mHardwareViewer.refresh(element);
}
@Override
protected Object getValue(Object element) {
String hardwareName = (String)element;
HardwareProperty property = mHardwareMap.get(hardwareName);
String value = mProperties.get(hardwareName);
switch (property.getType()) {
case INTEGER:
// intended fall-through.
case DISKSIZE:
return value;
case BOOLEAN:
return HardwareProperties.getBooleanValueIndex(value);
}
return null;
}
@Override
protected CellEditor getCellEditor(Object element) {
String hardwareName = (String)element;
HardwareProperty property = mHardwareMap.get(hardwareName);
switch (property.getType()) {
// TODO: custom TextCellEditor that restrict input.
case INTEGER:
// intended fall-through.
case DISKSIZE:
return new TextCellEditor(hardwareTable);
case BOOLEAN:
return new ComboBoxCellEditor(hardwareTable,
HardwareProperties.BOOLEAN_VALUES,
SWT.READ_ONLY | SWT.DROP_DOWN);
}
return null;
}
@Override
protected boolean canEdit(Object element) {
String hardwareName = (String)element;
HardwareProperty property = mHardwareMap.get(hardwareName);
return property != null;
}
});
mHardwareViewer.setInput(mProperties);
}
@Override
@@ -240,18 +561,44 @@ final class AvdCreationDialog extends Dialog {
@Override
protected void okPressed() {
if (onCreate()) {
if (createAvd()) {
super.okPressed();
}
}
/**
* Enable or disable the sd card widgets.
* @param sizeMode if true the size-based widgets are to be enabled, and the file-based ones
* disabled.
*/
private void enableSdCardWidgets(boolean sizeMode) {
mSdCardSize.setEnabled(sizeMode);
mSdCardSizeCombo.setEnabled(sizeMode);
mSdCardFile.setEnabled(!sizeMode);
mBrowseSdCard.setEnabled(!sizeMode);
}
/**
* Enable or disable the skin widgets.
* @param listMode if true the list-based widgets are to be enabled, and the size-based ones
* disabled.
*/
private void enableSkinWidgets(boolean listMode) {
mSkinCombo.setEnabled(listMode);
mSkinSizeWidth.setEnabled(!listMode);
mSkinSizeHeight.setEnabled(!listMode);
}
private void onBrowseSdCard() {
FileDialog dlg = new FileDialog(getContents().getShell(), SWT.OPEN);
dlg.setText("Choose SD Card image file.");
String fileName = dlg.open();
if (fileName != null) {
mSdCard.setText(fileName);
mSdCardFile.setText(fileName);
}
}
@@ -335,6 +682,7 @@ final class AvdCreationDialog extends Dialog {
mSkinCombo.select(index);
} else {
mSkinCombo.select(0); // default
loadSkin();
}
}
}
@@ -363,12 +711,29 @@ final class AvdCreationDialog extends Dialog {
// Validate SDCard path or value
if (error == null) {
String sdName = mSdCard.getText().trim();
// get the mode. We only need to check the file since the
// verifier on the size Text will prevent invalid input
boolean sdcardFileMode = mSdCardFileRadio.getSelection();
if (sdcardFileMode) {
String sdName = mSdCardFile.getText().trim();
if (sdName.length() > 0 && !new File(sdName).isFile()) {
error = "SD Card path isn't valid.";
}
}
}
if (sdName.length() > 0 &&
!new File(sdName).isFile() &&
!AvdManager.SDCARD_SIZE_PATTERN.matcher(sdName).matches()) {
error = "SD Card must be either a file path or a size\nsuch as 128K or 64M.";
// validate the skin
if (error == null) {
// get the mode, we should only check if it's in size mode since
// the built-in list mode is always valid.
if (mSkinSizeRadio.getSelection()) {
// need both with and heigh to be non null
String width = mSkinSizeWidth.getText(); // no need for trim, since the verifier
String height = mSkinSizeHeight.getText(); // rejects non digit.
if (width.length() == 0 || height.length() == 0) {
error = "Skin size is incorrect.\nBoth dimensions must be > 0";
}
}
}
@@ -378,7 +743,7 @@ final class AvdCreationDialog extends Dialog {
if (avdMatch != null && !mForceCreation.getSelection()) {
error = String.format(
"The AVD name '%s' is already used.\n" +
"Check \"Force\" to override existing AVD.",
"Check \"Force create\" to override existing AVD.",
avdName);
}
}
@@ -402,30 +767,135 @@ final class AvdCreationDialog extends Dialog {
mStatusComposite.pack(true);
}
private void loadSkin() {
int targetIndex = mTargetCombo.getSelectionIndex();
if (targetIndex < 0) {
return;
}
// resolve the target.
String targetName = mTargetCombo.getItem(targetIndex);
IAndroidTarget target = mCurrentTargets.get(targetName);
if (target == null) {
return;
}
// get the skin name
String skinName = null;
int skinIndex = mSkinCombo.getSelectionIndex();
if (skinIndex < 0) {
return;
} else if (skinIndex == 0) { // default skin for the target
skinName = target.getDefaultSkin();
} else {
skinName = mSkinCombo.getItem(skinIndex);
}
// load the skin properties
String path = target.getPath(IAndroidTarget.SKINS);
File skin = new File(path, skinName);
if (skin.isDirectory() == false && target.isPlatform() == false) {
// it's possible the skin is in the parent target
path = target.getParent().getPath(IAndroidTarget.SKINS);
skin = new File(path, skinName);
}
if (skin.isDirectory() == false) {
return;
}
// now get the hardware.ini from the add-on (if applicable) and from the skin
// (if applicable)
HashMap<String, String> hardwareValues = new HashMap<String, String>();
if (target.isPlatform() == false) {
File targetHardwareFile = new File(target.getLocation(), AvdManager.HARDWARE_INI);
if (targetHardwareFile.isFile()) {
Map<String, String> targetHardwareConfig = SdkManager.parsePropertyFile(
targetHardwareFile, null /*log*/);
if (targetHardwareConfig != null) {
hardwareValues.putAll(targetHardwareConfig);
}
}
}
// from the skin
File skinHardwareFile = new File(skin, AvdManager.HARDWARE_INI);
if (skinHardwareFile.isFile()) {
Map<String, String> skinHardwareConfig = SdkManager.parsePropertyFile(
skinHardwareFile, null /*log*/);
if (skinHardwareConfig != null) {
hardwareValues.putAll(skinHardwareConfig);
}
}
// now set those values in the list of properties for the AVD.
// We just check that none of those properties have been edited by the user yet.
for (Entry<String, String> entry : hardwareValues.entrySet()) {
if (mEditedProperties.contains(entry.getKey()) == false) {
mProperties.put(entry.getKey(), entry.getValue());
}
}
mHardwareViewer.refresh();
}
/**
* Creates an AVD from the values in the UI. Called when the user presses the OK button.
*/
private boolean onCreate() {
private boolean createAvd() {
String avdName = mAvdName.getText().trim();
String sdName = mSdCard.getText().trim();
int targetIndex = mTargetCombo.getSelectionIndex();
int skinIndex = mSkinCombo.getSelectionIndex();
boolean force = mForceCreation.getSelection();
// quick check on the name and the target selection
if (avdName.length() == 0 || targetIndex < 0) {
return false;
}
// resolve the target.
String targetName = mTargetCombo.getItem(targetIndex);
IAndroidTarget target = mCurrentTargets.get(targetName);
if (target == null) {
return false;
}
// get the SD card data from the UI.
String sdName = null;
if (mSdCardSizeRadio.getSelection()) {
// size mode
String value = mSdCardSize.getText().trim();
if (value.length() > 0) {
sdName = value;
// add the unit
switch (mSdCardSizeCombo.getSelectionIndex()) {
case 0:
sdName += "K";
break;
case 1:
sdName += "M";
break;
default:
// shouldn't be here
assert false;
}
}
} else {
// file mode.
sdName = mSdCardFile.getText().trim();
}
// get the Skin data from the UI
String skinName = null;
if (skinIndex > 0) {
// index 0 is the default, we don't use it
skinName = mSkinCombo.getItem(skinIndex);
if (mSkinListRadio.getSelection()) {
// built-in list provides the skin
int skinIndex = mSkinCombo.getSelectionIndex();
if (skinIndex > 0) {
// index 0 is the default, we don't use it
skinName = mSkinCombo.getItem(skinIndex);
}
} else {
// size mode. get both size and writes it as a skin name
// thanks to validatePage() we know the content of the fields is correct
skinName = mSkinSizeWidth.getText() + "x" + mSkinSizeHeight.getText(); //$NON-NLS-1$
}
SdkLog log = new SdkLog(String.format("Result of creating AVD '%s':", avdName),
@@ -440,6 +910,8 @@ final class AvdCreationDialog extends Dialog {
return false;
}
boolean force = mForceCreation.getSelection();
boolean success = false;
AvdInfo avdInfo = mAvdManager.createAvd(
avdFolder,
@@ -447,14 +919,13 @@ final class AvdCreationDialog extends Dialog {
target,
skinName,
sdName,
null, // hardwareConfig,
mProperties,
force,
log);
success = avdInfo != null;
log.displayResult(success);
return success;
}
}

View File

@@ -26,6 +26,7 @@ import com.android.sdklib.internal.avd.AvdManager.AvdInfo;
import com.android.sdklib.internal.avd.AvdManager.AvdInfo.AvdStatus;
import com.android.sdklib.internal.repository.ITask;
import com.android.sdklib.internal.repository.ITaskMonitor;
import com.android.sdkuilib.internal.repository.SettingsController;
import com.android.sdkuilib.internal.repository.icons.ImageFactory;
import com.android.sdkuilib.internal.tasks.ProgressTask;
import com.android.sdkuilib.repository.UpdaterWindow;
@@ -95,6 +96,8 @@ public final class AvdSelector {
private Image mOkImage;
private Image mBrokenImage;
private SettingsController mController;
/**
* The display mode of the AVD Selector.
@@ -380,6 +383,14 @@ public final class AvdSelector {
DisplayMode displayMode) {
this(parent, osSdkPath, manager, new TargetBasedFilter(filter), displayMode);
}
/**
* Sets an optional SettingsController.
* @param controller the controller.
*/
public void setSettingsController(SettingsController controller) {
mController = controller;
}
/**
* Sets the table grid layout data.
*
@@ -898,52 +909,70 @@ public final class AvdSelector {
return;
}
String path = mOsSdkPath +
File.separator +
SdkConstants.OS_SDK_TOOLS_FOLDER +
SdkConstants.FN_EMULATOR;
AvdStartDialog dialog = new AvdStartDialog(mTable.getShell(), avdInfo, mOsSdkPath,
mController);
if (dialog.open() == Window.OK) {
String path = mOsSdkPath +
File.separator +
SdkConstants.OS_SDK_TOOLS_FOLDER +
SdkConstants.FN_EMULATOR;
final String avdName = avdInfo.getName();
final String avdName = avdInfo.getName();
// build the command line based on the available parameters.
ArrayList<String> list = new ArrayList<String>();
list.add(path);
list.add("-avd"); //$NON-NLS-1$
list.add(avdName);
// build the command line based on the available parameters.
ArrayList<String> list = new ArrayList<String>();
list.add(path);
list.add("-avd"); //$NON-NLS-1$
list.add(avdName);
if (dialog.getWipeData()) {
list.add("-wipe-data"); //$NON-NLS-1$
}
float scale = dialog.getScale();
if (scale != 0.f) {
// do the rounding ourselves. This is because %.1f will write .4899 as .4
scale = Math.round(scale * 100);
scale /= 100.f;
list.add("-scale"); //$NON-NLS-1$
list.add(String.format("%.2f", scale)); //$NON-NLS-1$
}
// convert the list into an array for the call to exec.
final String[] command = list.toArray(new String[list.size()]);
// convert the list into an array for the call to exec.
final String[] command = list.toArray(new String[list.size()]);
// launch the emulator
new ProgressTask(mTable.getShell(),
"Starting Android Emulator",
new ITask() {
public void run(ITaskMonitor monitor) {
try {
monitor.setDescription("Starting emulator for AVD '%1$s'", avdName);
int n = 10;
monitor.setProgressMax(n);
Process process = Runtime.getRuntime().exec(command);
grabEmulatorOutput(process, monitor);
// launch the emulator
new ProgressTask(mTable.getShell(),
"Starting Android Emulator",
new ITask() {
public void run(ITaskMonitor monitor) {
try {
monitor.setDescription("Starting emulator for AVD '%1$s'",
avdName);
int n = 10;
monitor.setProgressMax(n);
Process process = Runtime.getRuntime().exec(command);
grabEmulatorOutput(process, monitor);
// This small wait prevents the dialog from closing too fast:
// When it works, the emulator returns immediately, even if no UI
// is shown yet. And when it fails (because the AVD is locked/running)
// if we don't have a wait we don't capture the error for some reason.
for (int i = 0; i < n; i++) {
try {
Thread.sleep(100);
monitor.incProgress(1);
} catch (InterruptedException e) {
// ignore
// This small wait prevents the dialog from closing too fast:
// When it works, the emulator returns immediately, even if
// no UI is shown yet. And when it fails (because the AVD is
// locked/running)
// if we don't have a wait we don't capture the error for
// some reason.
for (int i = 0; i < n; i++) {
try {
Thread.sleep(100);
monitor.incProgress(1);
} catch (InterruptedException e) {
// ignore
}
}
} catch (IOException e) {
monitor.setResult("Failed to start emulator: %1$s",
e.getMessage());
}
} catch (IOException e) {
monitor.setResult("Failed to start emulator: %1$s", e.getMessage());
}
}
});
});
}
}
/**

View File

@@ -0,0 +1,523 @@
/*
* Copyright (C) 2009 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.sdkuilib.internal.widgets;
import com.android.sdklib.internal.avd.AvdManager;
import com.android.sdklib.internal.avd.AvdManager.AvdInfo;
import com.android.sdkuilib.internal.repository.SettingsController;
import com.android.sdkuilib.ui.GridDialog;
import org.eclipse.jface.dialogs.IDialogConstants;
import org.eclipse.jface.window.Window;
import org.eclipse.swt.SWT;
import org.eclipse.swt.events.ModifyEvent;
import org.eclipse.swt.events.ModifyListener;
import org.eclipse.swt.events.SelectionAdapter;
import org.eclipse.swt.events.SelectionEvent;
import org.eclipse.swt.events.VerifyEvent;
import org.eclipse.swt.events.VerifyListener;
import org.eclipse.swt.layout.GridData;
import org.eclipse.swt.layout.GridLayout;
import org.eclipse.swt.widgets.Button;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Control;
import org.eclipse.swt.widgets.Group;
import org.eclipse.swt.widgets.Label;
import org.eclipse.swt.widgets.Shell;
import org.eclipse.swt.widgets.Text;
import java.awt.Toolkit;
import java.io.BufferedReader;
import java.io.File;
import java.io.FileReader;
import java.io.IOException;
import java.util.HashMap;
import java.util.Map;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
/**
* Dialog dealing with emulator launch options. The following options are supported:
* <ul>
* <li>-wipe-data</li>
* <li>-scale</li>
* </ul>
*
* Values are stored (in the class as static field) to be reused while the app is still running.
* The Monitor dpi is stored in the settings if availabe.
*/
final class AvdStartDialog extends GridDialog {
// static field to reuse values during the same session.
private static boolean sWipeData = false;
private static int sMonitorDpi = 72; // used if there's no setting controller.
private static final Map<String, String> sSkinScaling = new HashMap<String, String>();
private static final Pattern sScreenSizePattern = Pattern.compile("\\d*(\\.\\d?)?");
private final AvdInfo mAvd;
private final String mSdkLocation;
private final SettingsController mSettingsController;
private Text mScreenSize;
private Text mMonitorDpi;
private Button mScaleButton;
private float mScale = 0.f;
private boolean mWipeData = false;
private int mDensity = 160; // medium density
private int mSize1 = -1;
private int mSize2 = -1;
private String mSkinDisplay;
private boolean mEnableScaling = true;
private Label mScaleField;
AvdStartDialog(Shell parentShell, AvdInfo avd, String sdkLocation,
SettingsController settingsController) {
super(parentShell, 2, false);
mAvd = avd;
mSdkLocation = sdkLocation;
mSettingsController = settingsController;
if (mAvd == null) {
throw new IllegalArgumentException("avd cannot be null");
}
if (mSdkLocation == null) {
throw new IllegalArgumentException("sdkLocation cannot be null");
}
computeSkinData();
}
public boolean getWipeData() {
return mWipeData;
}
/**
* Returns the scaling factor, or 0.f if none are set.
*/
public float getScale() {
return mScale;
}
@Override
public void createDialogContent(final Composite parent) {
GridData gd;
Label l = new Label(parent, SWT.NONE);
l.setText("Skin:");
l = new Label(parent, SWT.NONE);
l.setText(mSkinDisplay == null ? "None" : mSkinDisplay);
l.setLayoutData(new GridData(GridData.FILL_HORIZONTAL));
l = new Label(parent, SWT.NONE);
l.setText("Density:");
l = new Label(parent, SWT.NONE);
l.setText(getDensityText());
l.setLayoutData(new GridData(GridData.FILL_HORIZONTAL));
mScaleButton = new Button(parent, SWT.CHECK);
mScaleButton.setText("Scale display to real size");
mScaleButton.setEnabled(mEnableScaling);
boolean defaultState = mEnableScaling && sSkinScaling.get(mAvd.getName()) != null;
mScaleButton.setSelection(defaultState);
mScaleButton.setLayoutData(gd = new GridData(GridData.FILL_HORIZONTAL));
gd.horizontalSpan = 2;
final Group scaleGroup = new Group(parent, SWT.NONE);
scaleGroup.setLayoutData(gd = new GridData(GridData.FILL_HORIZONTAL));
gd.horizontalIndent = 30;
gd.horizontalSpan = 2;
scaleGroup.setLayout(new GridLayout(3, false));
l = new Label(scaleGroup, SWT.NONE);
l.setText("Screen Size (in):");
mScreenSize = new Text(scaleGroup, SWT.BORDER);
mScreenSize.setText(getScreenSize());
mScreenSize.setLayoutData(new GridData(GridData.FILL_HORIZONTAL));
mScreenSize.addVerifyListener(new VerifyListener() {
public void verifyText(VerifyEvent event) {
// combine the current content and the new text
String text = mScreenSize.getText();
text = text.substring(0, event.start) + event.text + text.substring(event.end);
// now make sure it's a match for the regex
event.doit = sScreenSizePattern.matcher(text).matches();
}
});
mScreenSize.addModifyListener(new ModifyListener() {
public void modifyText(ModifyEvent event) {
onScaleChange();
}
});
// empty composite, only 2 widgets on this line.
new Composite(scaleGroup, SWT.NONE).setLayoutData(gd = new GridData());
gd.widthHint = gd.heightHint = 0;
l = new Label(scaleGroup, SWT.NONE);
l.setText("Monitor dpi:");
mMonitorDpi = new Text(scaleGroup, SWT.BORDER);
mMonitorDpi.setText(Integer.toString(getMonitorDpi()));
mMonitorDpi.setLayoutData(gd = new GridData(GridData.FILL_HORIZONTAL));
gd.widthHint = 50;
mMonitorDpi.addVerifyListener(new VerifyListener() {
public void verifyText(VerifyEvent event) {
// check for digit only.
for (int i = 0 ; i < event.text.length(); i++) {
char letter = event.text.charAt(i);
if (letter < '0' || letter > '9') {
event.doit = false;
return;
}
}
}
});
mMonitorDpi.addModifyListener(new ModifyListener() {
public void modifyText(ModifyEvent event) {
onScaleChange();
}
});
Button button = new Button(scaleGroup, SWT.PUSH | SWT.FLAT);
button.setText("?");
button.setToolTipText("Click to figure out your monitor's pixel density");
button.addSelectionListener(new SelectionAdapter() {
@Override
public void widgetSelected(SelectionEvent arg0) {
ResolutionChooserDialog dialog = new ResolutionChooserDialog(parent.getShell());
if (dialog.open() == Window.OK) {
mMonitorDpi.setText(Integer.toString(dialog.getDensity()));
}
}
});
l = new Label(scaleGroup, SWT.NONE);
l.setText("Scale:");
mScaleField = new Label(scaleGroup, SWT.NONE);
mScaleField.setLayoutData(new GridData(GridData.FILL, GridData.CENTER,
true /*grabExcessHorizontalSpace*/,
true /*grabExcessVerticalSpace*/,
2 /*horizontalSpan*/,
1 /*verticalSpan*/));
setScale(mScale); // set initial text value
enableGroup(scaleGroup, defaultState);
mScaleButton.addSelectionListener(new SelectionAdapter() {
@Override
public void widgetSelected(SelectionEvent event) {
boolean enabled = mScaleButton.getSelection();
enableGroup(scaleGroup, enabled);
if (enabled) {
onScaleChange();
} else {
setScale(0);
}
}
});
final Button wipeButton = new Button(parent, SWT.CHECK);
wipeButton.setLayoutData(gd = new GridData(GridData.FILL_HORIZONTAL));
gd.horizontalSpan = 2;
wipeButton.setText("Wipe user data");
wipeButton.setSelection(mWipeData = sWipeData);
wipeButton.addSelectionListener(new SelectionAdapter() {
@Override
public void widgetSelected(SelectionEvent arg0) {
mWipeData = wipeButton.getSelection();
}
});
l = new Label(parent, SWT.SEPARATOR | SWT.HORIZONTAL);
l.setLayoutData(gd = new GridData(GridData.FILL_HORIZONTAL));
gd.horizontalSpan = 2;
// if the scaling is enabled by default, we must initialize the value of mScale
if (defaultState) {
onScaleChange();
}
}
/** On Windows we need to manually enable/disable the children of a group */
private void enableGroup(final Group group, boolean enabled) {
group.setEnabled(enabled);
for (Control c : group.getChildren()) {
c.setEnabled(enabled);
}
}
@Override
protected void configureShell(Shell newShell) {
super.configureShell(newShell);
newShell.setText("Launch Options");
}
@Override
protected Button createButton(Composite parent, int id, String label, boolean defaultButton) {
if (id == IDialogConstants.OK_ID) {
label = "Launch";
}
return super.createButton(parent, id, label, defaultButton);
}
@Override
protected void okPressed() {
// override ok to store some info
// first the monitor dpi
String dpi = mMonitorDpi.getText();
if (dpi.length() > 0) {
sMonitorDpi = Integer.parseInt(dpi);
// if there is a setting controller, save it
if (mSettingsController != null) {
mSettingsController.setMonitorDensity(sMonitorDpi);
mSettingsController.saveSettings();
}
}
// now the scale factor
String key = mAvd.getName();
sSkinScaling.remove(key);
if (mScaleButton.getSelection()) {
String size = mScreenSize.getText();
if (size.length() > 0) {
sSkinScaling.put(key, size);
}
}
// and then the wipe-data checkbox
sWipeData = mWipeData;
// finally continue with the ok action
super.okPressed();
}
private void computeSkinData() {
Map<String, String> prop = mAvd.getProperties();
String dpi = prop.get("hw.lcd.density");
if (dpi != null && dpi.length() > 0) {
mDensity = Integer.parseInt(dpi);
}
findSkinResolution();
}
private void onScaleChange() {
String sizeStr = mScreenSize.getText();
if (sizeStr.length() == 0) {
setScale(0);
return;
}
String dpiStr = mMonitorDpi.getText();
if (dpiStr.length() == 0) {
setScale(0);
return;
}
int dpi = Integer.parseInt(dpiStr);
float size = Float.parseFloat(sizeStr);
/*
* We are trying to emulate the following device:
* resolution: 'mSize1'x'mSize2'
* density: 'mDensity'
* screen diagonal: 'size'
* ontop a monitor running at 'dpi'
*/
// We start by computing the screen diagonal in pixels, if the density was really mDensity
float diagonalPx = (float)Math.sqrt(mSize1*mSize1+mSize2*mSize2);
// Now we would convert this in actual inches:
// diagonalIn = diagonal / mDensity
// the scale factor is a mix of adapting to the new density and to the new size.
// (size/diagonalIn) * (dpi/mDensity)
// this can be simplified to:
setScale((size * dpi) / diagonalPx);
}
private void setScale(float scale) {
mScale = scale;
// Do the rounding exactly like AvdSelector will do.
scale = Math.round(scale * 100);
scale /= 100.f;
if (scale == 0.f) {
mScaleField.setText("default"); //$NON-NLS-1$
} else {
mScaleField.setText(String.format("%.2f", scale)); //$NON-NLS-1$
}
}
/**
* Returns the monitor dpi to start with.
* This can be coming from the settings, the session-based storage, or the from whatever Java
* can tell us.
*/
private int getMonitorDpi() {
if (mSettingsController != null) {
sMonitorDpi = mSettingsController.getMonitorDensity();
}
if (sMonitorDpi == -1) { // first time? try to get a value
sMonitorDpi = Toolkit.getDefaultToolkit().getScreenResolution();
}
return sMonitorDpi;
}
/**
* Returns the screen size to start with.
* <p/>If an emulator with the same skin was already launched, scaled, the size used is reused.
* <p/>Otherwise the default is returned (3)
*/
private String getScreenSize() {
String size = sSkinScaling.get(mAvd.getName());
if (size != null) {
return size;
}
return "3";
}
/**
* Returns a display string for the density.
*/
private String getDensityText() {
switch (mDensity) {
case 120:
return "Low (120)";
case 160:
return "Medium (160)";
case 240:
return "High (240)";
}
return Integer.toString(mDensity);
}
/**
* Finds the skin resolution and sets it in {@link #mSize1} and {@link #mSize2}.
*/
private void findSkinResolution() {
Map<String, String> prop = mAvd.getProperties();
String skinName = prop.get(AvdManager.AVD_INI_SKIN_NAME);
if (skinName != null) {
Matcher m = AvdManager.NUMERIC_SKIN_SIZE.matcher(skinName);
if (m != null && m.matches()) {
mSize1 = Integer.parseInt(m.group(1));
mSize2 = Integer.parseInt(m.group(2));
mSkinDisplay = skinName;
mEnableScaling = true;
return;
}
}
// The resolution is inside the layout file of the skin.
mEnableScaling = false; // default to false for now.
// path to the skin layout file.
String skinPath = prop.get(AvdManager.AVD_INI_SKIN_PATH);
if (skinPath != null) {
File skinFolder = new File(mSdkLocation, skinPath);
if (skinFolder.isDirectory()) {
File layoutFile = new File(skinFolder, "layout");
if (layoutFile.isFile()) {
if (parseLayoutFile(layoutFile)) {
mSkinDisplay = String.format("%1$s (%2$dx%3$d)", skinName, mSize1, mSize2);
mEnableScaling = true;
} else {
mSkinDisplay = skinName;
}
}
}
}
}
/**
* Parses a layout file.
* <p/>
* the format is relatively easy. It's a collection of items defined as
* &lg;name&gt; {
* &lg;content&gt;
* }
*
* content is either 1+ items or 1+ properties
* properties are defined as
* &lg;name&gt;&lg;whitespace&gt;&lg;value&gt;
*
* We're going to look for an item called display, with 2 properties height and width.
* This is very basic parser.
*
* @param layoutFile the file to parse
* @return true if both sizes where found.
*/
private boolean parseLayoutFile(File layoutFile) {
try {
BufferedReader input = new BufferedReader(new FileReader(layoutFile));
String line;
while ((line = input.readLine()) != null) {
// trim to remove whitespace
line = line.trim();
int len = line.length();
if (len == 0) continue;
// check if this is a new item
if (line.charAt(len-1) == '{') {
// this is the start of a node
String[] tokens = line.split(" ");
if ("display".equals(tokens[0])) {
// this is the one we're looking for!
while ((mSize1 == -1 || mSize2 == -1) &&
(line = input.readLine()) != null) {
// trim to remove whitespace
line = line.trim();
len = line.length();
if (len == 0) continue;
if ("}".equals(line)) { // looks like we're done with the item.
break;
}
tokens = line.split(" ");
if (tokens.length >= 2) {
// there can be multiple space between the name and value
// in which case we'll get an extra empty token in the middle.
if ("width".equals(tokens[0])) {
mSize1 = Integer.parseInt(tokens[tokens.length-1]);
} else if ("height".equals(tokens[0])) {
mSize2 = Integer.parseInt(tokens[tokens.length-1]);
}
}
}
return mSize1 != -1 && mSize2 != -1;
}
}
}
// if it reaches here, display was not found.
// false is returned below.
} catch (IOException e) {
// ignore.
}
return false;
}
}

View File

@@ -0,0 +1,119 @@
/*
* Copyright (C) 2009 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.sdkuilib.internal.widgets;
import com.android.sdklib.internal.avd.HardwareProperties.HardwareProperty;
import com.android.sdkuilib.ui.GridDialog;
import org.eclipse.swt.SWT;
import org.eclipse.swt.events.SelectionAdapter;
import org.eclipse.swt.events.SelectionEvent;
import org.eclipse.swt.widgets.Combo;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Label;
import org.eclipse.swt.widgets.Shell;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Map;
import java.util.Map.Entry;
/**
* Dialog to choose a hardware property
*/
class HardwarePropertyChooser extends GridDialog {
private final Map<String, HardwareProperty> mProperties;
private final Collection<String> mExceptProperties;
private HardwareProperty mChosenProperty;
private Label mTypeLabel;
private Label mDescriptionLabel;
HardwarePropertyChooser(Shell parentShell, Map<String, HardwareProperty> properties,
Collection<String> exceptProperties) {
super(parentShell, 2, false);
mProperties = properties;
mExceptProperties = exceptProperties;
}
public HardwareProperty getProperty() {
return mChosenProperty;
}
@Override
public void createDialogContent(Composite parent) {
Label l = new Label(parent, SWT.NONE);
l.setText("Property:");
final Combo c = new Combo(parent, SWT.DROP_DOWN | SWT.READ_ONLY);
// simple list for index->name resolution.
final ArrayList<String> indexToName = new ArrayList<String>();
for (Entry<String, HardwareProperty> entry : mProperties.entrySet()) {
if (mExceptProperties.contains(entry.getKey()) == false) {
c.add(entry.getValue().getAbstract());
indexToName.add(entry.getKey());
}
}
boolean hasValues = true;
if (indexToName.size() == 0) {
hasValues = false;
c.add("No properties");
c.select(0);
c.setEnabled(false);
}
c.addSelectionListener(new SelectionAdapter() {
@Override
public void widgetSelected(SelectionEvent event) {
int index = c.getSelectionIndex();
String name = indexToName.get(index);
processSelection(name, true /* pack */);
}
});
l = new Label(parent, SWT.NONE);
l.setText("Type:");
mTypeLabel = new Label(parent, SWT.NONE);
l = new Label(parent, SWT.NONE);
l.setText("Description:");
mDescriptionLabel = new Label(parent, SWT.NONE);
if (hasValues) {
c.select(0);
processSelection(indexToName.get(0), false /* pack */);
}
}
private void processSelection(String name, boolean pack) {
mChosenProperty = mProperties.get(name);
mTypeLabel.setText(mChosenProperty.getType().getValue());
String desc = mChosenProperty.getDescription();
if (desc != null) {
mDescriptionLabel.setText(mChosenProperty.getDescription());
} else {
mDescriptionLabel.setText("N/A");
}
if (pack) {
getShell().pack();
}
}
}

View File

@@ -0,0 +1,123 @@
/*
* Copyright (C) 2009 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.sdkuilib.internal.widgets;
import com.android.sdkuilib.ui.GridDialog;
import org.eclipse.jface.dialogs.IDialogConstants;
import org.eclipse.swt.SWT;
import org.eclipse.swt.events.SelectionAdapter;
import org.eclipse.swt.events.SelectionEvent;
import org.eclipse.swt.graphics.Rectangle;
import org.eclipse.swt.widgets.Button;
import org.eclipse.swt.widgets.Combo;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Control;
import org.eclipse.swt.widgets.Display;
import org.eclipse.swt.widgets.Label;
import org.eclipse.swt.widgets.Monitor;
import org.eclipse.swt.widgets.Shell;
/**
* Small dialog to let a user choose a screen size (from a fixed list) and a resolution
* (as returned by {@link Display#getMonitors()}).
* After the dialog as returned, one can query {@link #getDensity()} to get the chosen monitor
* pixel density.
*/
class ResolutionChooserDialog extends GridDialog {
public final static float[] MONITOR_SIZES = new float[] {
13.3f, 14, 15.4f, 15.6f, 17, 19, 20, 21, 24, 30,
};
private Button mButton;
private Combo mScreenSizeCombo;
private Combo mMonitorCombo;
private Monitor[] mMonitors;
private int mScreenSizeIndex = -1;
private int mMonitorIndex = 0;
ResolutionChooserDialog(Shell parentShell) {
super(parentShell, 2, false);
}
/**
* Returns the pixel density of the user-chosen monitor.
*/
int getDensity() {
float size = MONITOR_SIZES[mScreenSizeIndex];
Rectangle rect = mMonitors[mMonitorIndex].getBounds();
// compute the density
double d = Math.sqrt(rect.width * rect.width + rect.height * rect.height) / size;
return (int)Math.round(d);
}
@Override
protected void configureShell(Shell newShell) {
newShell.setText("Monitor Density");
super.configureShell(newShell);
}
@Override
protected Control createContents(Composite parent) {
Control control = super.createContents(parent);
mButton = getButton(IDialogConstants.OK_ID);
mButton.setEnabled(false);
return control;
}
@Override
public void createDialogContent(Composite parent) {
Label l = new Label(parent, SWT.NONE);
l.setText("Screen Size:");
mScreenSizeCombo = new Combo(parent, SWT.DROP_DOWN | SWT.READ_ONLY);
for (float size : MONITOR_SIZES) {
if (Math.round(size) == size) {
mScreenSizeCombo.add(String.format("%.0f\"", size));
} else {
mScreenSizeCombo.add(String.format("%.1f\"", size));
}
}
mScreenSizeCombo.addSelectionListener(new SelectionAdapter() {
@Override
public void widgetSelected(SelectionEvent arg0) {
mScreenSizeIndex = mScreenSizeCombo.getSelectionIndex();
mButton.setEnabled(mScreenSizeIndex != -1);
}
});
l = new Label(parent, SWT.NONE);
l.setText("Resolution:");
mMonitorCombo = new Combo(parent, SWT.DROP_DOWN | SWT.READ_ONLY);
mMonitors = parent.getDisplay().getMonitors();
for (Monitor m : mMonitors) {
Rectangle r = m.getBounds();
mMonitorCombo.add(String.format("%d x %d", r.width, r.height));
}
mMonitorCombo.select(mMonitorIndex);
mMonitorCombo.addSelectionListener(new SelectionAdapter() {
@Override
public void widgetDefaultSelected(SelectionEvent arg0) {
mMonitorIndex = mMonitorCombo.getSelectionIndex();
}
});
}
}

View File

@@ -69,6 +69,25 @@ public class UpdaterWindow {
mWindow.registerExtraPage(title, pageClass);
}
/**
* Indicate the initial page that should be selected when the window opens.
* <p/>
* This must be called before the call to {@link #open()}.
* If null or if the page class is not found, the first page will be selected.
*/
public void setInitialPage(Class<? extends Composite> pageClass) {
mWindow.setInitialPage(pageClass);
}
/**
* Sets whether the auto-update wizard will be shown when opening the window.
* <p/>
* This must be called before the call to {@link #open()}.
*/
public void setRequestAutoUpdate(boolean requestAutoUpdate) {
mWindow.setRequestAutoUpdate(requestAutoUpdate);
}
/**
* Adds a new listener to be notified when a change is made to the content of the SDK.
*/

View File

@@ -0,0 +1,78 @@
/*
* Copyright (C) 2009 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.sdkuilib.ui;
import org.eclipse.jface.dialogs.Dialog;
import org.eclipse.jface.dialogs.IDialogConstants;
import org.eclipse.swt.SWT;
import org.eclipse.swt.layout.GridData;
import org.eclipse.swt.layout.GridLayout;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Control;
import org.eclipse.swt.widgets.Shell;
/**
* jface-based dialog that properly sets up a {@link GridLayout} top composite with the proper
* margin.
*
* Implementing dialog must create the content of the dialog in
* {@link #createDialogContent(Composite)}.
*
*/
public abstract class GridDialog extends Dialog {
private final int mNumColumns;
private final boolean mMakeColumnsEqualWidth;
/**
* Creates the dialog
* @param parentShell the parent {@link Shell}.
* @param numColumns the number of columns in the grid
* @param makeColumnsEqualWidth whether or not the columns will have equal width
*/
public GridDialog(Shell parentShell, int numColumns, boolean makeColumnsEqualWidth) {
super(parentShell);
mNumColumns = numColumns;
mMakeColumnsEqualWidth = makeColumnsEqualWidth;
}
/**
* Creates the content of the dialog. The <var>parent</var> composite is a {@link GridLayout}
* created with the <var>numColumn</var> and <var>makeColumnsEqualWidth</var> parameters
* passed to {@link #GridDialog(Shell, int, boolean)}.
* @param parent the parent composite.
*/
public abstract void createDialogContent(Composite parent);
@Override
protected Control createDialogArea(Composite parent) {
Composite top = new Composite(parent, SWT.NONE);
GridLayout layout = new GridLayout(mNumColumns, mMakeColumnsEqualWidth);
layout.marginHeight = convertVerticalDLUsToPixels(IDialogConstants.VERTICAL_MARGIN);
layout.marginWidth = convertHorizontalDLUsToPixels(IDialogConstants.HORIZONTAL_MARGIN);
layout.verticalSpacing = convertVerticalDLUsToPixels(IDialogConstants.VERTICAL_SPACING);
layout.horizontalSpacing = convertHorizontalDLUsToPixels(
IDialogConstants.HORIZONTAL_SPACING);
top.setLayout(layout);
top.setLayoutData(new GridData(GridData.FILL_BOTH));
createDialogContent(top);
applyDialogFont(top);
return top;
}
}

View File

@@ -0,0 +1,107 @@
/*
* Copyright (C) 2009 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.sdkuilib.internal.repository;
import com.android.sdklib.internal.repository.Archive;
import com.android.sdklib.internal.repository.MockAddonPackage;
import com.android.sdklib.internal.repository.MockPlatformPackage;
import com.android.sdklib.internal.repository.MockToolPackage;
import com.android.sdklib.internal.repository.Package;
import com.android.sdklib.internal.repository.RepoSource;
import java.util.ArrayList;
import java.util.Arrays;
import junit.framework.TestCase;
public class UpdaterLogicTest extends TestCase {
private static class MockUpdaterLogic extends UpdaterLogic {
private final Package[] mRemotePackages;
public MockUpdaterLogic(Package[] remotePackages) {
mRemotePackages = remotePackages;
}
@Override
protected void fetchRemotePackages(ArrayList<Package> remotePkgs,
RepoSource[] remoteSources) {
// Ignore remoteSources and instead uses the remotePackages list given to the
// constructor.
if (mRemotePackages != null) {
remotePkgs.addAll(Arrays.asList(mRemotePackages));
}
}
}
public void testFindAddonDependency() throws Exception {
MockUpdaterLogic mul = new MockUpdaterLogic(null);
MockPlatformPackage p1 = new MockPlatformPackage(1, 1);
MockPlatformPackage p2 = new MockPlatformPackage(2, 1);
MockAddonPackage a1 = new MockAddonPackage(p1, 1);
MockAddonPackage a2 = new MockAddonPackage(p2, 2);
ArrayList<ArchiveInfo> out = new ArrayList<ArchiveInfo>();
ArrayList<Archive> selected = new ArrayList<Archive>();
ArrayList<Package> remote = new ArrayList<Package>();
// a2 depends on p2, which is not in the locals
Package[] locals = { p1, a1 };
RepoSource[] sources = null;
assertNull(mul.findPlatformDependency(a2, out, selected, remote, sources, locals));
assertEquals(0, out.size());
// p2 is now selected, and should be scheduled for install in out
Archive p2_archive = p2.getArchives()[0];
selected.add(p2_archive);
ArchiveInfo ai2 = mul.findPlatformDependency(a2, out, selected, remote, sources, locals);
assertNotNull(ai2);
assertSame(p2_archive, ai2.getNewArchive());
assertEquals(1, out.size());
assertSame(p2_archive, out.get(0).getNewArchive());
}
public void testFindPlatformDependency() throws Exception {
MockUpdaterLogic mul = new MockUpdaterLogic(null);
MockToolPackage t1 = new MockToolPackage(1);
MockToolPackage t2 = new MockToolPackage(2);
MockPlatformPackage p2 = new MockPlatformPackage(2, 1, 2);
ArrayList<ArchiveInfo> out = new ArrayList<ArchiveInfo>();
ArrayList<Archive> selected = new ArrayList<Archive>();
ArrayList<Package> remote = new ArrayList<Package>();
// p2 depends on t2, which is not locally installed
Package[] locals = { t1 };
RepoSource[] sources = null;
assertNull(mul.findToolsDependency(p2, out, selected, remote, sources, locals));
assertEquals(0, out.size());
// t2 is now selected and can be used as a dependency
Archive t2_archive = t2.getArchives()[0];
selected.add(t2_archive);
ArchiveInfo ai2 = mul.findToolsDependency(p2, out, selected, remote, sources, locals);
assertNotNull(ai2);
assertSame(t2_archive, ai2.getNewArchive());
assertEquals(1, out.size());
assertSame(t2_archive, out.get(0).getNewArchive());
}
}