Home | History | Annotate | Download | only in avd
      1 /*
      2  * Copyright (C) 2008 The Android Open Source Project
      3  *
      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
      7  *
      8  *      http://www.apache.org/licenses/LICENSE-2.0
      9  *
     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.
     15  */
     16 
     17 package com.android.sdklib.internal.avd;
     18 
     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;
     26 
     27 import java.io.BufferedReader;
     28 import java.io.File;
     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;
     39 import java.util.Map;
     40 import java.util.Map.Entry;
     41 import java.util.regex.Matcher;
     42 import java.util.regex.Pattern;
     43 
     44 /**
     45  * Android Virtual Device Manager to manage AVDs.
     46  */
     47 public final class AvdManager {
     48 
     49     /**
     50      * Exception thrown when something is wrong with a target path.
     51      */
     52     private final static class InvalidTargetPathException extends Exception {
     53         private static final long serialVersionUID = 1L;
     54 
     55         InvalidTargetPathException(String message) {
     56             super(message);
     57         }
     58     }
     59 
     60     public static final String AVD_FOLDER_EXTENSION = ".avd";  //$NON-NLS-1$
     61 
     62     public final static String AVD_INFO_PATH = "path";         //$NON-NLS-1$
     63     public final static String AVD_INFO_TARGET = "target";     //$NON-NLS-1$
     64 
     65     /**
     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.
     68      *
     69      * @see #NUMERIC_SKIN_SIZE
     70      */
     71     public final static String AVD_INI_SKIN_PATH = "skin.path"; //$NON-NLS-1$
     72     /**
     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.
     77      */
     78     public final static String AVD_INI_SKIN_NAME = "skin.name"; //$NON-NLS-1$
     79     /**
     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
     82      * a file.
     83      *
     84      * @see #SDCARD_IMG
     85      */
     86     public final static String AVD_INI_SDCARD_PATH = "sdcard.path"; //$NON-NLS-1$
     87     /**
     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.
     90      *
     91      * @see #SDCARD_SIZE_PATTERN
     92      */
     93     public final static String AVD_INI_SDCARD_SIZE = "sdcard.size"; //$NON-NLS-1$
     94     /**
     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.
     98      * <p/>
     99      * The emulator looks at {@link #AVD_INI_IMAGES_1} before {@link #AVD_INI_IMAGES_2}.
    100      */
    101     public final static String AVD_INI_IMAGES_1 = "image.sysdir.1"; //$NON-NLS-1$
    102     /**
    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.
    105      *
    106      * @see #AVD_INI_IMAGES_1
    107      */
    108     public final static String AVD_INI_IMAGES_2 = "image.sysdir.2"; //$NON-NLS-1$
    109 
    110     /**
    111      * Pattern to match pixel-sized skin "names", e.g. "320x480".
    112      */
    113     public final static Pattern NUMERIC_SKIN_SIZE = Pattern.compile("([0-9]{2,})x([0-9]{2,})"); //$NON-NLS-1$
    114 
    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$
    118 
    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);
    123 
    124     private final static Pattern IMAGE_NAME_PATTERN = Pattern.compile("(.+)\\.img$", //$NON-NLS-1$
    125             Pattern.CASE_INSENSITIVE);
    126 
    127     /**
    128      * Pattern for matching SD Card sizes, e.g. "4K" or "16M".
    129      */
    130     public final static Pattern SDCARD_SIZE_PATTERN = Pattern.compile("(\\d+)([MK])"); //$NON-NLS-1$
    131 
    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$
    134 
    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$
    137 
    138     public final static String HARDWARE_INI = "hardware.ini"; //$NON-NLS-1$
    139 
    140     /** An immutable structure describing an Android Virtual Device. */
    141     public static final class AvdInfo implements Comparable<AvdInfo> {
    142 
    143         /**
    144          * Status for an {@link AvdInfo}. Indicates whether or not this AVD is valid.
    145          */
    146         public static enum AvdStatus {
    147             /** No error */
    148             OK,
    149             /** Missing 'path' property in the ini file */
    150             ERROR_PATH,
    151             /** Missing config.ini file in the AVD data folder */
    152             ERROR_CONFIG,
    153             /** Missing 'target' property in the ini file */
    154             ERROR_TARGET_HASH,
    155             /** Target was not resolved from its hash */
    156             ERROR_TARGET,
    157             /** Unable to parse config.ini */
    158             ERROR_PROPERTIES,
    159             /** System Image folder in config.ini doesn't exist */
    160             ERROR_IMAGE_DIR;
    161         }
    162 
    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;
    169 
    170         /**
    171          * Creates a new valid AVD info. Values are immutable.
    172          * <p/>
    173          * Such an AVD is available and can be used.
    174          * The error string is set to null.
    175          *
    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.
    181          */
    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);
    185         }
    186 
    187         /**
    188          * Creates a new <em>invalid</em> AVD info. Values are immutable.
    189          * <p/>
    190          * Such an AVD is not complete and cannot be used.
    191          * The error string must be non-null.
    192          *
    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.
    199          */
    200         public AvdInfo(String name, String path, String targetHash, IAndroidTarget target,
    201                 Map<String, String> properties, AvdStatus status) {
    202             mName = name;
    203             mPath = path;
    204             mTargetHash = targetHash;
    205             mTarget = target;
    206             mProperties = properties == null ? null : Collections.unmodifiableMap(properties);
    207             mStatus = status;
    208         }
    209 
    210         /** Returns the name of the AVD. */
    211         public String getName() {
    212             return mName;
    213         }
    214 
    215         /** Returns the path of the AVD data directory. */
    216         public String getPath() {
    217             return mPath;
    218         }
    219 
    220         /**
    221          * Returns the target hash string.
    222          */
    223         public String getTargetHash() {
    224             return mTargetHash;
    225         }
    226 
    227         /** Returns the target of the AVD, or <code>null</code> if it has not been resolved. */
    228         public IAndroidTarget getTarget() {
    229             return mTarget;
    230         }
    231 
    232         /** Returns the {@link AvdStatus} of the receiver. */
    233         public AvdStatus getStatus() {
    234             return mStatus;
    235         }
    236 
    237         /**
    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.
    240          */
    241         public static File getIniFile(String name) throws AndroidLocationException {
    242             String avdRoot;
    243             avdRoot = getBaseAvdFolder();
    244             return new File(avdRoot, name + INI_EXTENSION);
    245         }
    246 
    247         /**
    248          * Returns the .ini {@link File} for this AVD.
    249          * @throws AndroidLocationException if there's a problem getting android root directory.
    250          */
    251         public File getIniFile() throws AndroidLocationException {
    252             return getIniFile(mName);
    253         }
    254 
    255         /**
    256          * Helper method that returns the Config {@link File} for a given AVD name.
    257          */
    258         public static File getConfigFile(String path) {
    259             return new File(path, CONFIG_INI);
    260         }
    261 
    262         /**
    263          * Returns the Config {@link File} for this AVD.
    264          */
    265         public File getConfigFile() {
    266             return getConfigFile(mPath);
    267         }
    268 
    269         /**
    270          * Returns an unmodifiable map of properties for the AVD. This can be null.
    271          */
    272         public Map<String, String> getProperties() {
    273             return mProperties;
    274         }
    275 
    276         /**
    277          * Returns the error message for the AVD or <code>null</code> if {@link #getStatus()}
    278          * returns {@link AvdStatus#OK}
    279          */
    280         public String getErrorMessage() {
    281             try {
    282                 switch (mStatus) {
    283                     case ERROR_PATH:
    284                         return String.format("Missing AVD 'path' property in %1$s", getIniFile());
    285                     case ERROR_CONFIG:
    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());
    289                     case ERROR_TARGET:
    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",
    294                                 getConfigFile());
    295                     case ERROR_IMAGE_DIR:
    296                         return String.format(
    297                                 "Invalid value in image.sysdir. Run 'android update avd -n %1$s'",
    298                                 mName);
    299                     case OK:
    300                         assert false;
    301                         return null;
    302                 }
    303             } catch (AndroidLocationException e) {
    304                 return "Unable to get HOME folder.";
    305             }
    306 
    307             return null;
    308         }
    309 
    310         /**
    311          * Returns whether an emulator is currently running the AVD.
    312          */
    313         public boolean isRunning() {
    314             File f = new File(mPath, "userdata-qemu.img.lock");
    315             return f.isFile();
    316         }
    317 
    318         /**
    319          * Compares this object with the specified object for order. Returns a
    320          * negative integer, zero, or a positive integer as this object is less
    321          * than, equal to, or greater than the specified object.
    322          *
    323          * @param o the Object to be compared.
    324          * @return a negative integer, zero, or a positive integer as this object is
    325          *         less than, equal to, or greater than the specified object.
    326          */
    327         public int compareTo(AvdInfo o) {
    328             // first handle possible missing targets (if the AVD failed to load for
    329             // unresolved targets.
    330             if (mTarget == null) {
    331                 return +1;
    332             } else if (o.mTarget == null) {
    333                 return -1;
    334             }
    335 
    336             // then compare the targets
    337             int targetDiff = mTarget.compareTo(o.mTarget);
    338 
    339             if (targetDiff == 0) {
    340                 // same target? compare on the avd name
    341                 return mName.compareTo(o.mName);
    342             }
    343 
    344             return targetDiff;
    345         }
    346     }
    347 
    348     private final ArrayList<AvdInfo> mAllAvdList = new ArrayList<AvdInfo>();
    349     private AvdInfo[] mValidAvdList;
    350     private AvdInfo[] mBrokenAvdList;
    351     private final SdkManager mSdkManager;
    352 
    353     /**
    354      * Returns the base folder where AVDs are created.
    355      *
    356      * @throws AndroidLocationException
    357      */
    358     public static String getBaseAvdFolder() throws AndroidLocationException {
    359         return AndroidLocation.getFolder() + AndroidLocation.FOLDER_AVD;
    360     }
    361 
    362     /**
    363      * Creates an AVD Manager for a given SDK represented by a {@link SdkManager}.
    364      * @param sdkManager The SDK.
    365      * @param log The log object to receive the log of the initial loading of the AVDs.
    366      *            This log object is not kept by this instance of AvdManager and each
    367      *            method takes its own logger. The rationale is that the AvdManager
    368      *            might be called from a variety of context, each with different
    369      *            logging needs. Cannot be null.
    370      * @throws AndroidLocationException
    371      */
    372     public AvdManager(SdkManager sdkManager, ISdkLog log) throws AndroidLocationException {
    373         mSdkManager = sdkManager;
    374         buildAvdList(mAllAvdList, log);
    375     }
    376 
    377     /**
    378      * Returns the {@link SdkManager} associated with the {@link AvdManager}.
    379      */
    380     public SdkManager getSdkManager() {
    381         return mSdkManager;
    382     }
    383 
    384     /**
    385      * Returns all the existing AVDs.
    386      * @return a newly allocated array containing all the AVDs.
    387      */
    388     public AvdInfo[] getAllAvds() {
    389         synchronized (mAllAvdList) {
    390             return mAllAvdList.toArray(new AvdInfo[mAllAvdList.size()]);
    391         }
    392     }
    393 
    394     /**
    395      * Returns all the valid AVDs.
    396      * @return a newly allocated array containing all valid the AVDs.
    397      */
    398     public AvdInfo[] getValidAvds() {
    399         synchronized (mAllAvdList) {
    400             if (mValidAvdList == null) {
    401                 ArrayList<AvdInfo> list = new ArrayList<AvdInfo>();
    402                 for (AvdInfo avd : mAllAvdList) {
    403                     if (avd.getStatus() == AvdStatus.OK) {
    404                         list.add(avd);
    405                     }
    406                 }
    407 
    408                 mValidAvdList = list.toArray(new AvdInfo[list.size()]);
    409             }
    410             return mValidAvdList;
    411         }
    412     }
    413 
    414     /**
    415      * Returns all the broken AVDs.
    416      * @return a newly allocated array containing all the broken AVDs.
    417      */
    418     public AvdInfo[] getBrokenAvds() {
    419         synchronized (mAllAvdList) {
    420             if (mBrokenAvdList == null) {
    421                 ArrayList<AvdInfo> list = new ArrayList<AvdInfo>();
    422                 for (AvdInfo avd : mAllAvdList) {
    423                     if (avd.getStatus() != AvdStatus.OK) {
    424                         list.add(avd);
    425                     }
    426                 }
    427                 mBrokenAvdList = list.toArray(new AvdInfo[list.size()]);
    428             }
    429             return mBrokenAvdList;
    430         }
    431     }
    432 
    433     /**
    434      * Returns the {@link AvdInfo} matching the given <var>name</var>.
    435      * <p/>
    436      * The search is case-insensitive.
    437      *
    438      * @param name the name of the AVD to return
    439      * @param validAvdOnly if <code>true</code>, only look through the list of valid AVDs.
    440      * @return the matching AvdInfo or <code>null</code> if none were found.
    441      */
    442     public AvdInfo getAvd(String name, boolean validAvdOnly) {
    443 
    444         boolean ignoreCase = SdkConstants.currentPlatform() == SdkConstants.PLATFORM_WINDOWS;
    445 
    446         if (validAvdOnly) {
    447             for (AvdInfo info : getValidAvds()) {
    448                 String name2 = info.getName();
    449                 if (name2.equals(name) || (ignoreCase && name2.equalsIgnoreCase(name))) {
    450                     return info;
    451                 }
    452             }
    453         } else {
    454             synchronized (mAllAvdList) {
    455                 for (AvdInfo info : mAllAvdList) {
    456                     String name2 = info.getName();
    457                     if (name2.equals(name) || (ignoreCase && name2.equalsIgnoreCase(name))) {
    458                         return info;
    459                     }
    460                 }
    461             }
    462         }
    463 
    464         return null;
    465     }
    466 
    467     /**
    468      * Reloads the AVD list.
    469      * @param log the log object to receive action logs. Cannot be null.
    470      * @throws AndroidLocationException if there was an error finding the location of the
    471      * AVD folder.
    472      */
    473     public void reloadAvds(ISdkLog log) throws AndroidLocationException {
    474         // build the list in a temp list first, in case the method throws an exception.
    475         // It's better than deleting the whole list before reading the new one.
    476         ArrayList<AvdInfo> allList = new ArrayList<AvdInfo>();
    477         buildAvdList(allList, log);
    478 
    479         synchronized (mAllAvdList) {
    480             mAllAvdList.clear();
    481             mAllAvdList.addAll(allList);
    482             mValidAvdList = mBrokenAvdList = null;
    483         }
    484     }
    485 
    486     /**
    487      * Creates a new AVD. It is expected that there is no existing AVD with this name already.
    488      *
    489      * @param avdFolder the data folder for the AVD. It will be created as needed.
    490      * @param name the name of the AVD
    491      * @param target the target of the AVD
    492      * @param skinName the name of the skin. Can be null. Must have been verified by caller.
    493      * @param sdcard the parameter value for the sdCard. Can be null. This is either a path to
    494      *        an existing sdcard image or a sdcard size (\d+, \d+K, \dM).
    495      * @param hardwareConfig the hardware setup for the AVD. Can be null to use defaults.
    496      * @param removePrevious If true remove any previous files.
    497      * @param log the log object to receive action logs. Cannot be null.
    498      * @return The new {@link AvdInfo} in case of success (which has just been added to the
    499      *         internal list) or null in case of failure.
    500      */
    501     public AvdInfo createAvd(File avdFolder, String name, IAndroidTarget target,
    502             String skinName, String sdcard, Map<String,String> hardwareConfig,
    503             boolean removePrevious, ISdkLog log) {
    504         if (log == null) {
    505             throw new IllegalArgumentException("log cannot be null");
    506         }
    507 
    508         File iniFile = null;
    509         boolean needCleanup = false;
    510         try {
    511             if (avdFolder.exists()) {
    512                 if (removePrevious) {
    513                     // AVD already exists and removePrevious is set, try to remove the
    514                     // directory's content first (but not the directory itself).
    515                     try {
    516                         deleteContentOf(avdFolder);
    517                     } catch (SecurityException e) {
    518                         log.error(e, "Failed to delete %1$s", avdFolder.getAbsolutePath());
    519                     }
    520                 } else {
    521                     // AVD shouldn't already exist if removePrevious is false.
    522                     log.error(null,
    523                             "Folder %1$s is in the way. Use --force if you want to overwrite.",
    524                             avdFolder.getAbsolutePath());
    525                     return null;
    526                 }
    527             } else {
    528                 // create the AVD folder.
    529                 avdFolder.mkdir();
    530             }
    531 
    532             // actually write the ini file
    533             iniFile = createAvdIniFile(name, avdFolder, target);
    534 
    535             // writes the userdata.img in it.
    536             String imagePath = target.getPath(IAndroidTarget.IMAGES);
    537             File userdataSrc = new File(imagePath, USERDATA_IMG);
    538 
    539             if (userdataSrc.exists() == false && target.isPlatform() == false) {
    540                 imagePath = target.getParent().getPath(IAndroidTarget.IMAGES);
    541                 userdataSrc = new File(imagePath, USERDATA_IMG);
    542             }
    543 
    544             if (userdataSrc.exists() == false) {
    545                 log.error(null, "Unable to find a '%1$s' file to copy into the AVD folder.",
    546                         USERDATA_IMG);
    547                 needCleanup = true;
    548                 return null;
    549             }
    550 
    551             FileInputStream fis = new FileInputStream(userdataSrc);
    552 
    553             File userdataDest = new File(avdFolder, USERDATA_IMG);
    554             FileOutputStream fos = new FileOutputStream(userdataDest);
    555 
    556             byte[] buffer = new byte[4096];
    557             int count;
    558             while ((count = fis.read(buffer)) != -1) {
    559                 fos.write(buffer, 0, count);
    560             }
    561 
    562             fos.close();
    563             fis.close();
    564 
    565             // Config file.
    566             HashMap<String, String> values = new HashMap<String, String>();
    567 
    568             if (setImagePathProperties(target, values, log) == false) {
    569                 needCleanup = true;
    570                 return null;
    571             }
    572 
    573             // Now the skin.
    574             if (skinName == null || skinName.length() == 0) {
    575                 skinName = target.getDefaultSkin();
    576             }
    577 
    578             if (NUMERIC_SKIN_SIZE.matcher(skinName).matches()) {
    579                 // Skin name is an actual screen resolution.
    580                 // Set skin.name for display purposes in the AVD manager and
    581                 // set skin.path for use by the emulator.
    582                 values.put(AVD_INI_SKIN_NAME, skinName);
    583                 values.put(AVD_INI_SKIN_PATH, skinName);
    584             } else {
    585                 // get the path of the skin (relative to the SDK)
    586                 // assume skin name is valid
    587                 String skinPath = getSkinRelativePath(skinName, target, log);
    588                 if (skinPath == null) {
    589                     needCleanup = true;
    590                     return null;
    591                 }
    592 
    593                 values.put(AVD_INI_SKIN_PATH, skinPath);
    594                 values.put(AVD_INI_SKIN_NAME, skinName);
    595             }
    596 
    597             if (sdcard != null && sdcard.length() > 0) {
    598                 File sdcardFile = new File(sdcard);
    599                 if (sdcardFile.isFile()) {
    600                     // sdcard value is an external sdcard, so we put its path into the config.ini
    601                     values.put(AVD_INI_SDCARD_PATH, sdcard);
    602                 } else {
    603                     // Sdcard is possibly a size. In that case we create a file called 'sdcard.img'
    604                     // in the AVD folder, and do not put any value in config.ini.
    605 
    606                     // First, check that it matches the pattern for sdcard size
    607                     Matcher m = SDCARD_SIZE_PATTERN.matcher(sdcard);
    608                     if (m.matches()) {
    609                         // get the sdcard values for checks
    610                         int sdcardSize = Integer.parseInt(m.group(1)); // pattern check
    611                                                                        // above makes
    612                                                                        // this unlikely to fail
    613                         String sdcardSizeModifier = m.group(2);
    614                         if ("K".equals(sdcardSizeModifier)) {
    615                             sdcardSize *= 1024;
    616                         } else { // must be "M" per the pattern
    617                             sdcardSize *= 1024 * 1024;
    618                         }
    619 
    620                         if (sdcardSize < 9 * 1024 * 1024) {
    621                             log.error(null, "SD Card size must be at least 9MB");
    622                             needCleanup = true;
    623                             return null;
    624                         }
    625 
    626                         // create the sdcard.
    627                         sdcardFile = new File(avdFolder, SDCARD_IMG);
    628                         String path = sdcardFile.getAbsolutePath();
    629 
    630                         // execute mksdcard with the proper parameters.
    631                         File toolsFolder = new File(mSdkManager.getLocation(),
    632                                 SdkConstants.FD_TOOLS);
    633                         File mkSdCard = new File(toolsFolder, SdkConstants.mkSdCardCmdName());
    634 
    635                         if (mkSdCard.isFile() == false) {
    636                             log.error(null, "'%1$s' is missing from the SDK tools folder.",
    637                                     mkSdCard.getName());
    638                             needCleanup = true;
    639                             return null;
    640                         }
    641 
    642                         if (createSdCard(mkSdCard.getAbsolutePath(), sdcard, path, log) == false) {
    643                             needCleanup = true;
    644                             return null; // mksdcard output has already been displayed, no need to
    645                                          // output anything else.
    646                         }
    647 
    648                         // add a property containing the size of the sdcard for display purpose
    649                         // only when the dev does 'android list avd'
    650                         values.put(AVD_INI_SDCARD_SIZE, sdcard);
    651                     } else {
    652                         log.error(null, "'%1$s' is not recognized as a valid sdcard value.\n"
    653                                 + "Value should be:\n" + "1. path to an sdcard.\n"
    654                                 + "2. size of the sdcard to create: <size>[K|M]", sdcard);
    655                         needCleanup = true;
    656                         return null;
    657                     }
    658                 }
    659             }
    660 
    661             // add the hardware config to the config file.
    662             // priority order is:
    663             // - values provided by the user
    664             // - values provided by the skin
    665             // - values provided by the target (add-on only).
    666             // In order to follow this priority, we'll add the lowest priority values first and then
    667             // override by higher priority values.
    668             // In the case of a platform with override values from the user, the skin value might
    669             // already be there, but it's ok.
    670 
    671             HashMap<String, String> finalHardwareValues = new HashMap<String, String>();
    672 
    673             File targetHardwareFile = new File(target.getLocation(), AvdManager.HARDWARE_INI);
    674             if (targetHardwareFile.isFile()) {
    675                 Map<String, String> targetHardwareConfig = SdkManager.parsePropertyFile(
    676                         targetHardwareFile, log);
    677                 if (targetHardwareConfig != null) {
    678                     finalHardwareValues.putAll(targetHardwareConfig);
    679                     values.putAll(targetHardwareConfig);
    680                 }
    681             }
    682 
    683             // get the hardware properties for this skin
    684             File skinFolder = getSkinPath(skinName, target);
    685             File skinHardwareFile = new File(skinFolder, AvdManager.HARDWARE_INI);
    686             if (skinHardwareFile.isFile()) {
    687                 Map<String, String> skinHardwareConfig = SdkManager.parsePropertyFile(
    688                         skinHardwareFile, log);
    689                 if (skinHardwareConfig != null) {
    690                     finalHardwareValues.putAll(skinHardwareConfig);
    691                     values.putAll(skinHardwareConfig);
    692                 }
    693             }
    694 
    695             // finally put the hardware provided by the user.
    696             if (hardwareConfig != null) {
    697                 finalHardwareValues.putAll(hardwareConfig);
    698                 values.putAll(hardwareConfig);
    699             }
    700 
    701             File configIniFile = new File(avdFolder, CONFIG_INI);
    702             writeIniFile(configIniFile, values);
    703 
    704             // Generate the log report first because we want to control where line breaks
    705             // are located when generating the hardware config list.
    706             StringBuilder report = new StringBuilder();
    707 
    708             if (target.isPlatform()) {
    709                 report.append(String.format("Created AVD '%1$s' based on %2$s",
    710                         name, target.getName()));
    711             } else {
    712                 report.append(String.format("Created AVD '%1$s' based on %2$s (%3$s)", name,
    713                         target.getName(), target.getVendor()));
    714             }
    715 
    716             // display the chosen hardware config
    717             if (finalHardwareValues.size() > 0) {
    718                 report.append(",\nwith the following hardware config:\n");
    719                 for (Entry<String, String> entry : finalHardwareValues.entrySet()) {
    720                     report.append(String.format("%s=%s\n",entry.getKey(), entry.getValue()));
    721                 }
    722             } else {
    723                 report.append("\n");
    724             }
    725 
    726             log.printf(report.toString());
    727 
    728             // create the AvdInfo object, and add it to the list
    729             AvdInfo newAvdInfo = new AvdInfo(name,
    730                     avdFolder.getAbsolutePath(),
    731                     target.hashString(),
    732                     target, values);
    733 
    734             AvdInfo oldAvdInfo = getAvd(name, false /*validAvdOnly*/);
    735 
    736             synchronized (mAllAvdList) {
    737                 if (oldAvdInfo != null && removePrevious) {
    738                     mAllAvdList.remove(oldAvdInfo);
    739                 }
    740                 mAllAvdList.add(newAvdInfo);
    741                 mValidAvdList = mBrokenAvdList = null;
    742             }
    743 
    744             if (removePrevious &&
    745                     newAvdInfo != null &&
    746                     oldAvdInfo != null &&
    747                     !oldAvdInfo.getPath().equals(newAvdInfo.getPath())) {
    748                 log.warning("Removing previous AVD directory at %s", oldAvdInfo.getPath());
    749                 // Remove the old data directory
    750                 File dir = new File(oldAvdInfo.getPath());
    751                 try {
    752                     deleteContentOf(dir);
    753                     dir.delete();
    754                 } catch (SecurityException e) {
    755                     log.error(e, "Failed to delete %1$s", dir.getAbsolutePath());
    756                 }
    757             }
    758 
    759             return newAvdInfo;
    760         } catch (AndroidLocationException e) {
    761             log.error(e, null);
    762         } catch (IOException e) {
    763             log.error(e, null);
    764         } catch (SecurityException e) {
    765             log.error(e, null);
    766         } finally {
    767             if (needCleanup) {
    768                 if (iniFile != null && iniFile.exists()) {
    769                     iniFile.delete();
    770                 }
    771 
    772                 try {
    773                     deleteContentOf(avdFolder);
    774                     avdFolder.delete();
    775                 } catch (SecurityException e) {
    776                     log.error(e, "Failed to delete %1$s", avdFolder.getAbsolutePath());
    777                 }
    778             }
    779         }
    780 
    781         return null;
    782     }
    783 
    784     /**
    785      * Returns the path to the target images folder as a relative path to the SDK, if the folder
    786      * is not empty. If the image folder is empty or does not exist, <code>null</code> is returned.
    787      * @throws InvalidTargetPathException if the target image folder is not in the current SDK.
    788      */
    789     private String getImageRelativePath(IAndroidTarget target)
    790             throws InvalidTargetPathException {
    791         String imageFullPath = target.getPath(IAndroidTarget.IMAGES);
    792 
    793         // make this path relative to the SDK location
    794         String sdkLocation = mSdkManager.getLocation();
    795         if (imageFullPath.startsWith(sdkLocation) == false) {
    796             // this really really should not happen.
    797             assert false;
    798             throw new InvalidTargetPathException("Target location is not inside the SDK.");
    799         }
    800 
    801         File folder = new File(imageFullPath);
    802         if (folder.isDirectory()) {
    803             String[] list = folder.list(new FilenameFilter() {
    804                 public boolean accept(File dir, String name) {
    805                     return IMAGE_NAME_PATTERN.matcher(name).matches();
    806                 }
    807             });
    808 
    809             if (list.length > 0) {
    810                 imageFullPath = imageFullPath.substring(sdkLocation.length());
    811                 if (imageFullPath.charAt(0) == File.separatorChar) {
    812                     imageFullPath = imageFullPath.substring(1);
    813                 }
    814 
    815                 return imageFullPath;
    816             }
    817         }
    818 
    819         return null;
    820     }
    821 
    822     /**
    823      * Returns the path to the skin, as a relative path to the SDK.
    824      * @param skinName The name of the skin to find. Case-sensitive.
    825      * @param target The target where to find the skin.
    826      * @param log the log object to receive action logs. Cannot be null.
    827      */
    828     public String getSkinRelativePath(String skinName, IAndroidTarget target, ISdkLog log) {
    829         if (log == null) {
    830             throw new IllegalArgumentException("log cannot be null");
    831         }
    832 
    833         // first look to see if the skin is in the target
    834         File skin = getSkinPath(skinName, target);
    835 
    836         // skin really does not exist!
    837         if (skin.exists() == false) {
    838             log.error(null, "Skin '%1$s' does not exist.", skinName);
    839             return null;
    840         }
    841 
    842         // get the skin path
    843         String path = skin.getAbsolutePath();
    844 
    845         // make this path relative to the SDK location
    846         String sdkLocation = mSdkManager.getLocation();
    847         if (path.startsWith(sdkLocation) == false) {
    848             // this really really should not happen.
    849             log.error(null, "Target location is not inside the SDK.");
    850             assert false;
    851             return null;
    852         }
    853 
    854         path = path.substring(sdkLocation.length());
    855         if (path.charAt(0) == File.separatorChar) {
    856             path = path.substring(1);
    857         }
    858         return path;
    859     }
    860 
    861     /**
    862      * Returns the full absolute OS path to a skin specified by name for a given target.
    863      * @param skinName The name of the skin to find. Case-sensitive.
    864      * @param target The target where to find the skin.
    865      * @return a {@link File} that may or may not actually exist.
    866      */
    867     public File getSkinPath(String skinName, IAndroidTarget target) {
    868         String path = target.getPath(IAndroidTarget.SKINS);
    869         File skin = new File(path, skinName);
    870 
    871         if (skin.exists() == false && target.isPlatform() == false) {
    872             target = target.getParent();
    873 
    874             path = target.getPath(IAndroidTarget.SKINS);
    875             skin = new File(path, skinName);
    876         }
    877 
    878         return skin;
    879     }
    880 
    881     /**
    882      * Creates the ini file for an AVD.
    883      *
    884      * @param name of the AVD.
    885      * @param avdFolder path for the data folder of the AVD.
    886      * @param target of the AVD.
    887      * @throws AndroidLocationException if there's a problem getting android root directory.
    888      * @throws IOException if {@link File#getAbsolutePath()} fails.
    889      */
    890     private File createAvdIniFile(String name, File avdFolder, IAndroidTarget target)
    891             throws AndroidLocationException, IOException {
    892         HashMap<String, String> values = new HashMap<String, String>();
    893         File iniFile = AvdInfo.getIniFile(name);
    894         values.put(AVD_INFO_PATH, avdFolder.getAbsolutePath());
    895         values.put(AVD_INFO_TARGET, target.hashString());
    896         writeIniFile(iniFile, values);
    897 
    898         return iniFile;
    899     }
    900 
    901     /**
    902      * Creates the ini file for an AVD.
    903      *
    904      * @param info of the AVD.
    905      * @throws AndroidLocationException if there's a problem getting android root directory.
    906      * @throws IOException if {@link File#getAbsolutePath()} fails.
    907      */
    908     private File createAvdIniFile(AvdInfo info) throws AndroidLocationException, IOException {
    909         return createAvdIniFile(info.getName(), new File(info.getPath()), info.getTarget());
    910     }
    911 
    912     /**
    913      * Actually deletes the files of an existing AVD.
    914      * <p/>
    915      * This also remove it from the manager's list, The caller does not need to
    916      * call {@link #removeAvd(AvdInfo)} afterwards.
    917      * <p/>
    918      * This method is designed to somehow work with an unavailable AVD, that is an AVD that
    919      * could not be loaded due to some error. That means this method still tries to remove
    920      * the AVD ini file or its folder if it can be found. An error will be output if any of
    921      * these operations fail.
    922      *
    923      * @param avdInfo the information on the AVD to delete
    924      * @param log the log object to receive action logs. Cannot be null.
    925      * @return True if the AVD was deleted with no error.
    926      */
    927     public boolean deleteAvd(AvdInfo avdInfo, ISdkLog log) {
    928         try {
    929             boolean error = false;
    930 
    931             File f = avdInfo.getIniFile();
    932             if (f != null && f.exists()) {
    933                 log.printf("Deleting file %1$s\n", f.getCanonicalPath());
    934                 if (!f.delete()) {
    935                     log.error(null, "Failed to delete %1$s\n", f.getCanonicalPath());
    936                     error = true;
    937                 }
    938             }
    939 
    940             String path = avdInfo.getPath();
    941             if (path != null) {
    942                 f = new File(path);
    943                 if (f.exists()) {
    944                     log.printf("Deleting folder %1$s\n", f.getCanonicalPath());
    945                     if (deleteContentOf(f) == false || f.delete() == false) {
    946                         log.error(null, "Failed to delete %1$s\n", f.getCanonicalPath());
    947                         error = true;
    948                     }
    949                 }
    950             }
    951 
    952             removeAvd(avdInfo);
    953 
    954             if (error) {
    955                 log.printf("\nAVD '%1$s' deleted with errors. See errors above.\n",
    956                         avdInfo.getName());
    957             } else {
    958                 log.printf("\nAVD '%1$s' deleted.\n", avdInfo.getName());
    959                 return true;
    960             }
    961 
    962         } catch (AndroidLocationException e) {
    963             log.error(e, null);
    964         } catch (IOException e) {
    965             log.error(e, null);
    966         } catch (SecurityException e) {
    967             log.error(e, null);
    968         }
    969         return false;
    970     }
    971 
    972     /**
    973      * Moves and/or rename an existing AVD and its files.
    974      * This also change it in the manager's list.
    975      * <p/>
    976      * The caller should make sure the name or path given are valid, do not exist and are
    977      * actually different than current values.
    978      *
    979      * @param avdInfo the information on the AVD to move.
    980      * @param newName the new name of the AVD if non null.
    981      * @param paramFolderPath the new data folder if non null.
    982      * @param log the log object to receive action logs. Cannot be null.
    983      * @return True if the move succeeded or there was nothing to do.
    984      *         If false, this method will have had already output error in the log.
    985      */
    986     public boolean moveAvd(AvdInfo avdInfo, String newName, String paramFolderPath, ISdkLog log) {
    987 
    988         try {
    989             if (paramFolderPath != null) {
    990                 File f = new File(avdInfo.getPath());
    991                 log.warning("Moving '%1$s' to '%2$s'.", avdInfo.getPath(), paramFolderPath);
    992                 if (!f.renameTo(new File(paramFolderPath))) {
    993                     log.error(null, "Failed to move '%1$s' to '%2$s'.",
    994                             avdInfo.getPath(), paramFolderPath);
    995                     return false;
    996                 }
    997 
    998                 // update AVD info
    999                 AvdInfo info = new AvdInfo(avdInfo.getName(), paramFolderPath,
   1000                         avdInfo.getTargetHash(), avdInfo.getTarget(), avdInfo.getProperties());
   1001                 replaceAvd(avdInfo, info);
   1002 
   1003                 // update the ini file
   1004                 createAvdIniFile(info);
   1005             }
   1006 
   1007             if (newName != null) {
   1008                 File oldIniFile = avdInfo.getIniFile();
   1009                 File newIniFile = AvdInfo.getIniFile(newName);
   1010 
   1011                 log.warning("Moving '%1$s' to '%2$s'.", oldIniFile.getPath(), newIniFile.getPath());
   1012                 if (!oldIniFile.renameTo(newIniFile)) {
   1013                     log.error(null, "Failed to move '%1$s' to '%2$s'.",
   1014                             oldIniFile.getPath(), newIniFile.getPath());
   1015                     return false;
   1016                 }
   1017 
   1018                 // update AVD info
   1019                 AvdInfo info = new AvdInfo(newName, avdInfo.getPath(),
   1020                         avdInfo.getTargetHash(), avdInfo.getTarget(), avdInfo.getProperties());
   1021                 replaceAvd(avdInfo, info);
   1022             }
   1023 
   1024             log.printf("AVD '%1$s' moved.\n", avdInfo.getName());
   1025 
   1026         } catch (AndroidLocationException e) {
   1027             log.error(e, null);
   1028         } catch (IOException e) {
   1029             log.error(e, null);
   1030         }
   1031 
   1032         // nothing to do or succeeded
   1033         return true;
   1034     }
   1035 
   1036     /**
   1037      * Helper method to recursively delete a folder's content (but not the folder itself).
   1038      *
   1039      * @throws SecurityException like {@link File#delete()} does if file/folder is not writable.
   1040      */
   1041     private boolean deleteContentOf(File folder) throws SecurityException {
   1042         for (File f : folder.listFiles()) {
   1043             if (f.isDirectory()) {
   1044                 if (deleteContentOf(f) == false) {
   1045                     return false;
   1046                 }
   1047             }
   1048             if (f.delete() == false) {
   1049                 return false;
   1050             }
   1051 
   1052         }
   1053 
   1054         return true;
   1055     }
   1056 
   1057     /**
   1058      * Returns a list of files that are potential AVD ini files.
   1059      * <p/>
   1060      * This lists the $HOME/.android/avd/<name>.ini files.
   1061      * Such files are properties file than then indicate where the AVD folder is located.
   1062      *
   1063      * @return A new {@link File} array or null. The array might be empty.
   1064      * @throws AndroidLocationException if there's a problem getting android root directory.
   1065      */
   1066     private File[] buildAvdFilesList() throws AndroidLocationException {
   1067         // get the Android prefs location.
   1068         String avdRoot = AvdManager.getBaseAvdFolder();
   1069 
   1070         // ensure folder validity.
   1071         File folder = new File(avdRoot);
   1072         if (folder.isFile()) {
   1073             throw new AndroidLocationException(
   1074                     String.format("%1$s is not a valid folder.", avdRoot));
   1075         } else if (folder.exists() == false) {
   1076             // folder is not there, we create it and return
   1077             folder.mkdirs();
   1078             return null;
   1079         }
   1080 
   1081         File[] avds = folder.listFiles(new FilenameFilter() {
   1082             public boolean accept(File parent, String name) {
   1083                 if (INI_NAME_PATTERN.matcher(name).matches()) {
   1084                     // check it's a file and not a folder
   1085                     boolean isFile = new File(parent, name).isFile();
   1086                     return isFile;
   1087                 }
   1088 
   1089                 return false;
   1090             }
   1091         });
   1092 
   1093         return avds;
   1094     }
   1095 
   1096     /**
   1097      * Computes the internal list of available AVDs
   1098      * @param allList the list to contain all the AVDs
   1099      * @param log the log object to receive action logs. Cannot be null.
   1100      *
   1101      * @throws AndroidLocationException if there's a problem getting android root directory.
   1102      */
   1103     private void buildAvdList(ArrayList<AvdInfo> allList, ISdkLog log)
   1104             throws AndroidLocationException {
   1105         File[] avds = buildAvdFilesList();
   1106         if (avds != null) {
   1107             for (File avd : avds) {
   1108                 AvdInfo info = parseAvdInfo(avd, log);
   1109                 if (info != null) {
   1110                     allList.add(info);
   1111                 }
   1112             }
   1113         }
   1114     }
   1115 
   1116     /**
   1117      * Parses an AVD .ini file to create an {@link AvdInfo}.
   1118      *
   1119      * @param path The path to the AVD .ini file
   1120      * @param log the log object to receive action logs. Cannot be null.
   1121      * @return A new {@link AvdInfo} with an {@link AvdStatus} indicating whether this AVD is
   1122      *         valid or not.
   1123      */
   1124     private AvdInfo parseAvdInfo(File path, ISdkLog log) {
   1125         Map<String, String> map = SdkManager.parsePropertyFile(path, log);
   1126 
   1127         String avdPath = map.get(AVD_INFO_PATH);
   1128         String targetHash = map.get(AVD_INFO_TARGET);
   1129 
   1130         IAndroidTarget target = null;
   1131         File configIniFile = null;
   1132         Map<String, String> properties = null;
   1133 
   1134         if (targetHash != null) {
   1135             target = mSdkManager.getTargetFromHashString(targetHash);
   1136         }
   1137 
   1138         // load the AVD properties.
   1139         if (avdPath != null) {
   1140             configIniFile = new File(avdPath, CONFIG_INI);
   1141         }
   1142 
   1143         if (configIniFile != null) {
   1144             if (!configIniFile.isFile()) {
   1145                 log.warning("Missing file '%1$s'.",  configIniFile.getPath());
   1146             } else {
   1147                 properties = SdkManager.parsePropertyFile(configIniFile, log);
   1148             }
   1149         }
   1150 
   1151         // get name
   1152         String name = path.getName();
   1153         Matcher matcher = INI_NAME_PATTERN.matcher(path.getName());
   1154         if (matcher.matches()) {
   1155             name = matcher.group(1);
   1156         }
   1157 
   1158         // check the image.sysdir are valid
   1159         boolean validImageSysdir = true;
   1160         if (properties != null) {
   1161             String imageSysDir = properties.get(AVD_INI_IMAGES_1);
   1162             if (imageSysDir != null) {
   1163                 File f = new File(mSdkManager.getLocation() + File.separator + imageSysDir);
   1164                 if (f.isDirectory() == false) {
   1165                     validImageSysdir = false;
   1166                 } else {
   1167                     imageSysDir = properties.get(AVD_INI_IMAGES_2);
   1168                     if (imageSysDir != null) {
   1169                         f = new File(mSdkManager.getLocation() + File.separator + imageSysDir);
   1170                         if (f.isDirectory() == false) {
   1171                             validImageSysdir = false;
   1172                         }
   1173                     }
   1174                 }
   1175             }
   1176         }
   1177 
   1178         AvdStatus status;
   1179 
   1180         if (avdPath == null) {
   1181             status = AvdStatus.ERROR_PATH;
   1182         } else if (configIniFile == null) {
   1183             status = AvdStatus.ERROR_CONFIG;
   1184         } else if (targetHash == null) {
   1185             status = AvdStatus.ERROR_TARGET_HASH;
   1186         } else if (target == null) {
   1187             status = AvdStatus.ERROR_TARGET;
   1188         } else if (properties == null) {
   1189             status = AvdStatus.ERROR_PROPERTIES;
   1190         } else if (validImageSysdir == false) {
   1191             status = AvdStatus.ERROR_IMAGE_DIR;
   1192         } else {
   1193             status = AvdStatus.OK;
   1194         }
   1195 
   1196         AvdInfo info = new AvdInfo(
   1197                 name,
   1198                 avdPath,
   1199                 targetHash,
   1200                 target,
   1201                 properties,
   1202                 status);
   1203 
   1204         return info;
   1205     }
   1206 
   1207     /**
   1208      * Writes a .ini file from a set of properties, using UTF-8 encoding.
   1209      *
   1210      * @param iniFile The file to generate.
   1211      * @param values THe properties to place in the ini file.
   1212      * @throws IOException if {@link FileWriter} fails to open, write or close the file.
   1213      */
   1214     private static void writeIniFile(File iniFile, Map<String, String> values)
   1215             throws IOException {
   1216         OutputStreamWriter writer = new OutputStreamWriter(new FileOutputStream(iniFile),
   1217                 SdkConstants.INI_CHARSET);
   1218 
   1219         for (Entry<String, String> entry : values.entrySet()) {
   1220             writer.write(String.format("%1$s=%2$s\n", entry.getKey(), entry.getValue()));
   1221         }
   1222         writer.close();
   1223     }
   1224 
   1225     /**
   1226      * Invokes the tool to create a new SD card image file.
   1227      *
   1228      * @param toolLocation The path to the mksdcard tool.
   1229      * @param size The size of the new SD Card, compatible with {@link #SDCARD_SIZE_PATTERN}.
   1230      * @param location The path of the new sdcard image file to generate.
   1231      * @param log the log object to receive action logs. Cannot be null.
   1232      * @return True if the sdcard could be created.
   1233      */
   1234     private boolean createSdCard(String toolLocation, String size, String location, ISdkLog log) {
   1235         try {
   1236             String[] command = new String[3];
   1237             command[0] = toolLocation;
   1238             command[1] = size;
   1239             command[2] = location;
   1240             Process process = Runtime.getRuntime().exec(command);
   1241 
   1242             ArrayList<String> errorOutput = new ArrayList<String>();
   1243             ArrayList<String> stdOutput = new ArrayList<String>();
   1244             int status = grabProcessOutput(process, errorOutput, stdOutput,
   1245                     true /* waitForReaders */);
   1246 
   1247             if (status == 0) {
   1248                 return true;
   1249             } else {
   1250                 for (String error : errorOutput) {
   1251                     log.error(null, error);
   1252                 }
   1253             }
   1254 
   1255         } catch (InterruptedException e) {
   1256             // pass, print error below
   1257         } catch (IOException e) {
   1258             // pass, print error below
   1259         }
   1260 
   1261         log.error(null, "Failed to create the SD card.");
   1262         return false;
   1263     }
   1264 
   1265     /**
   1266      * Gets the stderr/stdout outputs of a process and returns when the process is done.
   1267      * Both <b>must</b> be read or the process will block on windows.
   1268      * @param process The process to get the ouput from
   1269      * @param errorOutput The array to store the stderr output. cannot be null.
   1270      * @param stdOutput The array to store the stdout output. cannot be null.
   1271      * @param waitforReaders if true, this will wait for the reader threads.
   1272      * @return the process return code.
   1273      * @throws InterruptedException
   1274      */
   1275     private int grabProcessOutput(final Process process, final ArrayList<String> errorOutput,
   1276             final ArrayList<String> stdOutput, boolean waitforReaders)
   1277             throws InterruptedException {
   1278         assert errorOutput != null;
   1279         assert stdOutput != null;
   1280         // read the lines as they come. if null is returned, it's
   1281         // because the process finished
   1282         Thread t1 = new Thread("") { //$NON-NLS-1$
   1283             @Override
   1284             public void run() {
   1285                 // create a buffer to read the stderr output
   1286                 InputStreamReader is = new InputStreamReader(process.getErrorStream());
   1287                 BufferedReader errReader = new BufferedReader(is);
   1288 
   1289                 try {
   1290                     while (true) {
   1291                         String line = errReader.readLine();
   1292                         if (line != null) {
   1293                             errorOutput.add(line);
   1294                         } else {
   1295                             break;
   1296                         }
   1297                     }
   1298                 } catch (IOException e) {
   1299                     // do nothing.
   1300                 }
   1301             }
   1302         };
   1303 
   1304         Thread t2 = new Thread("") { //$NON-NLS-1$
   1305             @Override
   1306             public void run() {
   1307                 InputStreamReader is = new InputStreamReader(process.getInputStream());
   1308                 BufferedReader outReader = new BufferedReader(is);
   1309 
   1310                 try {
   1311                     while (true) {
   1312                         String line = outReader.readLine();
   1313                         if (line != null) {
   1314                             stdOutput.add(line);
   1315                         } else {
   1316                             break;
   1317                         }
   1318                     }
   1319                 } catch (IOException e) {
   1320                     // do nothing.
   1321                 }
   1322             }
   1323         };
   1324 
   1325         t1.start();
   1326         t2.start();
   1327 
   1328         // it looks like on windows process#waitFor() can return
   1329         // before the thread have filled the arrays, so we wait for both threads and the
   1330         // process itself.
   1331         if (waitforReaders) {
   1332             try {
   1333                 t1.join();
   1334             } catch (InterruptedException e) {
   1335                 // nothing to do here
   1336             }
   1337             try {
   1338                 t2.join();
   1339             } catch (InterruptedException e) {
   1340                 // nothing to do here
   1341             }
   1342         }
   1343 
   1344         // get the return code from the process
   1345         return process.waitFor();
   1346     }
   1347 
   1348     /**
   1349      * Removes an {@link AvdInfo} from the internal list.
   1350      *
   1351      * @param avdInfo The {@link AvdInfo} to remove.
   1352      * @return true if this {@link AvdInfo} was present and has been removed.
   1353      */
   1354     public boolean removeAvd(AvdInfo avdInfo) {
   1355         synchronized (mAllAvdList) {
   1356             if (mAllAvdList.remove(avdInfo)) {
   1357                 mValidAvdList = mBrokenAvdList = null;
   1358                 return true;
   1359             }
   1360         }
   1361 
   1362         return false;
   1363     }
   1364 
   1365     /**
   1366      * Updates an AVD with new path to the system image folders.
   1367      * @param name the name of the AVD to update.
   1368      * @param log the log object to receive action logs. Cannot be null.
   1369      * @throws IOException
   1370      */
   1371     public void updateAvd(String name, ISdkLog log) throws IOException {
   1372         // find the AVD to update. It should be be in the broken list.
   1373         AvdInfo avd = null;
   1374         synchronized (mAllAvdList) {
   1375             for (AvdInfo info : mAllAvdList) {
   1376                 if (info.getName().equals(name)) {
   1377                     avd = info;
   1378                     break;
   1379                 }
   1380             }
   1381         }
   1382 
   1383         if (avd == null) {
   1384             // not in the broken list, just return.
   1385             log.error(null, "There is no Android Virtual Device named '%s'.", name);
   1386             return;
   1387         }
   1388 
   1389         updateAvd(avd, log);
   1390     }
   1391 
   1392 
   1393     /**
   1394      * Updates an AVD with new path to the system image folders.
   1395      * @param avd the AVD to update.
   1396      * @param log the log object to receive action logs. Cannot be null.
   1397      * @throws IOException
   1398      */
   1399     public void updateAvd(AvdInfo avd, ISdkLog log) throws IOException {
   1400         // get the properties. This is a unmodifiable Map.
   1401         Map<String, String> oldProperties = avd.getProperties();
   1402 
   1403         // create a new map
   1404         Map<String, String> properties = new HashMap<String, String>();
   1405         if (oldProperties != null) {
   1406             properties.putAll(oldProperties);
   1407         }
   1408 
   1409         AvdStatus status;
   1410 
   1411         // create the path to the new system images.
   1412         if (setImagePathProperties(avd.getTarget(), properties, log)) {
   1413             if (properties.containsKey(AVD_INI_IMAGES_1)) {
   1414                 log.printf("Updated '%1$s' with value '%2$s'\n", AVD_INI_IMAGES_1,
   1415                         properties.get(AVD_INI_IMAGES_1));
   1416             }
   1417 
   1418             if (properties.containsKey(AVD_INI_IMAGES_2)) {
   1419                 log.printf("Updated '%1$s' with value '%2$s'\n", AVD_INI_IMAGES_2,
   1420                         properties.get(AVD_INI_IMAGES_2));
   1421             }
   1422 
   1423             status = AvdStatus.OK;
   1424         } else {
   1425             log.error(null, "Unable to find non empty system images folders for %1$s",
   1426                     avd.getName());
   1427             //FIXME: display paths to empty image folders?
   1428             status = AvdStatus.ERROR_IMAGE_DIR;
   1429         }
   1430 
   1431         // now write the config file
   1432         File configIniFile = new File(avd.getPath(), CONFIG_INI);
   1433         writeIniFile(configIniFile, properties);
   1434 
   1435         // finally create a new AvdInfo for this unbroken avd and add it to the list.
   1436         // instead of creating the AvdInfo object directly we reparse it, to detect other possible
   1437         // errors
   1438         // FIXME: We may want to create this AvdInfo by reparsing the AVD instead. This could detect other errors.
   1439         AvdInfo newAvd = new AvdInfo(
   1440                 avd.getName(),
   1441                 avd.getPath(),
   1442                 avd.getTargetHash(),
   1443                 avd.getTarget(),
   1444                 properties,
   1445                 status);
   1446 
   1447         replaceAvd(avd, newAvd);
   1448     }
   1449 
   1450     /**
   1451      * Sets the paths to the system images in a properties map.
   1452      * @param target the target in which to find the system images.
   1453      * @param properties the properties in which to set the paths.
   1454      * @param log the log object to receive action logs. Cannot be null.
   1455      * @return true if success, false if some path are missing.
   1456      */
   1457     private boolean setImagePathProperties(IAndroidTarget target,
   1458             Map<String, String> properties,
   1459             ISdkLog log) {
   1460         properties.remove(AVD_INI_IMAGES_1);
   1461         properties.remove(AVD_INI_IMAGES_2);
   1462 
   1463         try {
   1464             String property = AVD_INI_IMAGES_1;
   1465 
   1466             // First the image folders of the target itself
   1467             String imagePath = getImageRelativePath(target);
   1468             if (imagePath != null) {
   1469                 properties.put(property, imagePath);
   1470                 property = AVD_INI_IMAGES_2;
   1471             }
   1472 
   1473 
   1474             // If the target is an add-on we need to add the Platform image as a backup.
   1475             IAndroidTarget parent = target.getParent();
   1476             if (parent != null) {
   1477                 imagePath = getImageRelativePath(parent);
   1478                 if (imagePath != null) {
   1479                     properties.put(property, imagePath);
   1480                 }
   1481             }
   1482 
   1483             // we need at least one path!
   1484             return properties.containsKey(AVD_INI_IMAGES_1);
   1485         } catch (InvalidTargetPathException e) {
   1486             log.error(e, e.getMessage());
   1487         }
   1488 
   1489         return false;
   1490     }
   1491 
   1492     /**
   1493      * Replaces an old {@link AvdInfo} with a new one in the lists storing them.
   1494      * @param oldAvd the {@link AvdInfo} to remove.
   1495      * @param newAvd the {@link AvdInfo} to add.
   1496      */
   1497     private void replaceAvd(AvdInfo oldAvd, AvdInfo newAvd) {
   1498         synchronized (mAllAvdList) {
   1499             mAllAvdList.remove(oldAvd);
   1500             mAllAvdList.add(newAvd);
   1501             mValidAvdList = mBrokenAvdList = null;
   1502         }
   1503     }
   1504 }
   1505