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