eclair snapshot
This commit is contained in:
@@ -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>
|
||||
|
||||
@@ -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" "$@"
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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]);
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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 |
@@ -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;
|
||||
|
||||
@@ -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";
|
||||
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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()));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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/>
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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 <description> 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;
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -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()
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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 <description> 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;
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -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 <description> 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;
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
@@ -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);
|
||||
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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,
|
||||
|
||||
@@ -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 <description> 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;
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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");
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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");
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -56,6 +56,7 @@ public class AvdManagerPage extends Composite implements ISdkListener {
|
||||
mUpdaterData.getOsSdkRoot(),
|
||||
mUpdaterData.getAvdManager(),
|
||||
DisplayMode.MANAGER);
|
||||
mAvdSelector.setSettingsController(mUpdaterData.getSettingsController());
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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*/);
|
||||
}
|
||||
|
||||
|
||||
@@ -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() {
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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*/);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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*/);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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());
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -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
|
||||
* ≶name> {
|
||||
* ≶content>
|
||||
* }
|
||||
*
|
||||
* content is either 1+ items or 1+ properties
|
||||
* properties are defined as
|
||||
* ≶name>≶whitespace>≶value>
|
||||
*
|
||||
* 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;
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -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.
|
||||
*/
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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());
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user