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