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