From 4600a064127295db53d6f1d384d08055cbc09531 Mon Sep 17 00:00:00 2001 From: Raphael Moll <> Date: Tue, 31 Mar 2009 12:39:09 -0700 Subject: [PATCH] AI 143754: SdkManager: list unknown AVDs and why they didn't load. BUG=1703143 Automated import of CL 143754 --- .../app/src/com/android/sdkmanager/Main.java | 21 ++- .../src/com/android/sdklib/avd/AvdManager.java | 203 ++++++++++++++++----- 2 files changed, 180 insertions(+), 44 deletions(-) diff --git a/sdkmanager/app/src/com/android/sdkmanager/Main.java b/sdkmanager/app/src/com/android/sdkmanager/Main.java index adf37ed0b..191aa9eda 100644 --- a/sdkmanager/app/src/com/android/sdkmanager/Main.java +++ b/sdkmanager/app/src/com/android/sdkmanager/Main.java @@ -459,11 +459,30 @@ class Main { mSdkLog.printf(" Sdcard: %s\n", sdcard); } } + + // Are there some unused AVDs? + List badAvds = avdManager.getUnavailableAvdList(); + + if (badAvds == null || badAvds.size() == 0) { + return; + } + + mSdkLog.printf("\nThe following Android Virtual Devices are no longer available:\n"); + boolean needSeparator = false; + for (AvdInfo info : badAvds) { + if (needSeparator) { + mSdkLog.printf("---------\n"); + } + mSdkLog.printf(" Name: %s\n", info.getName() == null ? "--" : info.getName()); + mSdkLog.printf(" Path: %s\n", info.getPath() == null ? "--" : info.getPath()); + mSdkLog.printf(" Error: %s\n", info.getError() == null ? "--" : info.getError()); + needSeparator = true; + } } catch (AndroidLocationException e) { errorAndExit(e.getMessage()); } } - + /** * Creates a new AVD. This is a text based creation with command line prompt. */ diff --git a/sdkmanager/libs/sdklib/src/com/android/sdklib/avd/AvdManager.java b/sdkmanager/libs/sdklib/src/com/android/sdklib/avd/AvdManager.java index 93577e42b..4342551ec 100644 --- a/sdkmanager/libs/sdklib/src/com/android/sdklib/avd/AvdManager.java +++ b/sdkmanager/libs/sdklib/src/com/android/sdklib/avd/AvdManager.java @@ -32,8 +32,11 @@ import java.io.FilenameFilter; 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; @@ -118,15 +121,43 @@ public final class AvdManager { private final String mPath; private final IAndroidTarget mTarget; private final Map mProperties; - - /** Creates a new AVD info. Values are immutable. - * @param properties */ + private final String mError; + + /** + * Creates a new valid AVD info. Values are immutable. + *

+ * 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 properties) { + this(name, path, target, properties, null /*error*/); + } + + /** + * Creates a new invalid AVD info. Values are immutable. + *

+ * 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 properties, String error) { mName = name; mPath = path; mTarget = target; mProperties = properties; + mError = error; } /** Returns the name of the AVD. */ @@ -144,6 +175,11 @@ public final class AvdManager { 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. @@ -634,29 +670,27 @@ public final class AvdManager { } } - private void buildAvdList(ArrayList list) throws AndroidLocationException { + /** + * Returns a list of files that are potential AVD ini files. + *

+ * This lists the $HOME/.android/avd/.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() { @@ -664,10 +698,6 @@ public final class AvdManager { 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; } @@ -675,52 +705,130 @@ public final class AvdManager { } }); + 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 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 map = SdkManager.parsePropertyFile(path, mSdkLog); - - String avdPath = map.get(AVD_INFO_PATH); - if (avdPath == null) { + + public List getUnavailableAvdList() throws AndroidLocationException { + AvdInfo[] avds = getAvds(); + File[] allAvds = buildAvdFilesList(); + if (allAvds == null || allAvds.length == 0) { return null; } + + TreeSet list = new TreeSet(Arrays.asList(allAvds)); + + for (AvdInfo info : avds) { + if (list.remove(info.getIniFile())) { + if (list.size() == 0) { + return null; + } + } + } + + ArrayList errorAvds = new ArrayList(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 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 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 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 values) throws IOException { FileWriter writer = new FileWriter(iniFile); @@ -732,6 +840,15 @@ public final class AvdManager { } + /** + * 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]; -- 2.11.0