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