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