2 * Copyright (C) 2008 The Android Open Source Project
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
8 * http://www.apache.org/licenses/LICENSE-2.0
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
17 package com.android.sdklib.internal.avd;
19 import com.android.prefs.AndroidLocation;
20 import com.android.prefs.AndroidLocation.AndroidLocationException;
21 import com.android.sdklib.IAndroidTarget;
22 import com.android.sdklib.ISdkLog;
23 import com.android.sdklib.SdkConstants;
24 import com.android.sdklib.SdkManager;
25 import com.android.sdklib.internal.avd.AvdManager.AvdInfo.AvdStatus;
27 import java.io.BufferedReader;
29 import java.io.FileInputStream;
30 import java.io.FileOutputStream;
31 import java.io.FileWriter;
32 import java.io.FilenameFilter;
33 import java.io.IOException;
34 import java.io.InputStreamReader;
35 import java.io.OutputStreamWriter;
36 import java.util.ArrayList;
37 import java.util.Collections;
38 import java.util.HashMap;
40 import java.util.Map.Entry;
41 import java.util.regex.Matcher;
42 import java.util.regex.Pattern;
45 * Android Virtual Device Manager to manage AVDs.
47 public final class AvdManager {
50 * Exception thrown when something is wrong with a target path.
52 private final static class InvalidTargetPathException extends Exception {
53 private static final long serialVersionUID = 1L;
55 InvalidTargetPathException(String message) {
60 public static final String AVD_FOLDER_EXTENSION = ".avd"; //$NON-NLS-1$
62 public final static String AVD_INFO_PATH = "path"; //$NON-NLS-1$
63 public final static String AVD_INFO_TARGET = "target"; //$NON-NLS-1$
66 * AVD/config.ini key name representing the SDK-relative path of the skin folder, if any,
67 * or a 320x480 like constant for a numeric skin size.
69 * @see #NUMERIC_SKIN_SIZE
71 public final static String AVD_INI_SKIN_PATH = "skin.path"; //$NON-NLS-1$
73 * AVD/config.ini key name representing an UI name for the skin.
74 * This config key is ignored by the emulator. It is only used by the SDK manager or
75 * tools to give a friendlier name to the skin.
76 * If missing, use the {@link #AVD_INI_SKIN_PATH} key instead.
78 public final static String AVD_INI_SKIN_NAME = "skin.name"; //$NON-NLS-1$
80 * AVD/config.ini key name representing the path to the sdcard file.
81 * If missing, the default name "sdcard.img" will be used for the sdcard, if there's such
86 public final static String AVD_INI_SDCARD_PATH = "sdcard.path"; //$NON-NLS-1$
88 * AVD/config.ini key name representing the size of the SD card.
89 * This property is for UI purposes only. It is not used by the emulator.
91 * @see #SDCARD_SIZE_PATTERN
93 public final static String AVD_INI_SDCARD_SIZE = "sdcard.size"; //$NON-NLS-1$
95 * AVD/config.ini key name representing the first path where the emulator looks
96 * for system images. Typically this is the path to the add-on system image or
97 * the path to the platform system image if there's no add-on.
99 * The emulator looks at {@link #AVD_INI_IMAGES_1} before {@link #AVD_INI_IMAGES_2}.
101 public final static String AVD_INI_IMAGES_1 = "image.sysdir.1"; //$NON-NLS-1$
103 * AVD/config.ini key name representing the second path where the emulator looks
104 * for system images. Typically this is the path to the platform system image.
106 * @see #AVD_INI_IMAGES_1
108 public final static String AVD_INI_IMAGES_2 = "image.sysdir.2"; //$NON-NLS-1$
111 * Pattern to match pixel-sized skin "names", e.g. "320x480".
113 public final static Pattern NUMERIC_SKIN_SIZE = Pattern.compile("[0-9]{2,}x[0-9]{2,}"); //$NON-NLS-1$
115 private final static String USERDATA_IMG = "userdata.img"; //$NON-NLS-1$
116 private final static String CONFIG_INI = "config.ini"; //$NON-NLS-1$
117 private final static String SDCARD_IMG = "sdcard.img"; //$NON-NLS-1$
119 private final static String INI_EXTENSION = ".ini"; //$NON-NLS-1$
120 private final static Pattern INI_NAME_PATTERN = Pattern.compile("(.+)\\" + //$NON-NLS-1$
121 INI_EXTENSION + "$", //$NON-NLS-1$
122 Pattern.CASE_INSENSITIVE);
124 private final static Pattern IMAGE_NAME_PATTERN = Pattern.compile("(.+)\\.img$", //$NON-NLS-1$
125 Pattern.CASE_INSENSITIVE);
128 * Pattern for matching SD Card sizes, e.g. "4K" or "16M".
130 public final static Pattern SDCARD_SIZE_PATTERN = Pattern.compile("\\d+[MK]"); //$NON-NLS-1$
132 /** Regex used to validate characters that compose an AVD name. */
133 public final static Pattern RE_AVD_NAME = Pattern.compile("[a-zA-Z0-9._-]+"); //$NON-NLS-1$
135 /** List of valid characters for an AVD name. Used for display purposes. */
136 public final static String CHARS_AVD_NAME = "a-z A-Z 0-9 . _ -"; //$NON-NLS-1$
138 public final static String HARDWARE_INI = "hardware.ini"; //$NON-NLS-1$
140 /** An immutable structure describing an Android Virtual Device. */
141 public static final class AvdInfo {
144 * Status for an {@link AvdInfo}. Indicates whether or not this AVD is valid.
146 public static enum AvdStatus {
149 /** Missing 'path' property in the ini file */
151 /** Missing config.ini file in the AVD data folder */
153 /** Missing 'target' property in the ini file */
155 /** Target was not resolved from its hash */
157 /** Unable to parse config.ini */
159 /** System Image folder in config.ini doesn't exist */
163 private final String mName;
164 private final String mPath;
165 private final String mTargetHash;
166 private final IAndroidTarget mTarget;
167 private final Map<String, String> mProperties;
168 private final AvdStatus mStatus;
171 * Creates a new valid AVD info. Values are immutable.
173 * Such an AVD is available and can be used.
174 * The error string is set to null.
176 * @param name The name of the AVD (for display or reference)
177 * @param path The path to the config.ini file
178 * @param targetHash the target hash
179 * @param target The target. Can be null, if the target was not resolved.
180 * @param properties The property map. Cannot be null.
182 public AvdInfo(String name, String path, String targetHash, IAndroidTarget target,
183 Map<String, String> properties) {
184 this(name, path, targetHash, target, properties, AvdStatus.OK);
188 * Creates a new <em>invalid</em> AVD info. Values are immutable.
190 * Such an AVD is not complete and cannot be used.
191 * The error string must be non-null.
193 * @param name The name of the AVD (for display or reference)
194 * @param path The path to the config.ini file
195 * @param targetHash the target hash
196 * @param target The target. Can be null, if the target was not resolved.
197 * @param properties The property map. Can be null.
198 * @param status The {@link AvdStatus} of this AVD. Cannot be null.
200 public AvdInfo(String name, String path, String targetHash, IAndroidTarget target,
201 Map<String, String> properties, AvdStatus status) {
204 mTargetHash = targetHash;
206 mProperties = properties == null ? null : Collections.unmodifiableMap(properties);
210 /** Returns the name of the AVD. */
211 public String getName() {
215 /** Returns the path of the AVD data directory. */
216 public String getPath() {
221 * Returns the target hash string.
223 public String getTargetHash() {
227 /** Returns the target of the AVD, or <code>null</code> if it has not been resolved. */
228 public IAndroidTarget getTarget() {
232 /** Returns the {@link AvdStatus} of the receiver. */
233 public AvdStatus getStatus() {
238 * Helper method that returns the .ini {@link File} for a given AVD name.
239 * @throws AndroidLocationException if there's a problem getting android root directory.
241 public static File getIniFile(String name) throws AndroidLocationException {
243 avdRoot = AndroidLocation.getFolder() + AndroidLocation.FOLDER_AVD;
244 return new File(avdRoot, name + INI_EXTENSION);
248 * Returns the .ini {@link File} for this AVD.
249 * @throws AndroidLocationException if there's a problem getting android root directory.
251 public File getIniFile() throws AndroidLocationException {
252 return getIniFile(mName);
256 * Helper method that returns the Config {@link File} for a given AVD name.
258 public static File getConfigFile(String path) {
259 return new File(path, CONFIG_INI);
263 * Returns the Config {@link File} for this AVD.
265 public File getConfigFile() {
266 return getConfigFile(mPath);
270 * Returns an unmodifiable map of properties for the AVD. This can be null.
272 public Map<String, String> getProperties() {
277 * Returns the error message for the AVD or <code>null</code> if {@link #getStatus()}
278 * returns {@link AvdStatus#OK}
280 public String getErrorMessage() {
284 return String.format("Missing AVD 'path' property in %1$s", getIniFile());
286 return String.format("Missing config.ini file in %1$s", mPath);
287 case ERROR_TARGET_HASH:
288 return String.format("Missing 'target' property in %1$s", getIniFile());
290 return String.format("Unknown target '%1$s' in %2$s",
291 mTargetHash, getIniFile());
292 case ERROR_PROPERTIES:
293 return String.format("Failed to parse properties from %1$s",
295 case ERROR_IMAGE_DIR:
296 return String.format(
297 "Invalid value in image.sysdir. Run 'android update avd -n %1$s'",
303 } catch (AndroidLocationException e) {
304 return "Unable to get HOME folder.";
311 private final ArrayList<AvdInfo> mAllAvdList = new ArrayList<AvdInfo>();
312 private AvdInfo[] mValidAvdList;
313 private AvdInfo[] mBrokenAvdList;
314 private final SdkManager mSdkManager;
317 * Creates an AVD Manager for a given SDK represented by a {@link SdkManager}.
318 * @param sdkManager The SDK.
319 * @param log The log object to receive the log of the initial loading of the AVDs.
320 * This log object is not kept by this instance of AvdManager and each
321 * method takes its own logger. The rationale is that the AvdManager
322 * might be called from a variety of context, each with different
324 * @throws AndroidLocationException
326 public AvdManager(SdkManager sdkManager, ISdkLog log) throws AndroidLocationException {
327 mSdkManager = sdkManager;
328 buildAvdList(mAllAvdList, log);
332 * Returns the {@link SdkManager} associated with the {@link AvdManager}.
334 public SdkManager getSdkManager() {
339 * Returns all the existing AVDs.
340 * @return a newly allocated array containing all the AVDs.
342 public AvdInfo[] getAllAvds() {
343 synchronized (mAllAvdList) {
344 return mAllAvdList.toArray(new AvdInfo[mAllAvdList.size()]);
349 * Returns all the valid AVDs.
350 * @return a newly allocated array containing all valid the AVDs.
352 public AvdInfo[] getValidAvds() {
353 synchronized (mAllAvdList) {
354 if (mValidAvdList == null) {
355 ArrayList<AvdInfo> list = new ArrayList<AvdInfo>();
356 for (AvdInfo avd : mAllAvdList) {
357 if (avd.getStatus() == AvdStatus.OK) {
362 mValidAvdList = list.toArray(new AvdInfo[list.size()]);
364 return mValidAvdList;
369 * Returns all the broken AVDs.
370 * @return a newly allocated array containing all the broken AVDs.
372 public AvdInfo[] getBrokenAvds() {
373 synchronized (mAllAvdList) {
374 if (mBrokenAvdList == null) {
375 ArrayList<AvdInfo> list = new ArrayList<AvdInfo>();
376 for (AvdInfo avd : mAllAvdList) {
377 if (avd.getStatus() != AvdStatus.OK) {
381 mBrokenAvdList = list.toArray(new AvdInfo[list.size()]);
383 return mBrokenAvdList;
388 * Returns the {@link AvdInfo} matching the given <var>name</var>.
389 * @param name the name of the AVD to return
390 * @param validAvdOnly if <code>true</code>, only look through the list of valid AVDs.
391 * @return the matching AvdInfo or <code>null</code> if none were found.
393 public AvdInfo getAvd(String name, boolean validAvdOnly) {
395 for (AvdInfo info : getValidAvds()) {
396 if (info.getName().equals(name)) {
401 synchronized (mAllAvdList) {
402 for (AvdInfo info : mAllAvdList) {
403 if (info.getName().equals(name)) {
414 * Reloads the AVD list.
415 * @param log the log object to receive action logs. Cannot be null.
416 * @throws AndroidLocationException if there was an error finding the location of the
419 public void reloadAvds(ISdkLog log) throws AndroidLocationException {
420 // build the list in a temp list first, in case the method throws an exception.
421 // It's better than deleting the whole list before reading the new one.
422 ArrayList<AvdInfo> allList = new ArrayList<AvdInfo>();
423 buildAvdList(allList, log);
425 synchronized (mAllAvdList) {
427 mAllAvdList.addAll(allList);
428 mValidAvdList = mBrokenAvdList = null;
433 * Creates a new AVD. It is expected that there is no existing AVD with this name already.
435 * @param avdFolder the data folder for the AVD. It will be created as needed.
436 * @param name the name of the AVD
437 * @param target the target of the AVD
438 * @param skinName the name of the skin. Can be null. Must have been verified by caller.
439 * @param sdcard the parameter value for the sdCard. Can be null. This is either a path to
440 * an existing sdcard image or a sdcard size (\d+, \d+K, \dM).
441 * @param hardwareConfig the hardware setup for the AVD. Can be null to use defaults.
442 * @param removePrevious If true remove any previous files.
443 * @param log the log object to receive action logs. Cannot be null.
444 * @return The new {@link AvdInfo} in case of success (which has just been added to the
445 * internal list) or null in case of failure.
447 public AvdInfo createAvd(File avdFolder, String name, IAndroidTarget target,
448 String skinName, String sdcard, Map<String,String> hardwareConfig,
449 boolean removePrevious, ISdkLog log) {
451 throw new IllegalArgumentException("log cannot be null");
455 boolean needCleanup = false;
457 if (avdFolder.exists()) {
458 if (removePrevious) {
459 // AVD already exists and removePrevious is set, try to remove the
460 // directory's content first (but not the directory itself).
461 recursiveDelete(avdFolder);
463 // AVD shouldn't already exist if removePrevious is false.
465 "Folder %1$s is in the way. Use --force if you want to overwrite.",
466 avdFolder.getAbsolutePath());
470 // create the AVD folder.
474 // actually write the ini file
475 iniFile = createAvdIniFile(name, avdFolder, target);
477 // writes the userdata.img in it.
478 String imagePath = target.getPath(IAndroidTarget.IMAGES);
479 File userdataSrc = new File(imagePath, USERDATA_IMG);
481 if (userdataSrc.exists() == false && target.isPlatform() == false) {
482 imagePath = target.getParent().getPath(IAndroidTarget.IMAGES);
483 userdataSrc = new File(imagePath, USERDATA_IMG);
486 if (userdataSrc.exists() == false) {
487 log.error(null, "Unable to find a '%1$s' file to copy into the AVD folder.",
493 FileInputStream fis = new FileInputStream(userdataSrc);
495 File userdataDest = new File(avdFolder, USERDATA_IMG);
496 FileOutputStream fos = new FileOutputStream(userdataDest);
498 byte[] buffer = new byte[4096];
500 while ((count = fis.read(buffer)) != -1) {
501 fos.write(buffer, 0, count);
508 HashMap<String, String> values = new HashMap<String, String>();
510 if (setImagePathProperties(target, values, log) == false) {
516 if (skinName == null || skinName.length() == 0) {
517 skinName = target.getDefaultSkin();
520 if (NUMERIC_SKIN_SIZE.matcher(skinName).matches()) {
521 // Skin name is an actual screen resolution.
522 // Set skin.name for display purposes in the AVD manager and
523 // set skin.path for use by the emulator.
524 values.put(AVD_INI_SKIN_NAME, skinName);
525 values.put(AVD_INI_SKIN_PATH, skinName);
527 // get the path of the skin (relative to the SDK)
528 // assume skin name is valid
529 String skinPath = getSkinRelativePath(skinName, target, log);
530 if (skinPath == null) {
535 values.put(AVD_INI_SKIN_PATH, skinPath);
536 values.put(AVD_INI_SKIN_NAME, skinName);
539 if (sdcard != null && sdcard.length() > 0) {
540 File sdcardFile = new File(sdcard);
541 if (sdcardFile.isFile()) {
542 // sdcard value is an external sdcard, so we put its path into the config.ini
543 values.put(AVD_INI_SDCARD_PATH, sdcard);
545 // Sdcard is possibly a size. In that case we create a file called 'sdcard.img'
546 // in the AVD folder, and do not put any value in config.ini.
548 // First, check that it matches the pattern for sdcard size
549 Matcher m = SDCARD_SIZE_PATTERN.matcher(sdcard);
551 // create the sdcard.
552 sdcardFile = new File(avdFolder, SDCARD_IMG);
553 String path = sdcardFile.getAbsolutePath();
555 // execute mksdcard with the proper parameters.
556 File toolsFolder = new File(mSdkManager.getLocation(),
557 SdkConstants.FD_TOOLS);
558 File mkSdCard = new File(toolsFolder, SdkConstants.mkSdCardCmdName());
560 if (mkSdCard.isFile() == false) {
561 log.error(null, "'%1$s' is missing from the SDK tools folder.",
567 if (createSdCard(mkSdCard.getAbsolutePath(), sdcard, path, log) == false) {
569 return null; // mksdcard output has already been displayed, no need to
570 // output anything else.
573 // add a property containing the size of the sdcard for display purpose
574 // only when the dev does 'android list avd'
575 values.put(AVD_INI_SDCARD_SIZE, sdcard);
578 "'%1$s' is not recognized as a valid sdcard value.\n" +
579 "Value should be:\n" +
580 "1. path to an sdcard.\n" +
581 "2. size of the sdcard to create: <size>[K|M]",
589 // add the hardware config to the config file.
590 // priority order is:
591 // - values provided by the user
592 // - values provided by the skin
593 // - values provided by the target (add-on only).
594 // In order to follow this priority, we'll add the lowest priority values first and then
595 // override by higher priority values.
596 // In the case of a platform with override values from the user, the skin value might
597 // already be there, but it's ok.
599 HashMap<String, String> finalHardwareValues = new HashMap<String, String>();
601 File targetHardwareFile = new File(target.getLocation(), AvdManager.HARDWARE_INI);
602 if (targetHardwareFile.isFile()) {
603 Map<String, String> targetHardwareConfig = SdkManager.parsePropertyFile(
604 targetHardwareFile, log);
605 if (targetHardwareConfig != null) {
606 finalHardwareValues.putAll(targetHardwareConfig);
607 values.putAll(targetHardwareConfig);
611 // get the hardware properties for this skin
612 File skinFolder = getSkinPath(skinName, target);
613 File skinHardwareFile = new File(skinFolder, AvdManager.HARDWARE_INI);
614 if (skinHardwareFile.isFile()) {
615 Map<String, String> skinHardwareConfig = SdkManager.parsePropertyFile(
616 skinHardwareFile, log);
617 if (skinHardwareConfig != null) {
618 finalHardwareValues.putAll(skinHardwareConfig);
619 values.putAll(skinHardwareConfig);
623 // finally put the hardware provided by the user.
624 if (hardwareConfig != null) {
625 finalHardwareValues.putAll(hardwareConfig);
626 values.putAll(hardwareConfig);
629 File configIniFile = new File(avdFolder, CONFIG_INI);
630 writeIniFile(configIniFile, values);
632 if (target.isPlatform()) {
633 log.printf("Created AVD '%1$s' based on %2$s", name, target.getName());
635 log.printf("Created AVD '%1$s' based on %2$s (%3$s)", name,
636 target.getName(), target.getVendor());
639 // display the chosen hardware config
640 if (finalHardwareValues.size() > 0) {
641 log.printf(", with the following hardware config:\n");
642 for (Entry<String, String> entry : finalHardwareValues.entrySet()) {
643 log.printf("%s=%s\n",entry.getKey(), entry.getValue());
649 // create the AvdInfo object, and add it to the list
650 AvdInfo newAvdInfo = new AvdInfo(name,
651 avdFolder.getAbsolutePath(),
655 AvdInfo oldAvdInfo = getAvd(name, false /*validAvdOnly*/);
657 synchronized (mAllAvdList) {
658 if (oldAvdInfo != null && removePrevious) {
659 mAllAvdList.remove(oldAvdInfo);
661 mAllAvdList.add(newAvdInfo);
662 mValidAvdList = mBrokenAvdList = null;
665 if (removePrevious &&
666 newAvdInfo != null &&
667 oldAvdInfo != null &&
668 !oldAvdInfo.getPath().equals(newAvdInfo.getPath())) {
669 log.warning("Removing previous AVD directory at %s", oldAvdInfo.getPath());
670 // Remove the old data directory
671 File dir = new File(oldAvdInfo.getPath());
672 recursiveDelete(dir);
677 } catch (AndroidLocationException e) {
679 } catch (IOException e) {
683 if (iniFile != null && iniFile.exists()) {
687 recursiveDelete(avdFolder);
696 * Returns the path to the target images folder as a relative path to the SDK, if the folder
697 * is not empty. If the image folder is empty or does not exist, <code>null</code> is returned.
698 * @throws InvalidTargetPathException if the target image folder is not in the current SDK.
700 private String getImageRelativePath(IAndroidTarget target)
701 throws InvalidTargetPathException {
702 String imageFullPath = target.getPath(IAndroidTarget.IMAGES);
704 // make this path relative to the SDK location
705 String sdkLocation = mSdkManager.getLocation();
706 if (imageFullPath.startsWith(sdkLocation) == false) {
707 // this really really should not happen.
709 throw new InvalidTargetPathException("Target location is not inside the SDK.");
712 File folder = new File(imageFullPath);
713 if (folder.isDirectory()) {
714 String[] list = folder.list(new FilenameFilter() {
715 public boolean accept(File dir, String name) {
716 return IMAGE_NAME_PATTERN.matcher(name).matches();
720 if (list.length > 0) {
721 imageFullPath = imageFullPath.substring(sdkLocation.length());
722 if (imageFullPath.charAt(0) == File.separatorChar) {
723 imageFullPath = imageFullPath.substring(1);
726 return imageFullPath;
734 * Returns the path to the skin, as a relative path to the SDK.
735 * @param skinName The name of the skin to find. Case-sensitive.
736 * @param target The target where to find the skin.
737 * @param log the log object to receive action logs. Cannot be null.
739 public String getSkinRelativePath(String skinName, IAndroidTarget target, ISdkLog log) {
741 throw new IllegalArgumentException("log cannot be null");
744 // first look to see if the skin is in the target
745 File skin = getSkinPath(skinName, target);
747 // skin really does not exist!
748 if (skin.exists() == false) {
749 log.error(null, "Skin '%1$s' does not exist.", skinName);
754 String path = skin.getAbsolutePath();
756 // make this path relative to the SDK location
757 String sdkLocation = mSdkManager.getLocation();
758 if (path.startsWith(sdkLocation) == false) {
759 // this really really should not happen.
760 log.error(null, "Target location is not inside the SDK.");
765 path = path.substring(sdkLocation.length());
766 if (path.charAt(0) == File.separatorChar) {
767 path = path.substring(1);
773 * Returns the full absolute OS path to a skin specified by name for a given target.
774 * @param skinName The name of the skin to find. Case-sensitive.
775 * @param target The target where to find the skin.
776 * @return a {@link File} that may or may not actually exist.
778 public File getSkinPath(String skinName, IAndroidTarget target) {
779 String path = target.getPath(IAndroidTarget.SKINS);
780 File skin = new File(path, skinName);
782 if (skin.exists() == false && target.isPlatform() == false) {
783 target = target.getParent();
785 path = target.getPath(IAndroidTarget.SKINS);
786 skin = new File(path, skinName);
793 * Creates the ini file for an AVD.
795 * @param name of the AVD.
796 * @param avdFolder path for the data folder of the AVD.
797 * @param target of the AVD.
798 * @throws AndroidLocationException if there's a problem getting android root directory.
799 * @throws IOException if {@link File#getAbsolutePath()} fails.
801 private File createAvdIniFile(String name, File avdFolder, IAndroidTarget target)
802 throws AndroidLocationException, IOException {
803 HashMap<String, String> values = new HashMap<String, String>();
804 File iniFile = AvdInfo.getIniFile(name);
805 values.put(AVD_INFO_PATH, avdFolder.getAbsolutePath());
806 values.put(AVD_INFO_TARGET, target.hashString());
807 writeIniFile(iniFile, values);
813 * Creates the ini file for an AVD.
815 * @param info of the AVD.
816 * @throws AndroidLocationException if there's a problem getting android root directory.
817 * @throws IOException if {@link File#getAbsolutePath()} fails.
819 private File createAvdIniFile(AvdInfo info) throws AndroidLocationException, IOException {
820 return createAvdIniFile(info.getName(), new File(info.getPath()), info.getTarget());
824 * Actually deletes the files of an existing AVD.
826 * This also remove it from the manager's list, The caller does not need to
827 * call {@link #removeAvd(AvdInfo)} afterwards.
829 * This method is designed to somehow work with an unavailable AVD, that is an AVD that
830 * could not be loaded due to some error. That means this method still tries to remove
831 * the AVD ini file or its folder if it can be found. An error will be output if any of
832 * these operations fail.
834 * @param avdInfo the information on the AVD to delete
835 * @param log the log object to receive action logs. Cannot be null.
836 * @return True if the AVD was deleted with no error.
838 public boolean deleteAvd(AvdInfo avdInfo, ISdkLog log) {
840 boolean error = false;
842 File f = avdInfo.getIniFile();
843 if (f != null && f.exists()) {
844 log.printf("Deleting file %1$s\n", f.getCanonicalPath());
846 log.error(null, "Failed to delete %1$s\n", f.getCanonicalPath());
851 String path = avdInfo.getPath();
855 log.printf("Deleting folder %1$s\n", f.getCanonicalPath());
858 log.error(null, "Failed to delete %1$s\n", f.getCanonicalPath());
867 log.printf("\nAVD '%1$s' deleted with errors. See errors above.\n",
870 log.printf("\nAVD '%1$s' deleted.\n", avdInfo.getName());
874 } catch (AndroidLocationException e) {
876 } catch (IOException e) {
883 * Moves and/or rename an existing AVD and its files.
884 * This also change it in the manager's list.
886 * The caller should make sure the name or path given are valid, do not exist and are
887 * actually different than current values.
889 * @param avdInfo the information on the AVD to move.
890 * @param newName the new name of the AVD if non null.
891 * @param paramFolderPath the new data folder if non null.
892 * @param log the log object to receive action logs. Cannot be null.
893 * @return True if the move succeeded or there was nothing to do.
894 * If false, this method will have had already output error in the log.
896 public boolean moveAvd(AvdInfo avdInfo, String newName, String paramFolderPath, ISdkLog log) {
899 if (paramFolderPath != null) {
900 File f = new File(avdInfo.getPath());
901 log.warning("Moving '%1$s' to '%2$s'.", avdInfo.getPath(), paramFolderPath);
902 if (!f.renameTo(new File(paramFolderPath))) {
903 log.error(null, "Failed to move '%1$s' to '%2$s'.",
904 avdInfo.getPath(), paramFolderPath);
909 AvdInfo info = new AvdInfo(avdInfo.getName(), paramFolderPath,
910 avdInfo.getTargetHash(), avdInfo.getTarget(), avdInfo.getProperties());
911 replaceAvd(avdInfo, info);
913 // update the ini file
914 createAvdIniFile(avdInfo);
917 if (newName != null) {
918 File oldIniFile = avdInfo.getIniFile();
919 File newIniFile = AvdInfo.getIniFile(newName);
921 log.warning("Moving '%1$s' to '%2$s'.", oldIniFile.getPath(), newIniFile.getPath());
922 if (!oldIniFile.renameTo(newIniFile)) {
923 log.error(null, "Failed to move '%1$s' to '%2$s'.",
924 oldIniFile.getPath(), newIniFile.getPath());
929 AvdInfo info = new AvdInfo(newName, avdInfo.getPath(),
930 avdInfo.getTargetHash(), avdInfo.getTarget(), avdInfo.getProperties());
931 replaceAvd(avdInfo, info);
934 log.printf("AVD '%1$s' moved.\n", avdInfo.getName());
936 } catch (AndroidLocationException e) {
938 } catch (IOException e) {
942 // nothing to do or succeeded
947 * Helper method to recursively delete a folder's content (but not the folder itself).
949 * @throws SecurityException like {@link File#delete()} does if file/folder is not writable.
951 public void recursiveDelete(File folder) {
952 for (File f : folder.listFiles()) {
953 if (f.isDirectory()) {
954 recursiveDelete(folder);
961 * Returns a list of files that are potential AVD ini files.
963 * This lists the $HOME/.android/avd/<name>.ini files.
964 * Such files are properties file than then indicate where the AVD folder is located.
966 * @return A new {@link File} array or null. The array might be empty.
967 * @throws AndroidLocationException if there's a problem getting android root directory.
969 private File[] buildAvdFilesList() throws AndroidLocationException {
970 // get the Android prefs location.
971 String avdRoot = AndroidLocation.getFolder() + AndroidLocation.FOLDER_AVD;
973 // ensure folder validity.
974 File folder = new File(avdRoot);
975 if (folder.isFile()) {
976 throw new AndroidLocationException(
977 String.format("%1$s is not a valid folder.", avdRoot));
978 } else if (folder.exists() == false) {
979 // folder is not there, we create it and return
984 File[] avds = folder.listFiles(new FilenameFilter() {
985 public boolean accept(File parent, String name) {
986 if (INI_NAME_PATTERN.matcher(name).matches()) {
987 // check it's a file and not a folder
988 boolean isFile = new File(parent, name).isFile();
1000 * Computes the internal list of available AVDs
1001 * @param allList the list to contain all the AVDs
1002 * @param log the log object to receive action logs. Cannot be null.
1004 * @throws AndroidLocationException if there's a problem getting android root directory.
1006 private void buildAvdList(ArrayList<AvdInfo> allList, ISdkLog log)
1007 throws AndroidLocationException {
1008 File[] avds = buildAvdFilesList();
1010 for (File avd : avds) {
1011 AvdInfo info = parseAvdInfo(avd, log);
1020 * Parses an AVD .ini file to create an {@link AvdInfo}.
1022 * @param path The path to the AVD .ini file
1023 * @param log the log object to receive action logs. Cannot be null.
1024 * @return A new {@link AvdInfo} with an {@link AvdStatus} indicating whether this AVD is
1027 private AvdInfo parseAvdInfo(File path, ISdkLog log) {
1028 Map<String, String> map = SdkManager.parsePropertyFile(path, log);
1030 String avdPath = map.get(AVD_INFO_PATH);
1031 String targetHash = map.get(AVD_INFO_TARGET);
1033 IAndroidTarget target = null;
1034 File configIniFile = null;
1035 Map<String, String> properties = null;
1037 if (targetHash != null) {
1038 target = mSdkManager.getTargetFromHashString(targetHash);
1041 // load the AVD properties.
1042 if (avdPath != null) {
1043 configIniFile = new File(avdPath, CONFIG_INI);
1046 if (configIniFile != null) {
1047 properties = SdkManager.parsePropertyFile(configIniFile, log);
1051 String name = path.getName();
1052 Matcher matcher = INI_NAME_PATTERN.matcher(path.getName());
1053 if (matcher.matches()) {
1054 name = matcher.group(1);
1057 // check the image.sysdir are valid
1058 boolean validImageSysdir = true;
1059 if (properties != null) {
1060 String imageSysDir = properties.get(AVD_INI_IMAGES_1);
1061 if (imageSysDir != null) {
1062 File f = new File(mSdkManager.getLocation() + File.separator + imageSysDir);
1063 if (f.isDirectory() == false) {
1064 validImageSysdir = false;
1066 imageSysDir = properties.get(AVD_INI_IMAGES_2);
1067 if (imageSysDir != null) {
1068 f = new File(mSdkManager.getLocation() + File.separator + imageSysDir);
1069 if (f.isDirectory() == false) {
1070 validImageSysdir = false;
1079 if (avdPath == null) {
1080 status = AvdStatus.ERROR_PATH;
1081 } else if (configIniFile == null) {
1082 status = AvdStatus.ERROR_CONFIG;
1083 } else if (targetHash == null) {
1084 status = AvdStatus.ERROR_TARGET_HASH;
1085 } else if (target == null) {
1086 status = AvdStatus.ERROR_TARGET;
1087 } else if (properties == null) {
1088 status = AvdStatus.ERROR_PROPERTIES;
1089 } else if (validImageSysdir == false) {
1090 status = AvdStatus.ERROR_IMAGE_DIR;
1092 status = AvdStatus.OK;
1095 AvdInfo info = new AvdInfo(
1107 * Writes a .ini file from a set of properties, using UTF-8 encoding.
1109 * @param iniFile The file to generate.
1110 * @param values THe properties to place in the ini file.
1111 * @throws IOException if {@link FileWriter} fails to open, write or close the file.
1113 private static void writeIniFile(File iniFile, Map<String, String> values)
1114 throws IOException {
1115 OutputStreamWriter writer = new OutputStreamWriter(new FileOutputStream(iniFile),
1116 SdkConstants.INI_CHARSET);
1118 for (Entry<String, String> entry : values.entrySet()) {
1119 writer.write(String.format("%1$s=%2$s\n", entry.getKey(), entry.getValue()));
1125 * Invokes the tool to create a new SD card image file.
1127 * @param toolLocation The path to the mksdcard tool.
1128 * @param size The size of the new SD Card, compatible with {@link #SDCARD_SIZE_PATTERN}.
1129 * @param location The path of the new sdcard image file to generate.
1130 * @param log the log object to receive action logs. Cannot be null.
1131 * @return True if the sdcard could be created.
1133 private boolean createSdCard(String toolLocation, String size, String location, ISdkLog log) {
1135 String[] command = new String[3];
1136 command[0] = toolLocation;
1138 command[2] = location;
1139 Process process = Runtime.getRuntime().exec(command);
1141 ArrayList<String> errorOutput = new ArrayList<String>();
1142 ArrayList<String> stdOutput = new ArrayList<String>();
1143 int status = grabProcessOutput(process, errorOutput, stdOutput,
1144 true /* waitForReaders */);
1149 for (String error : errorOutput) {
1150 log.error(null, error);
1154 } catch (InterruptedException e) {
1155 // pass, print error below
1156 } catch (IOException e) {
1157 // pass, print error below
1160 log.error(null, "Failed to create the SD card.");
1165 * Gets the stderr/stdout outputs of a process and returns when the process is done.
1166 * Both <b>must</b> be read or the process will block on windows.
1167 * @param process The process to get the ouput from
1168 * @param errorOutput The array to store the stderr output. cannot be null.
1169 * @param stdOutput The array to store the stdout output. cannot be null.
1170 * @param waitforReaders if true, this will wait for the reader threads.
1171 * @return the process return code.
1172 * @throws InterruptedException
1174 private int grabProcessOutput(final Process process, final ArrayList<String> errorOutput,
1175 final ArrayList<String> stdOutput, boolean waitforReaders)
1176 throws InterruptedException {
1177 assert errorOutput != null;
1178 assert stdOutput != null;
1179 // read the lines as they come. if null is returned, it's
1180 // because the process finished
1181 Thread t1 = new Thread("") { //$NON-NLS-1$
1184 // create a buffer to read the stderr output
1185 InputStreamReader is = new InputStreamReader(process.getErrorStream());
1186 BufferedReader errReader = new BufferedReader(is);
1190 String line = errReader.readLine();
1192 errorOutput.add(line);
1197 } catch (IOException e) {
1203 Thread t2 = new Thread("") { //$NON-NLS-1$
1206 InputStreamReader is = new InputStreamReader(process.getInputStream());
1207 BufferedReader outReader = new BufferedReader(is);
1211 String line = outReader.readLine();
1213 stdOutput.add(line);
1218 } catch (IOException e) {
1227 // it looks like on windows process#waitFor() can return
1228 // before the thread have filled the arrays, so we wait for both threads and the
1230 if (waitforReaders) {
1233 } catch (InterruptedException e) {
1234 // nothing to do here
1238 } catch (InterruptedException e) {
1239 // nothing to do here
1243 // get the return code from the process
1244 return process.waitFor();
1248 * Removes an {@link AvdInfo} from the internal list.
1250 * @param avdInfo The {@link AvdInfo} to remove.
1251 * @return true if this {@link AvdInfo} was present and has been removed.
1253 public boolean removeAvd(AvdInfo avdInfo) {
1254 synchronized (mAllAvdList) {
1255 if (mAllAvdList.remove(avdInfo)) {
1256 mValidAvdList = mBrokenAvdList = null;
1265 * Updates an AVD with new path to the system image folders.
1266 * @param name the name of the AVD to update.
1267 * @param log the log object to receive action logs. Cannot be null.
1268 * @throws IOException
1270 public void updateAvd(String name, ISdkLog log) throws IOException {
1271 // find the AVD to update. It should be be in the broken list.
1273 synchronized (mAllAvdList) {
1274 for (AvdInfo info : mAllAvdList) {
1275 if (info.getName().equals(name)) {
1283 // not in the broken list, just return.
1284 log.error(null, "There is no Android Virtual Device named '%s'.", name);
1288 updateAvd(avd, log);
1293 * Updates an AVD with new path to the system image folders.
1294 * @param avd the AVD to update.
1295 * @param log the log object to receive action logs. Cannot be null.
1296 * @throws IOException
1298 public void updateAvd(AvdInfo avd, ISdkLog log) throws IOException {
1299 // get the properties. This is a unmodifiable Map.
1300 Map<String, String> oldProperties = avd.getProperties();
1303 Map<String, String> properties = new HashMap<String, String>();
1304 if (oldProperties != null) {
1305 properties.putAll(oldProperties);
1310 // create the path to the new system images.
1311 if (setImagePathProperties(avd.getTarget(), properties, log)) {
1312 if (properties.containsKey(AVD_INI_IMAGES_1)) {
1313 log.printf("Updated '%1$s' with value '%2$s'\n", AVD_INI_IMAGES_1,
1314 properties.get(AVD_INI_IMAGES_1));
1317 if (properties.containsKey(AVD_INI_IMAGES_2)) {
1318 log.printf("Updated '%1$s' with value '%2$s'\n", AVD_INI_IMAGES_2,
1319 properties.get(AVD_INI_IMAGES_2));
1322 status = AvdStatus.OK;
1324 log.error(null, "Unable to find non empty system images folders for %1$s",
1326 //FIXME: display paths to empty image folders?
1327 status = AvdStatus.ERROR_IMAGE_DIR;
1330 // now write the config file
1331 File configIniFile = new File(avd.getPath(), CONFIG_INI);
1332 writeIniFile(configIniFile, properties);
1334 // finally create a new AvdInfo for this unbroken avd and add it to the list.
1335 // instead of creating the AvdInfo object directly we reparse it, to detect other possible
1337 // FIXME: We may want to create this AvdInfo by reparsing the AVD instead. This could detect other errors.
1338 AvdInfo newAvd = new AvdInfo(
1341 avd.getTargetHash(),
1346 replaceAvd(avd, newAvd);
1350 * Sets the paths to the system images in a properties map.
1351 * @param target the target in which to find the system images.
1352 * @param properties the properties in which to set the paths.
1353 * @param log the log object to receive action logs. Cannot be null.
1354 * @return true if success, false if some path are missing.
1356 private boolean setImagePathProperties(IAndroidTarget target,
1357 Map<String, String> properties,
1359 properties.remove(AVD_INI_IMAGES_1);
1360 properties.remove(AVD_INI_IMAGES_2);
1363 String property = AVD_INI_IMAGES_1;
1365 // First the image folders of the target itself
1366 String imagePath = getImageRelativePath(target);
1367 if (imagePath != null) {
1368 properties.put(property, imagePath);
1369 property = AVD_INI_IMAGES_2;
1373 // If the target is an add-on we need to add the Platform image as a backup.
1374 IAndroidTarget parent = target.getParent();
1375 if (parent != null) {
1376 imagePath = getImageRelativePath(parent);
1377 if (imagePath != null) {
1378 properties.put(property, imagePath);
1382 // we need at least one path!
1383 return properties.containsKey(AVD_INI_IMAGES_1);
1384 } catch (InvalidTargetPathException e) {
1385 log.error(e, e.getMessage());
1392 * Replaces an old {@link AvdInfo} with a new one in the lists storing them.
1393 * @param oldAvd the {@link AvdInfo} to remove.
1394 * @param newAvd the {@link AvdInfo} to add.
1396 private void replaceAvd(AvdInfo oldAvd, AvdInfo newAvd) {
1397 synchronized (mAllAvdList) {
1398 mAllAvdList.remove(oldAvd);
1399 mAllAvdList.add(newAvd);
1400 mValidAvdList = mBrokenAvdList = null;