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