import java.io.IOException;
import java.io.InputStreamReader;
import java.util.ArrayList;
+import java.util.Arrays;
import java.util.HashMap;
+import java.util.List;
import java.util.Map;
+import java.util.TreeSet;
import java.util.Map.Entry;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
private final String mPath;
private final IAndroidTarget mTarget;
private final Map<String, String> mProperties;
-
- /** Creates a new AVD info. Values are immutable.
- * @param properties */
+ private final String mError;
+
+ /**
+ * Creates a new valid AVD info. Values are immutable.
+ * <p/>
+ * Such an AVD is available and can be used.
+ * The error string is set to null.
+ *
+ * @param name The name of the AVD (for display or reference)
+ * @param path The path to the config.ini file
+ * @param target The target. Cannot be null.
+ * @param properties The property map. Cannot be null.
+ */
public AvdInfo(String name, String path, IAndroidTarget target,
Map<String, String> properties) {
+ this(name, path, target, properties, null /*error*/);
+ }
+
+ /**
+ * Creates a new <em>invalid</em> AVD info. Values are immutable.
+ * <p/>
+ * Such an AVD is not complete and cannot be used.
+ * The error string must be non-null.
+ *
+ * @param name The name of the AVD (for display or reference)
+ * @param path The path to the config.ini file
+ * @param target The target. Can be null.
+ * @param properties The property map. Can be null.
+ * @param error The error describing why this AVD is invalid. Cannot be null.
+ */
+ public AvdInfo(String name, String path, IAndroidTarget target,
+ Map<String, String> properties, String error) {
mName = name;
mPath = path;
mTarget = target;
mProperties = properties;
+ mError = error;
}
/** Returns the name of the AVD. */
return mTarget;
}
+ /** Returns the error describing why an AVD failed to load. Always null for valid AVDs. */
+ public String getError() {
+ return mError;
+ }
+
/**
* Helper method that returns the .ini {@link File} for a given AVD name.
* @throws AndroidLocationException if there's a problem getting android root directory.
}
}
- private void buildAvdList(ArrayList<AvdInfo> list) throws AndroidLocationException {
+ /**
+ * Returns a list of files that are potential AVD ini files.
+ * <p/>
+ * This lists the $HOME/.android/avd/<name>.ini files.
+ * Such files are properties file than then indicate where the AVD folder is located.
+ *
+ * @return A new {@link File} array or null. The array might be empty.
+ * @throws AndroidLocationException if there's a problem getting android root directory.
+ */
+ private File[] buildAvdFilesList() throws AndroidLocationException {
// get the Android prefs location.
String avdRoot = AndroidLocation.getFolder() + AndroidLocation.FOLDER_AVD;
- final boolean avdListDebug = System.getenv("AVD_LIST_DEBUG") != null;
- if (avdListDebug) {
- mSdkLog.printf("[AVD LIST DEBUG] AVD root: '%s'\n", avdRoot);
- }
-
// ensure folder validity.
File folder = new File(avdRoot);
if (folder.isFile()) {
- if (avdListDebug) {
- mSdkLog.printf("[AVD LIST DEBUG] AVD root is a file.\n");
- }
throw new AndroidLocationException(String.format("%s is not a valid folder.", avdRoot));
} else if (folder.exists() == false) {
- if (avdListDebug) {
- mSdkLog.printf("[AVD LIST DEBUG] AVD root folder doesn't exist, creating.\n");
- }
// folder is not there, we create it and return
folder.mkdirs();
- return;
+ return null;
}
File[] avds = folder.listFiles(new FilenameFilter() {
if (INI_NAME_PATTERN.matcher(name).matches()) {
// check it's a file and not a folder
boolean isFile = new File(parent, name).isFile();
- if (avdListDebug) {
- mSdkLog.printf("[AVD LIST DEBUG] Item '%s': %s\n",
- name, isFile ? "accepted file" : "rejected");
- }
return isFile;
}
}
});
+ return avds;
+ }
+
+ /**
+ * Computes the internal list of available AVDs.
+ * This only contains AVDs that reference the target currently available.
+ *
+ * @param list An array list that will contain the list of AVDs.
+ * @throws AndroidLocationException if there's a problem getting android root directory.
+ */
+ private void buildAvdList(ArrayList<AvdInfo> list) throws AndroidLocationException {
+
+ File[] avds = buildAvdFilesList();
+
for (File avd : avds) {
- AvdInfo info = parseAvdInfo(avd);
+ AvdInfo info = parseAvdInfo(avd, false /*acceptError*/);
if (info != null) {
list.add(info);
- if (avdListDebug) {
- mSdkLog.printf("[AVD LIST DEBUG] Added AVD '%s'\n", info.getPath());
- }
- } else if (avdListDebug) {
- mSdkLog.printf("[AVD LIST DEBUG] Failed to parse AVD '%s'\n", avd.getPath());
}
}
}
-
- private AvdInfo parseAvdInfo(File path) {
- Map<String, String> map = SdkManager.parsePropertyFile(path, mSdkLog);
-
- String avdPath = map.get(AVD_INFO_PATH);
- if (avdPath == null) {
+
+ public List<AvdInfo> getUnavailableAvdList() throws AndroidLocationException {
+ AvdInfo[] avds = getAvds();
+ File[] allAvds = buildAvdFilesList();
+ if (allAvds == null || allAvds.length == 0) {
return null;
}
+
+ TreeSet<File> list = new TreeSet<File>(Arrays.asList(allAvds));
+
+ for (AvdInfo info : avds) {
+ if (list.remove(info.getIniFile())) {
+ if (list.size() == 0) {
+ return null;
+ }
+ }
+ }
+
+ ArrayList<AvdInfo> errorAvds = new ArrayList<AvdInfo>(list.size());
+ for (File file : list) {
+ errorAvds.add(parseAvdInfo(file, true /*acceptError*/));
+ }
+ return errorAvds;
+ }
+
+ /**
+ * Parses an AVD config.ini to create an {@link AvdInfo}.
+ *
+ * @param path The path to the AVD config.ini
+ * @param acceptError When false, an AVD that fails to load will be discarded and null will be
+ * returned. When true, such an AVD will be returned with an error description.
+ * @return A new {@link AvdInfo} or null if the file is not valid or null if the AVD is not
+ * valid and acceptError is false.
+ */
+ private AvdInfo parseAvdInfo(File path, boolean acceptError) {
+ String error = null;
+ Map<String, String> map = SdkManager.parsePropertyFile(path, mSdkLog);
+
+ String avdPath = map.get(AVD_INFO_PATH);
String targetHash = map.get(AVD_INFO_TARGET);
- if (targetHash == null) {
- return null;
+
+ IAndroidTarget target = null;
+ File configIniFile = null;
+ Map<String, String> properties = null;
+
+ if (targetHash != null) {
+ target = mSdk.getTargetFromHashString(targetHash);
}
- IAndroidTarget target = mSdk.getTargetFromHashString(targetHash);
- if (target == null) {
- return null;
+ // load the avd properties.
+ if (avdPath != null) {
+ configIniFile = new File(avdPath, CONFIG_INI);
}
- // load the avd properties.
- File configIniFile = new File(avdPath, CONFIG_INI);
- Map<String, String> properties = SdkManager.parsePropertyFile(configIniFile, mSdkLog);
+ if (configIniFile != null) {
+ properties = SdkManager.parsePropertyFile(configIniFile, mSdkLog);
+ }
+ // get name
+ String name = path.getName();
Matcher matcher = INI_NAME_PATTERN.matcher(path.getName());
-
+ if (matcher.matches()) {
+ name = matcher.group(1);
+ }
+
+ if (!acceptError) {
+ if (avdPath == null ||
+ targetHash == null ||
+ target == null ||
+ configIniFile == null ||
+ properties == null) {
+ return null;
+ }
+ } else {
+ if (avdPath == null || configIniFile == null) {
+ error = String.format("Missing AVD 'path' property in %1$s", name);
+ } else if (targetHash == null) {
+ error = String.format("Missing 'target' property in %1$s", name);
+ } else if (target == null) {
+ error = String.format("Unknown 'target=%2$s' property in %1$s", name, targetHash);
+ } else if (properties == null) {
+ error = String.format("Failed to parse properties from %1$s", avdPath);
+ }
+ }
+
AvdInfo info = new AvdInfo(
- matcher.matches() ? matcher.group(1) : path.getName(), // should not happen
+ name,
avdPath,
target,
- properties);
+ properties,
+ error);
return info;
}
+ /**
+ * Writes a new AVD config.ini file from a set of properties.
+ *
+ * @param iniFile The file to generate.
+ * @param values THe properties to place in the ini file.
+ * @throws IOException if {@link FileWriter} fails to open, write or close the file.
+ */
private static void createConfigIni(File iniFile, Map<String, String> values)
throws IOException {
FileWriter writer = new FileWriter(iniFile);
}
+ /**
+ * Invokes the tool to create a new SD card image file.
+ *
+ * @param toolLocation The path to the mksdcard tool.
+ * @param size The size of the new SD Card, compatible with {@link #SDCARD_SIZE_PATTERN}.
+ * @param location The path of the new sdcard image file to generate.
+ * @param log The logger object, to report errors.
+ * @return True if the sdcard could be created.
+ */
private boolean createSdCard(String toolLocation, String size, String location, ISdkLog log) {
try {
String[] command = new String[3];