AI 146739: am: CL 146689 Broken AVDs are now loaded by default, and we provide a way to fix one (new) type of error (wrong paths to the system image)

Original author: xav
  Merged from: //branches/cupcake/...

Automated import of CL 146739
This commit is contained in:
Xavier Ducrohet
2009-04-17 11:18:24 -07:00
committed by The Android Open Source Project
parent 268c824eb1
commit 62ca2ce538
6 changed files with 446 additions and 201 deletions

View File

@@ -387,7 +387,7 @@ public final class AndroidLaunchController implements IDebugBridgeChangeListener
// not compatible. // not compatible.
AvdInfo preferredAvd = null; AvdInfo preferredAvd = null;
if (config.mAvdName != null) { if (config.mAvdName != null) {
preferredAvd = avdManager.getAvd(config.mAvdName); preferredAvd = avdManager.getAvd(config.mAvdName, true /*validAvdOnly*/);
if (projectTarget.isCompatibleBaseFor(preferredAvd.getTarget()) == false) { if (projectTarget.isCompatibleBaseFor(preferredAvd.getTarget()) == false) {
preferredAvd = null; preferredAvd = null;
@@ -434,7 +434,7 @@ public final class AndroidLaunchController implements IDebugBridgeChangeListener
for (IDevice d : devices) { for (IDevice d : devices) {
String deviceAvd = d.getAvdName(); String deviceAvd = d.getAvdName();
if (deviceAvd != null) { // physical devices return null. if (deviceAvd != null) { // physical devices return null.
AvdInfo info = avdManager.getAvd(deviceAvd); AvdInfo info = avdManager.getAvd(deviceAvd, true /*validAvdOnly*/);
if (info != null && projectTarget.isCompatibleBaseFor(info.getTarget())) { if (info != null && projectTarget.isCompatibleBaseFor(info.getTarget())) {
compatibleRunningAvds.put(d, info); compatibleRunningAvds.put(d, info);
} }
@@ -465,7 +465,7 @@ public final class AndroidLaunchController implements IDebugBridgeChangeListener
// we are going to take the closest AVD. ie a compatible AVD that has the API level // we are going to take the closest AVD. ie a compatible AVD that has the API level
// closest to the project target. // closest to the project target.
AvdInfo[] avds = avdManager.getAvds(); AvdInfo[] avds = avdManager.getValidAvds();
AvdInfo defaultAvd = null; AvdInfo defaultAvd = null;
for (AvdInfo avd : avds) { for (AvdInfo avd : avds) {
if (projectTarget.isCompatibleBaseFor(avd.getTarget())) { if (projectTarget.isCompatibleBaseFor(avd.getTarget())) {

View File

@@ -157,7 +157,8 @@ public class DeviceChooserDialog extends Dialog implements IDeviceChangeListener
} }
} else { } else {
// get the AvdInfo // get the AvdInfo
AvdInfo info = mSdk.getAvdManager().getAvd(device.getAvdName()); AvdInfo info = mSdk.getAvdManager().getAvd(device.getAvdName(),
true /*validAvdOnly*/);
if (info == null) { if (info == null) {
return mWarningImage; return mWarningImage;
} }
@@ -184,7 +185,8 @@ public class DeviceChooserDialog extends Dialog implements IDeviceChangeListener
} }
case 2: case 2:
if (device.isEmulator()) { if (device.isEmulator()) {
AvdInfo info = mSdk.getAvdManager().getAvd(device.getAvdName()); AvdInfo info = mSdk.getAvdManager().getAvd(device.getAvdName(),
true /*validAvdOnly*/);
if (info == null) { if (info == null) {
return "?"; return "?";
} }
@@ -263,7 +265,7 @@ public class DeviceChooserDialog extends Dialog implements IDeviceChangeListener
// get the full list of Android Virtual Devices // get the full list of Android Virtual Devices
AvdManager avdManager = mSdk.getAvdManager(); AvdManager avdManager = mSdk.getAvdManager();
if (avdManager != null) { if (avdManager != null) {
mFullAvdList = avdManager.getAvds(); mFullAvdList = avdManager.getValidAvds();
} else { } else {
mFullAvdList = null; mFullAvdList = null;
} }

View File

@@ -149,7 +149,7 @@ public class EmulatorConfigTab extends AbstractLaunchConfigurationTab {
// radio button for the target mode // radio button for the target mode
Group targetModeGroup = new Group(topComp, SWT.NONE); Group targetModeGroup = new Group(topComp, SWT.NONE);
targetModeGroup.setText("Device Target Selection Mode"); targetModeGroup.setText("Deployment Target Selection Mode");
gd = new GridData(GridData.FILL_HORIZONTAL); gd = new GridData(GridData.FILL_HORIZONTAL);
targetModeGroup.setLayoutData(gd); targetModeGroup.setLayoutData(gd);
layout = new GridLayout(); layout = new GridLayout();
@@ -186,7 +186,7 @@ public class EmulatorConfigTab extends AbstractLaunchConfigurationTab {
offsetComp.setLayout(layout); offsetComp.setLayout(layout);
mPreferredAvdLabel = new Label(offsetComp, SWT.NONE); mPreferredAvdLabel = new Label(offsetComp, SWT.NONE);
mPreferredAvdLabel.setText("Select a preferred Android Virtual Device:"); mPreferredAvdLabel.setText("Select a preferred Android Virtual Device for deployment:");
AvdInfo[] avds = new AvdInfo[0]; AvdInfo[] avds = new AvdInfo[0];
mPreferredAvdSelector = new AvdSelector(offsetComp, avds); mPreferredAvdSelector = new AvdSelector(offsetComp, avds);
mPreferredAvdSelector.setTableHeightHint(100); mPreferredAvdSelector.setTableHeightHint(100);
@@ -338,7 +338,7 @@ public class EmulatorConfigTab extends AbstractLaunchConfigurationTab {
// update the AVD list // update the AVD list
AvdInfo[] avds = null; AvdInfo[] avds = null;
if (avdManager != null) { if (avdManager != null) {
avds = avdManager.getAvds(); avds = avdManager.getValidAvds();
} }
IAndroidTarget projectTarget = null; IAndroidTarget projectTarget = null;
@@ -359,7 +359,7 @@ public class EmulatorConfigTab extends AbstractLaunchConfigurationTab {
} }
if (stringValue != null && stringValue.length() > 0 && avdManager != null) { if (stringValue != null && stringValue.length() > 0 && avdManager != null) {
AvdInfo targetAvd = avdManager.getAvd(stringValue); AvdInfo targetAvd = avdManager.getAvd(stringValue, true /*validAvdOnly*/);
mPreferredAvdSelector.setSelection(targetAvd); mPreferredAvdSelector.setSelection(targetAvd);
} else { } else {
mPreferredAvdSelector.setSelection(null); mPreferredAvdSelector.setSelection(null);

View File

@@ -214,6 +214,10 @@ class Main {
SdkCommandLine.OBJECT_AVD.equals(directObject)) { SdkCommandLine.OBJECT_AVD.equals(directObject)) {
moveAvd(); moveAvd();
} else if (SdkCommandLine.VERB_UPDATE.equals(verb) &&
SdkCommandLine.OBJECT_AVD.equals(directObject)) {
updateAvd();
} else if (SdkCommandLine.VERB_CREATE.equals(verb) && } else if (SdkCommandLine.VERB_CREATE.equals(verb) &&
SdkCommandLine.OBJECT_PROJECT.equals(directObject)) { SdkCommandLine.OBJECT_PROJECT.equals(directObject)) {
createProject(); createProject();
@@ -221,6 +225,7 @@ class Main {
} else if (SdkCommandLine.VERB_UPDATE.equals(verb) && } else if (SdkCommandLine.VERB_UPDATE.equals(verb) &&
SdkCommandLine.OBJECT_PROJECT.equals(directObject)) { SdkCommandLine.OBJECT_PROJECT.equals(directObject)) {
updateProject(); updateProject();
} else { } else {
mSdkCommandLine.printHelpAndExit(null); mSdkCommandLine.printHelpAndExit(null);
} }
@@ -424,7 +429,7 @@ class Main {
mSdkLog.printf("Available Android Virtual Devices:\n"); mSdkLog.printf("Available Android Virtual Devices:\n");
AvdInfo[] avds = avdManager.getAvds(); AvdInfo[] avds = avdManager.getValidAvds();
for (int index = 0 ; index < avds.length ; index++) { for (int index = 0 ; index < avds.length ; index++) {
AvdInfo info = avds[index]; AvdInfo info = avds[index];
if (index > 0) { if (index > 0) {
@@ -461,9 +466,9 @@ class Main {
} }
// Are there some unused AVDs? // Are there some unused AVDs?
List<AvdInfo> badAvds = avdManager.getUnavailableAvds(); AvdInfo[] badAvds = avdManager.getBrokenAvds();
if (badAvds == null || badAvds.size() == 0) { if (badAvds.length == 0) {
return; return;
} }
@@ -475,7 +480,9 @@ class Main {
} }
mSdkLog.printf(" Name: %s\n", info.getName() == null ? "--" : info.getName()); mSdkLog.printf(" Name: %s\n", info.getName() == null ? "--" : info.getName());
mSdkLog.printf(" Path: %s\n", info.getPath() == null ? "--" : info.getPath()); mSdkLog.printf(" Path: %s\n", info.getPath() == null ? "--" : info.getPath());
mSdkLog.printf(" Error: %s\n", info.getError() == null ? "--" : info.getError());
String error = info.getErrorMessage();
mSdkLog.printf(" Error: %s\n", error == null ? "Uknown error" : error);
needSeparator = true; needSeparator = true;
} }
} catch (AndroidLocationException e) { } catch (AndroidLocationException e) {
@@ -511,7 +518,7 @@ class Main {
return; return;
} }
AvdInfo info = avdManager.getAvd(avdName); AvdInfo info = avdManager.getAvd(avdName, false /*validAvdOnly*/);
if (info != null) { if (info != null) {
if (mSdkCommandLine.getFlagForce()) { if (mSdkCommandLine.getFlagForce()) {
removePrevious = true; removePrevious = true;
@@ -544,7 +551,7 @@ class Main {
AvdInfo oldAvdInfo = null; AvdInfo oldAvdInfo = null;
if (removePrevious) { if (removePrevious) {
oldAvdInfo = avdManager.getAvd(avdName); oldAvdInfo = avdManager.getAvd(avdName, false /*validAvdOnly*/);
} }
// Validate skin is either default (empty) or NNNxMMM or a valid skin name. // Validate skin is either default (empty) or NNNxMMM or a valid skin name.
@@ -581,8 +588,7 @@ class Main {
skin, skin,
mSdkCommandLine.getParamSdCard(), mSdkCommandLine.getParamSdCard(),
hardwareConfig, hardwareConfig,
removePrevious, removePrevious);
mSdkLog);
if (newAvdInfo != null && if (newAvdInfo != null &&
oldAvdInfo != null && oldAvdInfo != null &&
@@ -609,20 +615,7 @@ class Main {
try { try {
String avdName = mSdkCommandLine.getParamName(); String avdName = mSdkCommandLine.getParamName();
AvdManager avdManager = new AvdManager(mSdkManager, mSdkLog); AvdManager avdManager = new AvdManager(mSdkManager, mSdkLog);
AvdInfo info = avdManager.getAvd(avdName); AvdInfo info = avdManager.getAvd(avdName, false /*validAvdOnly*/);
if (info == null) {
// Look in unavailable AVDs
List<AvdInfo> badAvds = avdManager.getUnavailableAvds();
if (badAvds != null) {
for (AvdInfo i : badAvds) {
if (i.getName().equals(avdName)) {
info = i;
break;
}
}
}
}
if (info == null) { if (info == null) {
errorAndExit("There is no Android Virtual Device named '%s'.", avdName); errorAndExit("There is no Android Virtual Device named '%s'.", avdName);
@@ -636,16 +629,16 @@ class Main {
} }
/** /**
* Move an AVD. * Moves an AVD.
*/ */
private void moveAvd() { private void moveAvd() {
try { try {
String avdName = mSdkCommandLine.getParamName(); String avdName = mSdkCommandLine.getParamName();
AvdManager avdManager = new AvdManager(mSdkManager, mSdkLog); AvdManager avdManager = new AvdManager(mSdkManager, mSdkLog);
AvdInfo info = avdManager.getAvd(avdName); AvdInfo info = avdManager.getAvd(avdName, true /*validAvdOnly*/);
if (info == null) { if (info == null) {
errorAndExit("There is no Android Virtual Device named '%s'.", avdName); errorAndExit("There is no valid Android Virtual Device named '%s'.", avdName);
return; return;
} }
@@ -703,13 +696,8 @@ class Main {
} }
// Check for conflicts // Check for conflicts
if (newName != null && avdManager.getAvd(newName) != null) {
errorAndExit("There is already an AVD named '%s'.", newName);
return;
}
if (newName != null) { if (newName != null) {
if (avdManager.getAvd(newName) != null) { if (avdManager.getAvd(newName, false /*validAvdOnly*/) != null) {
errorAndExit("There is already an AVD named '%s'.", newName); errorAndExit("There is already an AVD named '%s'.", newName);
return; return;
} }
@@ -735,6 +723,21 @@ class Main {
} }
} }
/**
* Updates a broken AVD.
*/
private void updateAvd() {
try {
String avdName = mSdkCommandLine.getParamName();
AvdManager avdManager = new AvdManager(mSdkManager, mSdkLog);
avdManager.updateAvd(avdName);
} catch (AndroidLocationException e) {
errorAndExit(e.getMessage());
} catch (IOException e) {
errorAndExit(e.getMessage());
}
}
/** /**
* Prompts the user to setup a hardware config for a Platform-based AVD. * Prompts the user to setup a hardware config for a Platform-based AVD.
* @throws IOException * @throws IOException

View File

@@ -79,6 +79,8 @@ public class SdkCommandLine extends CommandLineProcessor {
"Moves or renames an Android Virtual Device." }, "Moves or renames an Android Virtual Device." },
{ VERB_DELETE, OBJECT_AVD, { VERB_DELETE, OBJECT_AVD,
"Deletes an Android Virtual Device." }, "Deletes an Android Virtual Device." },
{ VERB_UPDATE, OBJECT_AVD,
"Updates an Android Virtual Device to match the folders of a new SDK." },
{ VERB_CREATE, OBJECT_PROJECT, { VERB_CREATE, OBJECT_PROJECT,
"Creates a new Android Project." }, "Creates a new Android Project." },
@@ -128,6 +130,12 @@ public class SdkCommandLine extends CommandLineProcessor {
VERB_MOVE, OBJECT_AVD, "p", KEY_PATH, VERB_MOVE, OBJECT_AVD, "p", KEY_PATH,
"New location path of the directory where to move the AVD", null); "New location path of the directory where to move the AVD", null);
// --- update avd ---
define(MODE.STRING, true,
VERB_UPDATE, OBJECT_AVD, "n", KEY_NAME,
"Name of the AVD to update", null);
// --- create project --- // --- create project ---
define(MODE.ENUM, true, define(MODE.ENUM, true,

View File

@@ -22,6 +22,7 @@ import com.android.sdklib.IAndroidTarget;
import com.android.sdklib.ISdkLog; import com.android.sdklib.ISdkLog;
import com.android.sdklib.SdkConstants; import com.android.sdklib.SdkConstants;
import com.android.sdklib.SdkManager; import com.android.sdklib.SdkManager;
import com.android.sdklib.avd.AvdManager.AvdInfo.AvdStatus;
import java.io.BufferedReader; import java.io.BufferedReader;
import java.io.File; import java.io.File;
@@ -32,11 +33,9 @@ import java.io.FilenameFilter;
import java.io.IOException; import java.io.IOException;
import java.io.InputStreamReader; import java.io.InputStreamReader;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Arrays; import java.util.Collections;
import java.util.HashMap; import java.util.HashMap;
import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.TreeSet;
import java.util.Map.Entry; import java.util.Map.Entry;
import java.util.regex.Matcher; import java.util.regex.Matcher;
import java.util.regex.Pattern; import java.util.regex.Pattern;
@@ -46,10 +45,21 @@ import java.util.regex.Pattern;
*/ */
public final class AvdManager { public final class AvdManager {
/**
* Exception thrown when something is wrong with a target path.
*/
private final static class InvalidTargetPathException extends Exception {
private static final long serialVersionUID = 1L;
InvalidTargetPathException(String message) {
super(message);
}
}
public static final String AVD_FOLDER_EXTENSION = ".avd"; public static final String AVD_FOLDER_EXTENSION = ".avd";
private final static String AVD_INFO_PATH = "path"; public final static String AVD_INFO_PATH = "path";
private final static String AVD_INFO_TARGET = "target"; public final static String AVD_INFO_TARGET = "target";
/** /**
* AVD/config.ini key name representing the SDK-relative path of the skin folder, if any, * AVD/config.ini key name representing the SDK-relative path of the skin folder, if any,
@@ -101,7 +111,6 @@ public final class AvdManager {
*/ */
public final static Pattern NUMERIC_SKIN_SIZE = Pattern.compile("[0-9]{2,}x[0-9]{2,}"); public final static Pattern NUMERIC_SKIN_SIZE = Pattern.compile("[0-9]{2,}x[0-9]{2,}");
private final static String USERDATA_IMG = "userdata.img"; private final static String USERDATA_IMG = "userdata.img";
private final static String CONFIG_INI = "config.ini"; private final static String CONFIG_INI = "config.ini";
private final static String SDCARD_IMG = "sdcard.img"; private final static String SDCARD_IMG = "sdcard.img";
@@ -117,11 +126,32 @@ public final class AvdManager {
/** An immutable structure describing an Android Virtual Device. */ /** An immutable structure describing an Android Virtual Device. */
public static final class AvdInfo { public static final class AvdInfo {
/**
* Status for an {@link AvdInfo}. Indicates whether or not this AVD is valid.
*/
public static enum AvdStatus {
/** No error */
OK,
/** Missing 'path' property in the ini file */
ERROR_PATH,
/** Missing config.ini file in the AVD data folder */
ERROR_CONFIG,
/** Missing 'target' property in the ini file */
ERROR_TARGET_HASH,
/** Target was not resolved from its hash */
ERROR_TARGET,
/** Unable to parse config.ini */
ERROR_PROPERTIES,
/** System Image folder in config.ini doesn't exist */
ERROR_IMAGE_DIR;
}
private final String mName; private final String mName;
private final String mPath; private final String mPath;
private final IAndroidTarget mTarget; private final IAndroidTarget mTarget;
private final Map<String, String> mProperties; private final Map<String, String> mProperties;
private final String mError; private final AvdStatus mStatus;
/** /**
* Creates a new valid AVD info. Values are immutable. * Creates a new valid AVD info. Values are immutable.
@@ -136,7 +166,7 @@ public final class AvdManager {
*/ */
public AvdInfo(String name, String path, IAndroidTarget target, public AvdInfo(String name, String path, IAndroidTarget target,
Map<String, String> properties) { Map<String, String> properties) {
this(name, path, target, properties, null /*error*/); this(name, path, target, properties, AvdStatus.OK);
} }
/** /**
@@ -152,12 +182,12 @@ public final class AvdManager {
* @param error The error describing why this AVD is invalid. Cannot be null. * @param error The error describing why this AVD is invalid. Cannot be null.
*/ */
public AvdInfo(String name, String path, IAndroidTarget target, public AvdInfo(String name, String path, IAndroidTarget target,
Map<String, String> properties, String error) { Map<String, String> properties, AvdStatus status) {
mName = name; mName = name;
mPath = path; mPath = path;
mTarget = target; mTarget = target;
mProperties = properties; mProperties = Collections.unmodifiableMap(properties);
mError = error; mStatus = status;
} }
/** Returns the name of the AVD. */ /** Returns the name of the AVD. */
@@ -175,9 +205,9 @@ public final class AvdManager {
return mTarget; return mTarget;
} }
/** Returns the error describing why an AVD failed to load. Always null for valid AVDs. */ /** Returns the {@link AvdStatus} of the receiver. */
public String getError() { public AvdStatus getStatus() {
return mError; return mStatus;
} }
/** /**
@@ -198,42 +228,146 @@ public final class AvdManager {
return getIniFile(mName); return getIniFile(mName);
} }
/**
* Helper method that returns the Config {@link File} for a given AVD name.
*/
public static File getConfigFile(String path) {
return new File(path, CONFIG_INI);
}
/**
* Returns the Config {@link File} for this AVD.
*/
public File getConfigFile() {
return getConfigFile(mPath);
}
/** /**
* Returns a map of properties for the AVD. * Returns a map of properties for the AVD.
*/ */
public Map<String, String> getProperties() { public Map<String, String> getProperties() {
return mProperties; return mProperties;
} }
/**
* Returns the error message for the AVD or <code>null</code> if {@link #getStatus()}
* returns {@link AvdStatus#OK}
*/
public String getErrorMessage() {
try {
switch (mStatus) {
case ERROR_PATH:
return String.format("Missing AVD 'path' property in %1$s", getIniFile());
case ERROR_CONFIG:
return String.format("Missing config.ini file in %1$s", mPath);
case ERROR_TARGET_HASH:
return String.format("Missing 'target' property in %1$s", getIniFile());
case ERROR_TARGET:
return String.format("Unknown target '%1$s' in %2$s",
getProperties().get(AvdManager.AVD_INFO_TARGET),
getIniFile());
case ERROR_PROPERTIES:
return String.format("Failed to parse properties from %1$s",
getConfigFile());
case ERROR_IMAGE_DIR:
return String.format(
"Invalid value in image.sysdir. Run 'android update avd -n %1$s'",
mName);
case OK:
assert false;
return null;
}
} catch (AndroidLocationException e) {
return "Unable to get HOME folder.";
} }
private final ArrayList<AvdInfo> mAvdList = new ArrayList<AvdInfo>(); return null;
}
}
private final ArrayList<AvdInfo> mAllAvdList = new ArrayList<AvdInfo>();
private AvdInfo[] mValidAvdList;
private AvdInfo[] mBrokenAvdList;
private ISdkLog mSdkLog; private ISdkLog mSdkLog;
private final SdkManager mSdk; private final SdkManager mSdk;
public AvdManager(SdkManager sdk, ISdkLog sdkLog) throws AndroidLocationException { public AvdManager(SdkManager sdk, ISdkLog sdkLog) throws AndroidLocationException {
mSdk = sdk; mSdk = sdk;
mSdkLog = sdkLog; mSdkLog = sdkLog;
buildAvdList(mAvdList); buildAvdList(mAllAvdList);
} }
/** /**
* Returns the existing AVDs. * Returns all the existing AVDs.
* @return a newly allocated array containing all the AVDs. * @return a newly allocated array containing all the AVDs.
*/ */
public AvdInfo[] getAvds() { public AvdInfo[] getAllAvds() {
return mAvdList.toArray(new AvdInfo[mAvdList.size()]); synchronized (mAllAvdList) {
return mAllAvdList.toArray(new AvdInfo[mAllAvdList.size()]);
}
}
/**
* Returns all the valid AVDs.
* @return a newly allocated array containing all valid the AVDs.
*/
public AvdInfo[] getValidAvds() {
synchronized (mAllAvdList) {
if (mValidAvdList == null) {
ArrayList<AvdInfo> list = new ArrayList<AvdInfo>();
for (AvdInfo avd : mAllAvdList) {
if (avd.getStatus() == AvdStatus.OK) {
list.add(avd);
}
}
mValidAvdList = list.toArray(new AvdInfo[list.size()]);
}
return mValidAvdList;
}
}
/**
* Returns all the broken AVDs.
* @return a newly allocated array containing all the broken AVDs.
*/
public AvdInfo[] getBrokenAvds() {
synchronized (mAllAvdList) {
if (mBrokenAvdList == null) {
ArrayList<AvdInfo> list = new ArrayList<AvdInfo>();
for (AvdInfo avd : mBrokenAvdList) {
if (avd.getStatus() != AvdStatus.OK) {
list.add(avd);
}
}
mBrokenAvdList = list.toArray(new AvdInfo[list.size()]);
}
return mBrokenAvdList;
}
} }
/** /**
* Returns the {@link AvdInfo} matching the given <var>name</var>. * Returns the {@link AvdInfo} matching the given <var>name</var>.
* @param name the name of the AVD to return
* @param validAvdOnly if <code>true</code>, only look through the list of valid AVDs.
* @return the matching AvdInfo or <code>null</code> if none were found. * @return the matching AvdInfo or <code>null</code> if none were found.
*/ */
public AvdInfo getAvd(String name) { public AvdInfo getAvd(String name, boolean validAvdOnly) {
for (AvdInfo info : mAvdList) { if (validAvdOnly) {
for (AvdInfo info : getValidAvds()) {
if (info.getName().equals(name)) { if (info.getName().equals(name)) {
return info; return info;
} }
} }
} else {
synchronized (mAllAvdList) {
for (AvdInfo info : getValidAvds()) {
if (info.getName().equals(name)) {
return info;
}
}
}
}
return null; return null;
} }
@@ -246,10 +380,14 @@ public final class AvdManager {
public void reloadAvds() throws AndroidLocationException { public void reloadAvds() throws AndroidLocationException {
// build the list in a temp list first, in case the method throws an exception. // build the list in a temp list first, in case the method throws an exception.
// It's better than deleting the whole list before reading the new one. // It's better than deleting the whole list before reading the new one.
ArrayList<AvdInfo> list = new ArrayList<AvdInfo>(); ArrayList<AvdInfo> allList = new ArrayList<AvdInfo>();
buildAvdList(list); buildAvdList(allList);
mAvdList.clear();
mAvdList.addAll(list); synchronized (mAllAvdList) {
mAllAvdList.clear();
mAllAvdList.addAll(allList);
mValidAvdList = mBrokenAvdList = null;
}
} }
/** /**
@@ -265,7 +403,7 @@ public final class AvdManager {
*/ */
public AvdInfo createAvd(File avdFolder, String name, IAndroidTarget target, public AvdInfo createAvd(File avdFolder, String name, IAndroidTarget target,
String skinName, String sdcard, Map<String,String> hardwareConfig, String skinName, String sdcard, Map<String,String> hardwareConfig,
boolean removePrevious, ISdkLog log) { boolean removePrevious) {
File iniFile = null; File iniFile = null;
boolean needCleanup = false; boolean needCleanup = false;
@@ -277,8 +415,8 @@ public final class AvdManager {
recursiveDelete(avdFolder); recursiveDelete(avdFolder);
} else { } else {
// AVD shouldn't already exist if removePrevious is false. // AVD shouldn't already exist if removePrevious is false.
if (log != null) { if (mSdkLog != null) {
log.error(null, mSdkLog.error(null,
"Folder %1$s is in the way. Use --force if you want to overwrite.", "Folder %1$s is in the way. Use --force if you want to overwrite.",
avdFolder.getAbsolutePath()); avdFolder.getAbsolutePath());
} }
@@ -302,7 +440,7 @@ public final class AvdManager {
} }
if (userdataSrc.exists() == false) { if (userdataSrc.exists() == false) {
log.error(null, "Unable to find a '%1$s' file to copy into the AVD folder.", mSdkLog.error(null, "Unable to find a '%1$s' file to copy into the AVD folder.",
USERDATA_IMG); USERDATA_IMG);
needCleanup = true; needCleanup = true;
return null; return null;
@@ -325,28 +463,11 @@ public final class AvdManager {
// Config file. // Config file.
HashMap<String, String> values = new HashMap<String, String>(); HashMap<String, String> values = new HashMap<String, String>();
// First the image folders of the target itself if (setImagePathProperties(target, values) == false) {
imagePath = getImageRelativePath(target, log);
if (imagePath == null) {
needCleanup = true; needCleanup = true;
return null; return null;
} }
values.put(AVD_INI_IMAGES_1, imagePath);
// If the target is an add-on we need to add the Platform image as a backup.
IAndroidTarget parent = target.getParent();
if (parent != null) {
imagePath = getImageRelativePath(parent, log);
if (imagePath == null) {
needCleanup = true;
return null;
}
values.put(AVD_INI_IMAGES_2, imagePath);
}
// Now the skin. // Now the skin.
if (skinName == null) { if (skinName == null) {
skinName = target.getDefaultSkin(); skinName = target.getDefaultSkin();
@@ -361,7 +482,7 @@ public final class AvdManager {
} else { } else {
// get the path of the skin (relative to the SDK) // get the path of the skin (relative to the SDK)
// assume skin name is valid // assume skin name is valid
String skinPath = getSkinRelativePath(skinName, target, log); String skinPath = getSkinRelativePath(skinName, target);
if (skinPath == null) { if (skinPath == null) {
needCleanup = true; needCleanup = true;
return null; return null;
@@ -392,13 +513,13 @@ public final class AvdManager {
File mkSdCard = new File(toolsFolder, SdkConstants.mkSdCardCmdName()); File mkSdCard = new File(toolsFolder, SdkConstants.mkSdCardCmdName());
if (mkSdCard.isFile() == false) { if (mkSdCard.isFile() == false) {
log.error(null, "'%1$s' is missing from the SDK tools folder.", mSdkLog.error(null, "'%1$s' is missing from the SDK tools folder.",
mkSdCard.getName()); mkSdCard.getName());
needCleanup = true; needCleanup = true;
return null; return null;
} }
if (createSdCard(mkSdCard.getAbsolutePath(), sdcard, path, log) == false) { if (createSdCard(mkSdCard.getAbsolutePath(), sdcard, path) == false) {
needCleanup = true; needCleanup = true;
return null; // mksdcard output has already been displayed, no need to return null; // mksdcard output has already been displayed, no need to
// output anything else. // output anything else.
@@ -408,7 +529,7 @@ public final class AvdManager {
// only when the dev does 'android list avd' // only when the dev does 'android list avd'
values.put(AVD_INI_SDCARD_SIZE, sdcard); values.put(AVD_INI_SDCARD_SIZE, sdcard);
} else { } else {
log.error(null, mSdkLog.error(null,
"'%1$s' is not recognized as a valid sdcard value.\n" + "'%1$s' is not recognized as a valid sdcard value.\n" +
"Value should be:\n" + "Value should be:\n" +
"1. path to an sdcard.\n" + "1. path to an sdcard.\n" +
@@ -425,13 +546,13 @@ public final class AvdManager {
} }
File configIniFile = new File(avdFolder, CONFIG_INI); File configIniFile = new File(avdFolder, CONFIG_INI);
createConfigIni(configIniFile, values); writeIniFile(configIniFile, values);
if (log != null) { if (mSdkLog != null) {
if (target.isPlatform()) { if (target.isPlatform()) {
log.printf("Created AVD '%1$s' based on %2$s\n", name, target.getName()); mSdkLog.printf("Created AVD '%1$s' based on %2$s\n", name, target.getName());
} else { } else {
log.printf("Created AVD '%1$s' based on %2$s (%3$s)\n", name, target.getName(), mSdkLog.printf("Created AVD '%1$s' based on %2$s (%3$s)\n", name, target.getName(),
target.getVendor()); target.getVendor());
} }
} }
@@ -439,16 +560,19 @@ public final class AvdManager {
// create the AvdInfo object, and add it to the list // create the AvdInfo object, and add it to the list
AvdInfo avdInfo = new AvdInfo(name, avdFolder.getAbsolutePath(), target, values); AvdInfo avdInfo = new AvdInfo(name, avdFolder.getAbsolutePath(), target, values);
mAvdList.add(avdInfo); synchronized (mAllAvdList) {
mAllAvdList.add(avdInfo);
mValidAvdList = mBrokenAvdList = null;
}
return avdInfo; return avdInfo;
} catch (AndroidLocationException e) { } catch (AndroidLocationException e) {
if (log != null) { if (mSdkLog != null) {
log.error(e, null); mSdkLog.error(e, null);
} }
} catch (IOException e) { } catch (IOException e) {
if (log != null) { if (mSdkLog != null) {
log.error(e, null); mSdkLog.error(e, null);
} }
} finally { } finally {
if (needCleanup) { if (needCleanup) {
@@ -465,31 +589,39 @@ public final class AvdManager {
} }
/** /**
* Returns the path to the target images folder as a relative path to the SDK. * Returns the path to the target images folder as a relative path to the SDK, if the folder
* is not empty. If the image folder is empty or does not exist, <code>null</code> is returned.
* @throws InvalidTargetPathException if the target image folder is not in the current SDK.
*/ */
private String getImageRelativePath(IAndroidTarget target, ISdkLog log) { private String getImageRelativePath(IAndroidTarget target)
throws InvalidTargetPathException {
String imageFullPath = target.getPath(IAndroidTarget.IMAGES); String imageFullPath = target.getPath(IAndroidTarget.IMAGES);
// make this path relative to the SDK location // make this path relative to the SDK location
String sdkLocation = mSdk.getLocation(); String sdkLocation = mSdk.getLocation();
if (imageFullPath.startsWith(sdkLocation) == false) { if (imageFullPath.startsWith(sdkLocation) == false) {
// this really really should not happen. // this really really should not happen.
log.error(null, "Target location is not inside the SDK.");
assert false; assert false;
return null; throw new InvalidTargetPathException("Target location is not inside the SDK.");
} }
File folder = new File(imageFullPath);
if (folder.isDirectory() && folder.list().length > 0) {
imageFullPath = imageFullPath.substring(sdkLocation.length()); imageFullPath = imageFullPath.substring(sdkLocation.length());
if (imageFullPath.charAt(0) == File.separatorChar) { if (imageFullPath.charAt(0) == File.separatorChar) {
imageFullPath = imageFullPath.substring(1); imageFullPath = imageFullPath.substring(1);
} }
return imageFullPath; return imageFullPath;
} }
return null;
}
/** /**
* Returns the path to the skin, as a relative path to the SDK. * Returns the path to the skin, as a relative path to the SDK.
*/ */
private String getSkinRelativePath(String skinName, IAndroidTarget target, ISdkLog log) { private String getSkinRelativePath(String skinName, IAndroidTarget target) {
// first look to see if the skin is in the target // first look to see if the skin is in the target
String path = target.getPath(IAndroidTarget.SKINS); String path = target.getPath(IAndroidTarget.SKINS);
@@ -504,7 +636,7 @@ public final class AvdManager {
// skin really does not exist! // skin really does not exist!
if (skin.exists() == false) { if (skin.exists() == false) {
log.error(null, "Skin '%1$s' does not exist.", skinName); mSdkLog.error(null, "Skin '%1$s' does not exist.", skinName);
return null; return null;
} }
@@ -515,7 +647,7 @@ public final class AvdManager {
String sdkLocation = mSdk.getLocation(); String sdkLocation = mSdk.getLocation();
if (path.startsWith(sdkLocation) == false) { if (path.startsWith(sdkLocation) == false) {
// this really really should not happen. // this really really should not happen.
log.error(null, "Target location is not inside the SDK."); mSdkLog.error(null, "Target location is not inside the SDK.");
assert false; assert false;
return null; return null;
} }
@@ -542,7 +674,7 @@ public final class AvdManager {
File iniFile = AvdInfo.getIniFile(name); File iniFile = AvdInfo.getIniFile(name);
values.put(AVD_INFO_PATH, avdFolder.getAbsolutePath()); values.put(AVD_INFO_PATH, avdFolder.getAbsolutePath());
values.put(AVD_INFO_TARGET, target.hashString()); values.put(AVD_INFO_TARGET, target.hashString());
createConfigIni(iniFile, values); writeIniFile(iniFile, values);
return iniFile; return iniFile;
} }
@@ -641,9 +773,7 @@ public final class AvdManager {
// update AVD info // update AVD info
AvdInfo info = new AvdInfo(avdInfo.getName(), paramFolderPath, avdInfo.getTarget(), AvdInfo info = new AvdInfo(avdInfo.getName(), paramFolderPath, avdInfo.getTarget(),
avdInfo.getProperties()); avdInfo.getProperties());
mAvdList.remove(avdInfo); replaceAvd(avdInfo, info);
mAvdList.add(info);
avdInfo = info;
// update the ini file // update the ini file
createAvdIniFile(avdInfo); createAvdIniFile(avdInfo);
@@ -663,8 +793,7 @@ public final class AvdManager {
// update AVD info // update AVD info
AvdInfo info = new AvdInfo(newName, avdInfo.getPath(), avdInfo.getTarget(), AvdInfo info = new AvdInfo(newName, avdInfo.getPath(), avdInfo.getTarget(),
avdInfo.getProperties()); avdInfo.getProperties());
mAvdList.remove(avdInfo); replaceAvd(avdInfo, info);
mAvdList.add(info);
} }
log.printf("AVD '%1$s' moved.\n", avdInfo.getName()); log.printf("AVD '%1$s' moved.\n", avdInfo.getName());
@@ -733,75 +862,33 @@ public final class AvdManager {
} }
/** /**
* Computes the internal list of available AVDs. * Computes the internal list of available AVDs
* This only contains AVDs that reference the target currently available. * @param allList the list to contain all the AVDs
* *
* @param list An array list that will contain the list of AVDs.
* @throws AndroidLocationException if there's a problem getting android root directory. * @throws AndroidLocationException if there's a problem getting android root directory.
*/ */
private void buildAvdList(ArrayList<AvdInfo> list) throws AndroidLocationException { private void buildAvdList(ArrayList<AvdInfo> allList) throws AndroidLocationException {
File[] avds = buildAvdFilesList(); File[] avds = buildAvdFilesList();
if (avds != null) { if (avds != null) {
for (File avd : avds) { for (File avd : avds) {
AvdInfo info = parseAvdInfo(avd, false /*acceptError*/); AvdInfo info = parseAvdInfo(avd);
if (info != null) { if (info != null) {
list.add(info); allList.add(info);
} }
} }
} }
} }
/** /**
* Computes the internal list of <em>not</em> available AVDs. * Parses an AVD .ini file to create an {@link AvdInfo}.
* <p/>
* These are the AVDs that failed to load for some reason or another.
* You can retrieve the load error using {@link AvdInfo#getError()}.
* <p/>
* These {@link AvdInfo} must not be used for usual operations (e.g. instanciating
* an emulator) or trying to use them for anything else but {@link #deleteAvd(AvdInfo, ISdkLog)}
* will have unpredictable results -- that is most likely the operation will fail.
* *
* @return A list of unavailable AVDs, all with errors. The list can be null or empty if there * @param path The path to the AVD .ini file
* are no AVDs to return.
* @throws AndroidLocationException if there's a problem getting android root directory.
*/
public List<AvdInfo> getUnavailableAvds() 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 * @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. * 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 * @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. * valid and acceptError is false.
*/ */
private AvdInfo parseAvdInfo(File path, boolean acceptError) { private AvdInfo parseAvdInfo(File path) {
String error = null;
Map<String, String> map = SdkManager.parsePropertyFile(path, mSdkLog); Map<String, String> map = SdkManager.parsePropertyFile(path, mSdkLog);
String avdPath = map.get(AVD_INFO_PATH); String avdPath = map.get(AVD_INFO_PATH);
@@ -831,44 +918,62 @@ public final class AvdManager {
name = matcher.group(1); name = matcher.group(1);
} }
if (!acceptError) { // check the image.sysdir are valid
if (avdPath == null || boolean validImageSysdir = true;
targetHash == null || if (properties != null) {
target == null || String imageSysDir = properties.get(AVD_INI_IMAGES_1);
configIniFile == null || if (imageSysDir != null) {
properties == null) { File f = new File(mSdk.getLocation() + File.separator + imageSysDir);
return null; if (f.isDirectory() == false) {
} validImageSysdir = false;
} else { } else {
if (avdPath == null || configIniFile == null) { imageSysDir = properties.get(AVD_INI_IMAGES_2);
error = String.format("Missing AVD 'path' property in %1$s", name); if (imageSysDir != null) {
} else if (targetHash == null) { f = new File(mSdk.getLocation() + File.separator + imageSysDir);
error = String.format("Missing 'target' property in %1$s", name); if (f.isDirectory() == false) {
} else if (target == null) { validImageSysdir = false;
error = String.format("Unknown target '%2$s' in %1$s", name, targetHash);
} else if (properties == null) {
error = String.format("Failed to parse properties from %1$s", avdPath);
} }
} }
}
}
}
AvdStatus status;
if (avdPath == null) {
status = AvdStatus.ERROR_PATH;
} else if (configIniFile == null) {
status = AvdStatus.ERROR_CONFIG;
} else if (targetHash == null) {
status = AvdStatus.ERROR_TARGET_HASH;
} else if (target == null) {
status = AvdStatus.ERROR_TARGET;
} else if (properties == null) {
status = AvdStatus.ERROR_PROPERTIES;
} else if (validImageSysdir == false) {
status = AvdStatus.ERROR_IMAGE_DIR;
} else {
status = AvdStatus.OK;
}
AvdInfo info = new AvdInfo( AvdInfo info = new AvdInfo(
name, name,
avdPath, avdPath,
target, target,
properties, properties,
error); status);
return info; return info;
} }
/** /**
* Writes a new AVD config.ini file from a set of properties. * Writes a .ini file from a set of properties.
* *
* @param iniFile The file to generate. * @param iniFile The file to generate.
* @param values THe properties to place in the ini file. * @param values THe properties to place in the ini file.
* @throws IOException if {@link FileWriter} fails to open, write or close the file. * @throws IOException if {@link FileWriter} fails to open, write or close the file.
*/ */
private static void createConfigIni(File iniFile, Map<String, String> values) private static void writeIniFile(File iniFile, Map<String, String> values)
throws IOException { throws IOException {
FileWriter writer = new FileWriter(iniFile); FileWriter writer = new FileWriter(iniFile);
@@ -876,7 +981,6 @@ public final class AvdManager {
writer.write(String.format("%1$s=%2$s\n", entry.getKey(), entry.getValue())); writer.write(String.format("%1$s=%2$s\n", entry.getKey(), entry.getValue()));
} }
writer.close(); writer.close();
} }
/** /**
@@ -888,7 +992,7 @@ public final class AvdManager {
* @param log The logger object, to report errors. * @param log The logger object, to report errors.
* @return True if the sdcard could be created. * @return True if the sdcard could be created.
*/ */
private boolean createSdCard(String toolLocation, String size, String location, ISdkLog log) { private boolean createSdCard(String toolLocation, String size, String location) {
try { try {
String[] command = new String[3]; String[] command = new String[3];
command[0] = toolLocation; command[0] = toolLocation;
@@ -905,7 +1009,7 @@ public final class AvdManager {
return true; return true;
} else { } else {
for (String error : errorOutput) { for (String error : errorOutput) {
log.error(null, error); mSdkLog.error(null, error);
} }
} }
@@ -915,7 +1019,7 @@ public final class AvdManager {
// pass, print error below // pass, print error below
} }
log.error(null, "Failed to create the SD card."); mSdkLog.error(null, "Failed to create the SD card.");
return false; return false;
} }
@@ -1007,7 +1111,135 @@ public final class AvdManager {
* @return true if this {@link AvdInfo} was present and has been removed. * @return true if this {@link AvdInfo} was present and has been removed.
*/ */
public boolean removeAvd(AvdInfo avdInfo) { public boolean removeAvd(AvdInfo avdInfo) {
return mAvdList.remove(avdInfo); synchronized (mAllAvdList) {
if (mAllAvdList.remove(avdInfo)) {
mValidAvdList = mBrokenAvdList = null;
return true;
}
} }
return false;
}
/**
* Updates an AVD with new path to the system image folders.
* @param name the name of the AVD to update.
* @throws IOException
* @throws AndroidLocationException
*/
public void updateAvd(String name) throws IOException, AndroidLocationException {
// find the AVD to update. It should be be in the broken list.
AvdInfo avd = null;
synchronized (mAllAvdList) {
for (AvdInfo info : mAllAvdList) {
if (info.getName().equals(name)) {
avd = info;
break;
}
}
}
if (avd == null) {
// not in the broken list, just return.
mSdkLog.error(null, "There is no Android Virtual Device named '%s'.", name);
return;
}
// get the properties. This is a unmodifiable Map.
Map<String, String> oldProperties = avd.getProperties();
// create a new map
Map<String, String> properties = new HashMap<String, String>();
properties.putAll(oldProperties);
AvdStatus status;
// create the path to the new system images.
if (setImagePathProperties(avd.getTarget(), properties)) {
if (properties.containsKey(AVD_INI_IMAGES_1)) {
mSdkLog.printf("Updated '%1$s' with value '%2$s'\n", AVD_INI_IMAGES_1,
properties.get(AVD_INI_IMAGES_1));
}
if (properties.containsKey(AVD_INI_IMAGES_2)) {
mSdkLog.printf("Updated '%1$s' with value '%2$s'\n", AVD_INI_IMAGES_2,
properties.get(AVD_INI_IMAGES_2));
}
status = AvdStatus.OK;
} else {
mSdkLog.error(null, "Unable to find non empty system images folders for %1$s", name);
//FIXME: display paths to empty image folders?
status = AvdStatus.ERROR_IMAGE_DIR;
}
// now write the config file
File configIniFile = new File(avd.getPath(), CONFIG_INI);
writeIniFile(configIniFile, properties);
// finally create a new AvdInfo for this unbroken avd and add it to the list.
// instead of creating the AvdInfo object directly we reparse it, to detect other possible
// errors
// FIXME: We may want to create this AvdInfo by reparsing the AVD instead. This could detect other errors.
AvdInfo newAvd = new AvdInfo(
name,
avd.getPath(),
avd.getTarget(),
properties,
status);
replaceAvd(avd, newAvd);
}
/**
* Sets the paths to the system images in a properties map.
* @param target the target in which to find the system images.
* @param properties the properties in which to set the paths.
* @return true if success, false if some path are missing.
*/
private boolean setImagePathProperties(IAndroidTarget target, Map<String, String> properties) {
properties.remove(AVD_INI_IMAGES_1);
properties.remove(AVD_INI_IMAGES_2);
try {
String property = AVD_INI_IMAGES_1;
// First the image folders of the target itself
String imagePath = getImageRelativePath(target);
if (imagePath != null) {
properties.put(property, imagePath);
property = AVD_INI_IMAGES_2;
}
// If the target is an add-on we need to add the Platform image as a backup.
IAndroidTarget parent = target.getParent();
if (parent != null) {
imagePath = getImageRelativePath(parent);
if (imagePath != null) {
properties.put(property, imagePath);
}
}
// we need at least one path!
return properties.containsKey(AVD_INI_IMAGES_1);
} catch (InvalidTargetPathException e) {
mSdkLog.error(e, e.getMessage());
}
return false;
}
/**
* Replaces an old {@link AvdInfo} with a new one in the lists storing them.
* @param oldAvd the {@link AvdInfo} to remove.
* @param newAvd the {@link AvdInfo} to add.
*/
private void replaceAvd(AvdInfo oldAvd, AvdInfo newAvd) {
synchronized (mAllAvdList) {
mAllAvdList.remove(oldAvd);
mAllAvdList.add(newAvd);
mValidAvdList = mBrokenAvdList = null;
}
}
} }