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 InvalidTargetPathException(String message)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 */ AvdManager(SdkManager sdkManager, ISdkLog log)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 */ getBaseAvdFolder()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 */ getSdkManager()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 */ parseSdcardSize(String sdcard, String[] parsedStrings)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 */ getAllAvds()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 */ getValidAvds()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 */ getBrokenAvds()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 */ getAvd(String name, boolean validAvdOnly)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 */ isAvdNameConflicting(String name)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 */ reloadAvds(ISdkLog log)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 */ createAvd( File avdFolder, String avdName, IAndroidTarget target, String abiType, String skinName, String sdcard, Map<String,String> hardwareConfig, boolean createSnapshot, boolean removePrevious, boolean editExisting, ISdkLog log)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 */ copyImageFile(File source, File destination)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 */ getImageRelativePath(IAndroidTarget target, String abiType)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 */ getSkinRelativePath(String skinName, IAndroidTarget target, ISdkLog log)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 */ getSkinPath(String skinName, IAndroidTarget target)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 */ createAvdIniFile(String name, File avdFolder, IAndroidTarget target, boolean removePrevious)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 */ createAvdIniFile(AvdInfo info)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 */ deleteAvd(AvdInfo avdInfo, ISdkLog log)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 */ moveAvd(AvdInfo avdInfo, String newName, String paramFolderPath, ISdkLog log)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 */ deleteContentOf(File folder)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 */ buildAvdFilesList()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 */ buildAvdList(ArrayList<AvdInfo> allList, ISdkLog log)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 */ parseAvdInfo(File iniPath, ISdkLog log)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 */ writeIniFile(File iniFile, Map<String, String> values)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 */ createSdCard(String toolLocation, String size, String location, ISdkLog log)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 */ grabProcessOutput(final Process process, final ArrayList<String> errorOutput, final ArrayList<String> stdOutput, boolean waitforReaders)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 */ removeAvd(AvdInfo avdInfo)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 */ updateAvd(String name, ISdkLog log)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 */ updateAvd(AvdInfo avd, ISdkLog log)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 */ setImagePathProperties(IAndroidTarget target, String abiType, Map<String, String> properties, ISdkLog log)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 */ replaceAvd(AvdInfo oldAvd, AvdInfo newAvd)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