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