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