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