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 throw new IllegalArgumentException(String.format( 886 "ABI Type %s is unknown for target %s", 887 abiType, 888 target.getDescription())); 889 } 890 891 File folder = systemImage.getLocation(); 892 String imageFullPath = folder.getAbsolutePath(); 893 894 // make this path relative to the SDK location 895 String sdkLocation = mSdkManager.getLocation(); 896 if (!imageFullPath.startsWith(sdkLocation)) { 897 // this really really should not happen. 898 assert false; 899 throw new InvalidTargetPathException("Target location is not inside the SDK."); 900 } 901 902 if (folder.isDirectory()) { 903 String[] list = folder.list(new FilenameFilter() { 904 public boolean accept(File dir, String name) { 905 return IMAGE_NAME_PATTERN.matcher(name).matches(); 906 } 907 }); 908 909 if (list.length > 0) { 910 // Remove the SDK root path, e.g. /sdk/dir1/dir2 => /dir1/dir2 911 imageFullPath = imageFullPath.substring(sdkLocation.length()); 912 // The path is relative, so it must not start with a file separator 913 if (imageFullPath.charAt(0) == File.separatorChar) { 914 imageFullPath = imageFullPath.substring(1); 915 } 916 // For compatibility with previous versions, we denote folders 917 // by ending the path with file separator 918 if (!imageFullPath.endsWith(File.separator)) { 919 imageFullPath += File.separator; 920 } 921 922 return imageFullPath; 923 } 924 } 925 926 return null; 927 } 928 929 /** 930 * Returns the path to the skin, as a relative path to the SDK. 931 * @param skinName The name of the skin to find. Case-sensitive. 932 * @param target The target where to find the skin. 933 * @param log the log object to receive action logs. Cannot be null. 934 */ 935 public String getSkinRelativePath(String skinName, IAndroidTarget target, ISdkLog log) { 936 if (log == null) { 937 throw new IllegalArgumentException("log cannot be null"); 938 } 939 940 // first look to see if the skin is in the target 941 File skin = getSkinPath(skinName, target); 942 943 // skin really does not exist! 944 if (skin.exists() == false) { 945 log.error(null, "Skin '%1$s' does not exist.", skinName); 946 return null; 947 } 948 949 // get the skin path 950 String path = skin.getAbsolutePath(); 951 952 // make this path relative to the SDK location 953 String sdkLocation = mSdkManager.getLocation(); 954 if (path.startsWith(sdkLocation) == false) { 955 // this really really should not happen. 956 log.error(null, "Target location is not inside the SDK."); 957 assert false; 958 return null; 959 } 960 961 path = path.substring(sdkLocation.length()); 962 if (path.charAt(0) == File.separatorChar) { 963 path = path.substring(1); 964 } 965 return path; 966 } 967 968 /** 969 * Returns the full absolute OS path to a skin specified by name for a given target. 970 * @param skinName The name of the skin to find. Case-sensitive. 971 * @param target The target where to find the skin. 972 * @return a {@link File} that may or may not actually exist. 973 */ 974 public File getSkinPath(String skinName, IAndroidTarget target) { 975 String path = target.getPath(IAndroidTarget.SKINS); 976 File skin = new File(path, skinName); 977 978 if (skin.exists() == false && target.isPlatform() == false) { 979 target = target.getParent(); 980 981 path = target.getPath(IAndroidTarget.SKINS); 982 skin = new File(path, skinName); 983 } 984 985 return skin; 986 } 987 988 /** 989 * Creates the ini file for an AVD. 990 * 991 * @param name of the AVD. 992 * @param avdFolder path for the data folder of the AVD. 993 * @param target of the AVD. 994 * @param removePrevious True if an existing ini file should be removed. 995 * @throws AndroidLocationException if there's a problem getting android root directory. 996 * @throws IOException if {@link File#getAbsolutePath()} fails. 997 */ 998 private File createAvdIniFile(String name, 999 File avdFolder, 1000 IAndroidTarget target, 1001 boolean removePrevious) 1002 throws AndroidLocationException, IOException { 1003 File iniFile = AvdInfo.getDefaultIniFile(this, name); 1004 1005 if (removePrevious) { 1006 if (iniFile.isFile()) { 1007 iniFile.delete(); 1008 } else if (iniFile.isDirectory()) { 1009 deleteContentOf(iniFile); 1010 iniFile.delete(); 1011 } 1012 } 1013 1014 HashMap<String, String> values = new HashMap<String, String>(); 1015 values.put(AVD_INFO_PATH, avdFolder.getAbsolutePath()); 1016 values.put(AVD_INFO_TARGET, target.hashString()); 1017 writeIniFile(iniFile, values); 1018 1019 return iniFile; 1020 } 1021 1022 /** 1023 * Creates the ini file for an AVD. 1024 * 1025 * @param info of the AVD. 1026 * @throws AndroidLocationException if there's a problem getting android root directory. 1027 * @throws IOException if {@link File#getAbsolutePath()} fails. 1028 */ 1029 private File createAvdIniFile(AvdInfo info) throws AndroidLocationException, IOException { 1030 return createAvdIniFile(info.getName(), 1031 new File(info.getDataFolderPath()), 1032 info.getTarget(), 1033 false /*removePrevious*/); 1034 } 1035 1036 /** 1037 * Actually deletes the files of an existing AVD. 1038 * <p/> 1039 * This also remove it from the manager's list, The caller does not need to 1040 * call {@link #removeAvd(AvdInfo)} afterwards. 1041 * <p/> 1042 * This method is designed to somehow work with an unavailable AVD, that is an AVD that 1043 * could not be loaded due to some error. That means this method still tries to remove 1044 * the AVD ini file or its folder if it can be found. An error will be output if any of 1045 * these operations fail. 1046 * 1047 * @param avdInfo the information on the AVD to delete 1048 * @param log the log object to receive action logs. Cannot be null. 1049 * @return True if the AVD was deleted with no error. 1050 */ 1051 public boolean deleteAvd(AvdInfo avdInfo, ISdkLog log) { 1052 try { 1053 boolean error = false; 1054 1055 File f = avdInfo.getIniFile(); 1056 if (f != null && f.exists()) { 1057 log.printf("Deleting file %1$s\n", f.getCanonicalPath()); 1058 if (!f.delete()) { 1059 log.error(null, "Failed to delete %1$s\n", f.getCanonicalPath()); 1060 error = true; 1061 } 1062 } 1063 1064 String path = avdInfo.getDataFolderPath(); 1065 if (path != null) { 1066 f = new File(path); 1067 if (f.exists()) { 1068 log.printf("Deleting folder %1$s\n", f.getCanonicalPath()); 1069 if (deleteContentOf(f) == false || f.delete() == false) { 1070 log.error(null, "Failed to delete %1$s\n", f.getCanonicalPath()); 1071 error = true; 1072 } 1073 } 1074 } 1075 1076 removeAvd(avdInfo); 1077 1078 if (error) { 1079 log.printf("\nAVD '%1$s' deleted with errors. See errors above.\n", 1080 avdInfo.getName()); 1081 } else { 1082 log.printf("\nAVD '%1$s' deleted.\n", avdInfo.getName()); 1083 return true; 1084 } 1085 1086 } catch (IOException e) { 1087 log.error(e, null); 1088 } catch (SecurityException e) { 1089 log.error(e, null); 1090 } 1091 return false; 1092 } 1093 1094 /** 1095 * Moves and/or rename an existing AVD and its files. 1096 * This also change it in the manager's list. 1097 * <p/> 1098 * The caller should make sure the name or path given are valid, do not exist and are 1099 * actually different than current values. 1100 * 1101 * @param avdInfo the information on the AVD to move. 1102 * @param newName the new name of the AVD if non null. 1103 * @param paramFolderPath the new data folder if non null. 1104 * @param log the log object to receive action logs. Cannot be null. 1105 * @return True if the move succeeded or there was nothing to do. 1106 * If false, this method will have had already output error in the log. 1107 */ 1108 public boolean moveAvd(AvdInfo avdInfo, String newName, String paramFolderPath, ISdkLog log) { 1109 1110 try { 1111 if (paramFolderPath != null) { 1112 File f = new File(avdInfo.getDataFolderPath()); 1113 log.warning("Moving '%1$s' to '%2$s'.", 1114 avdInfo.getDataFolderPath(), 1115 paramFolderPath); 1116 if (!f.renameTo(new File(paramFolderPath))) { 1117 log.error(null, "Failed to move '%1$s' to '%2$s'.", 1118 avdInfo.getDataFolderPath(), paramFolderPath); 1119 return false; 1120 } 1121 1122 // update AVD info 1123 AvdInfo info = new AvdInfo( 1124 avdInfo.getName(), 1125 avdInfo.getIniFile(), 1126 paramFolderPath, 1127 avdInfo.getTargetHash(), 1128 avdInfo.getTarget(), 1129 avdInfo.getAbiType(), 1130 avdInfo.getProperties()); 1131 replaceAvd(avdInfo, info); 1132 1133 // update the ini file 1134 createAvdIniFile(info); 1135 } 1136 1137 if (newName != null) { 1138 File oldIniFile = avdInfo.getIniFile(); 1139 File newIniFile = AvdInfo.getDefaultIniFile(this, newName); 1140 1141 log.warning("Moving '%1$s' to '%2$s'.", oldIniFile.getPath(), newIniFile.getPath()); 1142 if (!oldIniFile.renameTo(newIniFile)) { 1143 log.error(null, "Failed to move '%1$s' to '%2$s'.", 1144 oldIniFile.getPath(), newIniFile.getPath()); 1145 return false; 1146 } 1147 1148 // update AVD info 1149 AvdInfo info = new AvdInfo( 1150 newName, 1151 avdInfo.getIniFile(), 1152 avdInfo.getDataFolderPath(), 1153 avdInfo.getTargetHash(), 1154 avdInfo.getTarget(), 1155 avdInfo.getAbiType(), 1156 avdInfo.getProperties()); 1157 replaceAvd(avdInfo, info); 1158 } 1159 1160 log.printf("AVD '%1$s' moved.\n", avdInfo.getName()); 1161 1162 } catch (AndroidLocationException e) { 1163 log.error(e, null); 1164 } catch (IOException e) { 1165 log.error(e, null); 1166 } 1167 1168 // nothing to do or succeeded 1169 return true; 1170 } 1171 1172 /** 1173 * Helper method to recursively delete a folder's content (but not the folder itself). 1174 * 1175 * @throws SecurityException like {@link File#delete()} does if file/folder is not writable. 1176 */ 1177 private boolean deleteContentOf(File folder) throws SecurityException { 1178 File[] files = folder.listFiles(); 1179 if (files != null) { 1180 for (File f : files) { 1181 if (f.isDirectory()) { 1182 if (deleteContentOf(f) == false) { 1183 return false; 1184 } 1185 } 1186 if (f.delete() == false) { 1187 return false; 1188 } 1189 1190 } 1191 } 1192 1193 return true; 1194 } 1195 1196 /** 1197 * Returns a list of files that are potential AVD ini files. 1198 * <p/> 1199 * This lists the $HOME/.android/avd/<name>.ini files. 1200 * Such files are properties file than then indicate where the AVD folder is located. 1201 * <p/> 1202 * Note: the method is to be considered private. It is made protected so that 1203 * unit tests can easily override the AVD root. 1204 * 1205 * @return A new {@link File} array or null. The array might be empty. 1206 * @throws AndroidLocationException if there's a problem getting android root directory. 1207 */ 1208 private File[] buildAvdFilesList() throws AndroidLocationException { 1209 File folder = new File(getBaseAvdFolder()); 1210 1211 // ensure folder validity. 1212 if (folder.isFile()) { 1213 throw new AndroidLocationException( 1214 String.format("%1$s is not a valid folder.", folder.getAbsolutePath())); 1215 } else if (folder.exists() == false) { 1216 // folder is not there, we create it and return 1217 folder.mkdirs(); 1218 return null; 1219 } 1220 1221 File[] avds = folder.listFiles(new FilenameFilter() { 1222 public boolean accept(File parent, String name) { 1223 if (INI_NAME_PATTERN.matcher(name).matches()) { 1224 // check it's a file and not a folder 1225 boolean isFile = new File(parent, name).isFile(); 1226 return isFile; 1227 } 1228 1229 return false; 1230 } 1231 }); 1232 1233 return avds; 1234 } 1235 1236 /** 1237 * Computes the internal list of available AVDs 1238 * @param allList the list to contain all the AVDs 1239 * @param log the log object to receive action logs. Cannot be null. 1240 * 1241 * @throws AndroidLocationException if there's a problem getting android root directory. 1242 */ 1243 private void buildAvdList(ArrayList<AvdInfo> allList, ISdkLog log) 1244 throws AndroidLocationException { 1245 File[] avds = buildAvdFilesList(); 1246 if (avds != null) { 1247 for (File avd : avds) { 1248 AvdInfo info = parseAvdInfo(avd, log); 1249 if (info != null) { 1250 allList.add(info); 1251 } 1252 } 1253 } 1254 } 1255 1256 /** 1257 * Parses an AVD .ini file to create an {@link AvdInfo}. 1258 * 1259 * @param iniPath The path to the AVD .ini file 1260 * @param log the log object to receive action logs. Cannot be null. 1261 * @return A new {@link AvdInfo} with an {@link AvdStatus} indicating whether this AVD is 1262 * valid or not. 1263 */ 1264 private AvdInfo parseAvdInfo(File iniPath, ISdkLog log) { 1265 Map<String, String> map = ProjectProperties.parsePropertyFile( 1266 new FileWrapper(iniPath), 1267 log); 1268 1269 String avdPath = map.get(AVD_INFO_PATH); 1270 String targetHash = map.get(AVD_INFO_TARGET); 1271 1272 IAndroidTarget target = null; 1273 FileWrapper configIniFile = null; 1274 Map<String, String> properties = null; 1275 1276 if (targetHash != null) { 1277 target = mSdkManager.getTargetFromHashString(targetHash); 1278 } 1279 1280 // load the AVD properties. 1281 if (avdPath != null) { 1282 configIniFile = new FileWrapper(avdPath, CONFIG_INI); 1283 } 1284 1285 if (configIniFile != null) { 1286 if (!configIniFile.isFile()) { 1287 log.warning("Missing file '%1$s'.", configIniFile.getPath()); 1288 } else { 1289 properties = ProjectProperties.parsePropertyFile(configIniFile, log); 1290 } 1291 } 1292 1293 // get name 1294 String name = iniPath.getName(); 1295 Matcher matcher = INI_NAME_PATTERN.matcher(iniPath.getName()); 1296 if (matcher.matches()) { 1297 name = matcher.group(1); 1298 } 1299 1300 // get abi type 1301 String abiType = properties == null ? null : properties.get(AVD_INI_ABI_TYPE); 1302 // for the avds created previously without enhancement, i.e. They are created based 1303 // on previous API Levels. They are supposed to have ARM processor type 1304 if (abiType == null) { 1305 abiType = SdkConstants.ABI_ARMEABI; 1306 } 1307 1308 // check the image.sysdir are valid 1309 boolean validImageSysdir = true; 1310 if (properties != null) { 1311 String imageSysDir = properties.get(AVD_INI_IMAGES_1); 1312 if (imageSysDir != null) { 1313 File f = new File(mSdkManager.getLocation() + File.separator + imageSysDir); 1314 if (f.isDirectory() == false) { 1315 validImageSysdir = false; 1316 } else { 1317 imageSysDir = properties.get(AVD_INI_IMAGES_2); 1318 if (imageSysDir != null) { 1319 f = new File(mSdkManager.getLocation() + File.separator + imageSysDir); 1320 if (f.isDirectory() == false) { 1321 validImageSysdir = false; 1322 } 1323 } 1324 } 1325 } 1326 } 1327 1328 // TODO: What about missing sdcard, skins, etc? 1329 1330 AvdStatus status; 1331 1332 if (avdPath == null) { 1333 status = AvdStatus.ERROR_PATH; 1334 } else if (configIniFile == null) { 1335 status = AvdStatus.ERROR_CONFIG; 1336 } else if (targetHash == null) { 1337 status = AvdStatus.ERROR_TARGET_HASH; 1338 } else if (target == null) { 1339 status = AvdStatus.ERROR_TARGET; 1340 } else if (properties == null) { 1341 status = AvdStatus.ERROR_PROPERTIES; 1342 } else if (validImageSysdir == false) { 1343 status = AvdStatus.ERROR_IMAGE_DIR; 1344 } else { 1345 status = AvdStatus.OK; 1346 } 1347 1348 AvdInfo info = new AvdInfo( 1349 name, 1350 iniPath, 1351 avdPath, 1352 targetHash, 1353 target, 1354 abiType, 1355 properties, 1356 status); 1357 1358 return info; 1359 } 1360 1361 /** 1362 * Writes a .ini file from a set of properties, using UTF-8 encoding. 1363 * 1364 * @param iniFile The file to generate. 1365 * @param values THe properties to place in the ini file. 1366 * @throws IOException if {@link FileWriter} fails to open, write or close the file. 1367 */ 1368 private static void writeIniFile(File iniFile, Map<String, String> values) 1369 throws IOException { 1370 OutputStreamWriter writer = new OutputStreamWriter(new FileOutputStream(iniFile), 1371 SdkConstants.INI_CHARSET); 1372 1373 for (Entry<String, String> entry : values.entrySet()) { 1374 writer.write(String.format("%1$s=%2$s\n", entry.getKey(), entry.getValue())); 1375 } 1376 writer.close(); 1377 } 1378 1379 /** 1380 * Invokes the tool to create a new SD card image file. 1381 * 1382 * @param toolLocation The path to the mksdcard tool. 1383 * @param size The size of the new SD Card, compatible with {@link #SDCARD_SIZE_PATTERN}. 1384 * @param location The path of the new sdcard image file to generate. 1385 * @param log the log object to receive action logs. Cannot be null. 1386 * @return True if the sdcard could be created. 1387 */ 1388 private boolean createSdCard(String toolLocation, String size, String location, ISdkLog log) { 1389 try { 1390 String[] command = new String[3]; 1391 command[0] = toolLocation; 1392 command[1] = size; 1393 command[2] = location; 1394 Process process = Runtime.getRuntime().exec(command); 1395 1396 ArrayList<String> errorOutput = new ArrayList<String>(); 1397 ArrayList<String> stdOutput = new ArrayList<String>(); 1398 int status = grabProcessOutput(process, errorOutput, stdOutput, 1399 true /* waitForReaders */); 1400 1401 if (status == 0) { 1402 return true; 1403 } else { 1404 for (String error : errorOutput) { 1405 log.error(null, error); 1406 } 1407 } 1408 1409 } catch (InterruptedException e) { 1410 // pass, print error below 1411 } catch (IOException e) { 1412 // pass, print error below 1413 } 1414 1415 log.error(null, "Failed to create the SD card."); 1416 return false; 1417 } 1418 1419 /** 1420 * Gets the stderr/stdout outputs of a process and returns when the process is done. 1421 * Both <b>must</b> be read or the process will block on windows. 1422 * @param process The process to get the ouput from 1423 * @param errorOutput The array to store the stderr output. cannot be null. 1424 * @param stdOutput The array to store the stdout output. cannot be null. 1425 * @param waitforReaders if true, this will wait for the reader threads. 1426 * @return the process return code. 1427 * @throws InterruptedException 1428 */ 1429 private int grabProcessOutput(final Process process, final ArrayList<String> errorOutput, 1430 final ArrayList<String> stdOutput, boolean waitforReaders) 1431 throws InterruptedException { 1432 assert errorOutput != null; 1433 assert stdOutput != null; 1434 // read the lines as they come. if null is returned, it's 1435 // because the process finished 1436 Thread t1 = new Thread("") { //$NON-NLS-1$ 1437 @Override 1438 public void run() { 1439 // create a buffer to read the stderr output 1440 InputStreamReader is = new InputStreamReader(process.getErrorStream()); 1441 BufferedReader errReader = new BufferedReader(is); 1442 1443 try { 1444 while (true) { 1445 String line = errReader.readLine(); 1446 if (line != null) { 1447 errorOutput.add(line); 1448 } else { 1449 break; 1450 } 1451 } 1452 } catch (IOException e) { 1453 // do nothing. 1454 } 1455 } 1456 }; 1457 1458 Thread t2 = new Thread("") { //$NON-NLS-1$ 1459 @Override 1460 public void run() { 1461 InputStreamReader is = new InputStreamReader(process.getInputStream()); 1462 BufferedReader outReader = new BufferedReader(is); 1463 1464 try { 1465 while (true) { 1466 String line = outReader.readLine(); 1467 if (line != null) { 1468 stdOutput.add(line); 1469 } else { 1470 break; 1471 } 1472 } 1473 } catch (IOException e) { 1474 // do nothing. 1475 } 1476 } 1477 }; 1478 1479 t1.start(); 1480 t2.start(); 1481 1482 // it looks like on windows process#waitFor() can return 1483 // before the thread have filled the arrays, so we wait for both threads and the 1484 // process itself. 1485 if (waitforReaders) { 1486 try { 1487 t1.join(); 1488 } catch (InterruptedException e) { 1489 // nothing to do here 1490 } 1491 try { 1492 t2.join(); 1493 } catch (InterruptedException e) { 1494 // nothing to do here 1495 } 1496 } 1497 1498 // get the return code from the process 1499 return process.waitFor(); 1500 } 1501 1502 /** 1503 * Removes an {@link AvdInfo} from the internal list. 1504 * 1505 * @param avdInfo The {@link AvdInfo} to remove. 1506 * @return true if this {@link AvdInfo} was present and has been removed. 1507 */ 1508 public boolean removeAvd(AvdInfo avdInfo) { 1509 synchronized (mAllAvdList) { 1510 if (mAllAvdList.remove(avdInfo)) { 1511 mValidAvdList = mBrokenAvdList = null; 1512 return true; 1513 } 1514 } 1515 1516 return false; 1517 } 1518 1519 /** 1520 * Updates an AVD with new path to the system image folders. 1521 * @param name the name of the AVD to update. 1522 * @param log the log object to receive action logs. Cannot be null. 1523 * @throws IOException 1524 */ 1525 public void updateAvd(String name, ISdkLog log) throws IOException { 1526 // find the AVD to update. It should be be in the broken list. 1527 AvdInfo avd = null; 1528 synchronized (mAllAvdList) { 1529 for (AvdInfo info : mAllAvdList) { 1530 if (info.getName().equals(name)) { 1531 avd = info; 1532 break; 1533 } 1534 } 1535 } 1536 1537 if (avd == null) { 1538 // not in the broken list, just return. 1539 log.error(null, "There is no Android Virtual Device named '%s'.", name); 1540 return; 1541 } 1542 1543 updateAvd(avd, log); 1544 } 1545 1546 1547 /** 1548 * Updates an AVD with new path to the system image folders. 1549 * @param avd the AVD to update. 1550 * @param log the log object to receive action logs. Cannot be null. 1551 * @throws IOException 1552 */ 1553 public void updateAvd(AvdInfo avd, ISdkLog log) throws IOException { 1554 // get the properties. This is a unmodifiable Map. 1555 Map<String, String> oldProperties = avd.getProperties(); 1556 1557 // create a new map 1558 Map<String, String> properties = new HashMap<String, String>(); 1559 if (oldProperties != null) { 1560 properties.putAll(oldProperties); 1561 } 1562 1563 AvdStatus status; 1564 1565 // create the path to the new system images. 1566 if (setImagePathProperties(avd.getTarget(), avd.getAbiType(), properties, log)) { 1567 if (properties.containsKey(AVD_INI_IMAGES_1)) { 1568 log.printf("Updated '%1$s' with value '%2$s'\n", AVD_INI_IMAGES_1, 1569 properties.get(AVD_INI_IMAGES_1)); 1570 } 1571 1572 if (properties.containsKey(AVD_INI_IMAGES_2)) { 1573 log.printf("Updated '%1$s' with value '%2$s'\n", AVD_INI_IMAGES_2, 1574 properties.get(AVD_INI_IMAGES_2)); 1575 } 1576 1577 status = AvdStatus.OK; 1578 } else { 1579 log.error(null, "Unable to find non empty system images folders for %1$s", 1580 avd.getName()); 1581 //FIXME: display paths to empty image folders? 1582 status = AvdStatus.ERROR_IMAGE_DIR; 1583 } 1584 1585 // now write the config file 1586 File configIniFile = new File(avd.getDataFolderPath(), CONFIG_INI); 1587 writeIniFile(configIniFile, properties); 1588 1589 // finally create a new AvdInfo for this unbroken avd and add it to the list. 1590 // instead of creating the AvdInfo object directly we reparse it, to detect other possible 1591 // errors 1592 // FIXME: We may want to create this AvdInfo by reparsing the AVD instead. This could detect other errors. 1593 AvdInfo newAvd = new AvdInfo( 1594 avd.getName(), 1595 avd.getIniFile(), 1596 avd.getDataFolderPath(), 1597 avd.getTargetHash(), 1598 avd.getTarget(), 1599 avd.getAbiType(), 1600 properties, 1601 status); 1602 1603 replaceAvd(avd, newAvd); 1604 } 1605 1606 /** 1607 * Sets the paths to the system images in a properties map. 1608 * 1609 * @param target the target in which to find the system images. 1610 * @param abiType the abi type of the avd to find 1611 * the architecture-dependent system images. 1612 * @param properties the properties in which to set the paths. 1613 * @param log the log object to receive action logs. Cannot be null. 1614 * @return true if success, false if some path are missing. 1615 */ 1616 private boolean setImagePathProperties(IAndroidTarget target, 1617 String abiType, 1618 Map<String, String> properties, 1619 ISdkLog log) { 1620 properties.remove(AVD_INI_IMAGES_1); 1621 properties.remove(AVD_INI_IMAGES_2); 1622 1623 try { 1624 String property = AVD_INI_IMAGES_1; 1625 1626 // First the image folders of the target itself 1627 String imagePath = getImageRelativePath(target, abiType); 1628 if (imagePath != null) { 1629 properties.put(property, imagePath); 1630 property = AVD_INI_IMAGES_2; 1631 } 1632 1633 1634 // If the target is an add-on we need to add the Platform image as a backup. 1635 IAndroidTarget parent = target.getParent(); 1636 if (parent != null) { 1637 imagePath = getImageRelativePath(parent, abiType); 1638 if (imagePath != null) { 1639 properties.put(property, imagePath); 1640 } 1641 } 1642 1643 // we need at least one path! 1644 return properties.containsKey(AVD_INI_IMAGES_1); 1645 } catch (InvalidTargetPathException e) { 1646 log.error(e, e.getMessage()); 1647 } 1648 1649 return false; 1650 } 1651 1652 /** 1653 * Replaces an old {@link AvdInfo} with a new one in the lists storing them. 1654 * @param oldAvd the {@link AvdInfo} to remove. 1655 * @param newAvd the {@link AvdInfo} to add. 1656 */ 1657 private void replaceAvd(AvdInfo oldAvd, AvdInfo newAvd) { 1658 synchronized (mAllAvdList) { 1659 mAllAvdList.remove(oldAvd); 1660 mAllAvdList.add(newAvd); 1661 mValidAvdList = mBrokenAvdList = null; 1662 } 1663 } 1664 } 1665