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.sdkmanager; 18 19 import com.android.annotations.VisibleForTesting; 20 import com.android.annotations.VisibleForTesting.Visibility; 21 import com.android.io.FileWrapper; 22 import com.android.prefs.AndroidLocation; 23 import com.android.prefs.AndroidLocation.AndroidLocationException; 24 import com.android.sdklib.IAndroidTarget; 25 import com.android.sdklib.IAndroidTarget.IOptionalLibrary; 26 import com.android.sdklib.ISdkLog; 27 import com.android.sdklib.ISystemImage; 28 import com.android.sdklib.SdkConstants; 29 import com.android.sdklib.SdkManager; 30 import com.android.sdklib.internal.avd.AvdInfo; 31 import com.android.sdklib.internal.avd.AvdManager; 32 import com.android.sdklib.internal.avd.HardwareProperties; 33 import com.android.sdklib.internal.avd.HardwareProperties.HardwareProperty; 34 import com.android.sdklib.internal.build.MakeIdentity; 35 import com.android.sdklib.internal.project.ProjectCreator; 36 import com.android.sdklib.internal.project.ProjectCreator.OutputLevel; 37 import com.android.sdklib.internal.project.ProjectProperties; 38 import com.android.sdklib.internal.project.ProjectProperties.PropertyType; 39 import com.android.sdklib.internal.repository.packages.PlatformToolPackage; 40 import com.android.sdklib.internal.repository.packages.ToolPackage; 41 import com.android.sdklib.repository.SdkAddonConstants; 42 import com.android.sdklib.repository.SdkRepoConstants; 43 import com.android.sdklib.xml.AndroidXPathFactory; 44 import com.android.sdkuilib.internal.repository.SdkUpdaterNoWindow; 45 import com.android.sdkuilib.internal.widgets.MessageBoxLog; 46 import com.android.sdkuilib.repository.AvdManagerWindow; 47 import com.android.sdkuilib.repository.AvdManagerWindow.AvdInvocationContext; 48 import com.android.sdkuilib.repository.SdkUpdaterWindow; 49 import com.android.sdkuilib.repository.SdkUpdaterWindow.SdkInvocationContext; 50 import com.android.util.Pair; 51 52 import org.eclipse.swt.widgets.Display; 53 import org.xml.sax.InputSource; 54 55 import java.io.File; 56 import java.io.FileInputStream; 57 import java.io.FileNotFoundException; 58 import java.io.IOException; 59 import java.util.ArrayList; 60 import java.util.Arrays; 61 import java.util.HashMap; 62 import java.util.Map; 63 import java.util.Set; 64 import java.util.TreeSet; 65 import java.util.concurrent.atomic.AtomicBoolean; 66 67 import javax.xml.xpath.XPath; 68 import javax.xml.xpath.XPathExpressionException; 69 70 /** 71 * Main class for the 'android' application. 72 */ 73 public class Main { 74 75 /** Java property that defines the location of the sdk/tools directory. */ 76 public final static String TOOLSDIR = "com.android.sdkmanager.toolsdir"; 77 /** Java property that defines the working directory. On Windows the current working directory 78 * is actually the tools dir, in which case this is used to get the original CWD. */ 79 private final static String WORKDIR = "com.android.sdkmanager.workdir"; 80 81 /** Value returned by {@link #resolveTargetName(String)} when the target id does not match. */ 82 private final static int INVALID_TARGET_ID = 0; 83 84 private final static String[] BOOLEAN_YES_REPLIES = new String[] { "yes", "y" }; 85 private final static String[] BOOLEAN_NO_REPLIES = new String[] { "no", "n" }; 86 87 /** Path to the SDK folder. This is the parent of {@link #TOOLSDIR}. */ 88 private String mOsSdkFolder; 89 /** Logger object. Use this to print normal output, warnings or errors. */ 90 private ISdkLog mSdkLog; 91 /** The SDK manager parses the SDK folder and gives access to the content. */ 92 private SdkManager mSdkManager; 93 /** Command-line processor with options specific to SdkManager. */ 94 private SdkCommandLine mSdkCommandLine; 95 /** The working directory, either null or set to an existing absolute canonical directory. */ 96 private File mWorkDir; 97 98 public static void main(String[] args) { 99 new Main().run(args); 100 } 101 102 /** Used by tests to set the sdk manager. */ 103 @VisibleForTesting(visibility=Visibility.PRIVATE) 104 void setSdkManager(SdkManager sdkManager) { 105 mSdkManager = sdkManager; 106 } 107 108 /** 109 * Runs the sdk manager app 110 */ 111 private void run(String[] args) { 112 createLogger(); 113 init(); 114 mSdkCommandLine.parseArgs(args); 115 parseSdk(); 116 doAction(); 117 } 118 119 /** 120 * Creates the {@link #mSdkLog} object. 121 * This must be done before {@link #init()} as it will be used to report errors. 122 * This logger prints to the attached console. 123 */ 124 private void createLogger() { 125 mSdkLog = new ISdkLog() { 126 @Override 127 public void error(Throwable t, String errorFormat, Object... args) { 128 if (errorFormat != null) { 129 System.err.printf("Error: " + errorFormat, args); 130 if (!errorFormat.endsWith("\n")) { 131 System.err.printf("\n"); 132 } 133 } 134 if (t != null) { 135 System.err.printf("Error: %s\n", t.getMessage()); 136 } 137 } 138 139 @Override 140 public void warning(String warningFormat, Object... args) { 141 if (mSdkCommandLine.isVerbose()) { 142 System.out.printf("Warning: " + warningFormat, args); 143 if (!warningFormat.endsWith("\n")) { 144 System.out.printf("\n"); 145 } 146 } 147 } 148 149 @Override 150 public void printf(String msgFormat, Object... args) { 151 System.out.printf(msgFormat, args); 152 } 153 }; 154 } 155 156 /** For testing */ 157 public void setLogger(ISdkLog logger) { 158 mSdkLog = logger; 159 } 160 161 /** 162 * Init the application by making sure the SDK path is available and 163 * doing basic parsing of the SDK. 164 */ 165 private void init() { 166 mSdkCommandLine = new SdkCommandLine(mSdkLog); 167 168 // We get passed a property for the tools dir 169 String toolsDirProp = System.getProperty(TOOLSDIR); 170 if (toolsDirProp == null) { 171 // for debugging, it's easier to override using the process environment 172 toolsDirProp = System.getenv(TOOLSDIR); 173 } 174 175 if (toolsDirProp != null) { 176 // got back a level for the SDK folder 177 File tools; 178 if (toolsDirProp.length() > 0) { 179 tools = new File(toolsDirProp); 180 mOsSdkFolder = tools.getParent(); 181 } else { 182 try { 183 tools = new File(".").getCanonicalFile(); 184 mOsSdkFolder = tools.getParent(); 185 } catch (IOException e) { 186 // Will print an error below since mSdkFolder is not defined 187 } 188 } 189 } 190 191 if (mOsSdkFolder == null) { 192 errorAndExit("The tools directory property is not set, please make sure you are executing %1$s", 193 SdkConstants.androidCmdName()); 194 } 195 196 // We might get passed a property for the working directory 197 // Either it is a valid directory and mWorkDir is set to it's absolute canonical value 198 // or mWorkDir remains null. 199 String workDirProp = System.getProperty(WORKDIR); 200 if (workDirProp == null) { 201 workDirProp = System.getenv(WORKDIR); 202 } 203 if (workDirProp != null) { 204 // This should be a valid directory 205 mWorkDir = new File(workDirProp); 206 try { 207 mWorkDir = mWorkDir.getCanonicalFile().getAbsoluteFile(); 208 } catch (IOException e) { 209 mWorkDir = null; 210 } 211 if (mWorkDir == null || !mWorkDir.isDirectory()) { 212 errorAndExit("The working directory does not seem to be valid: '%1$s", workDirProp); 213 } 214 } 215 } 216 217 /** 218 * Does the basic SDK parsing required for all actions 219 */ 220 private void parseSdk() { 221 mSdkManager = SdkManager.createManager(mOsSdkFolder, mSdkLog); 222 223 if (mSdkManager == null) { 224 errorAndExit("Unable to parse SDK content."); 225 } 226 } 227 228 /** 229 * Actually do an action... 230 */ 231 private void doAction() { 232 String verb = mSdkCommandLine.getVerb(); 233 String directObject = mSdkCommandLine.getDirectObject(); 234 235 if (SdkCommandLine.VERB_LIST.equals(verb)) { 236 // list action. 237 if (SdkCommandLine.OBJECT_TARGET.equals(directObject)) { 238 displayTargetList(); 239 240 } else if (SdkCommandLine.OBJECT_AVD.equals(directObject)) { 241 displayAvdList(); 242 243 } else if (SdkCommandLine.OBJECT_SDK.equals(directObject)) { 244 displayRemoteSdkListNoUI(); 245 246 } else { 247 displayTargetList(); 248 displayAvdList(); 249 } 250 251 } else if (SdkCommandLine.VERB_CREATE.equals(verb)) { 252 if (SdkCommandLine.OBJECT_AVD.equals(directObject)) { 253 createAvd(); 254 255 } else if (SdkCommandLine.OBJECT_PROJECT.equals(directObject)) { 256 createProject(false /*library*/); 257 258 } else if (SdkCommandLine.OBJECT_TEST_PROJECT.equals(directObject)) { 259 createTestProject(); 260 261 } else if (SdkCommandLine.OBJECT_LIB_PROJECT.equals(directObject)) { 262 createProject(true /*library*/); 263 264 } else if (SdkCommandLine.OBJECT_IDENTITY.equals(directObject)) { 265 createIdentity(); 266 267 } 268 } else if (SdkCommandLine.VERB_UPDATE.equals(verb)) { 269 if (SdkCommandLine.OBJECT_AVD.equals(directObject)) { 270 updateAvd(); 271 272 } else if (SdkCommandLine.OBJECT_PROJECT.equals(directObject)) { 273 updateProject(false /*library*/); 274 275 } else if (SdkCommandLine.OBJECT_TEST_PROJECT.equals(directObject)) { 276 updateTestProject(); 277 278 } else if (SdkCommandLine.OBJECT_LIB_PROJECT.equals(directObject)) { 279 updateProject(true /*library*/); 280 281 } else if (SdkCommandLine.OBJECT_SDK.equals(directObject)) { 282 if (mSdkCommandLine.getFlagNoUI(verb)) { 283 updateSdkNoUI(); 284 } else { 285 showSdkManagerWindow(); 286 } 287 288 } else if (SdkCommandLine.OBJECT_ADB.equals(directObject)) { 289 updateAdb(); 290 291 } 292 } else if (SdkCommandLine.VERB_SDK.equals(verb)) { 293 showSdkManagerWindow(); 294 295 } else if (SdkCommandLine.VERB_AVD.equals(verb)) { 296 showAvdManagerWindow(); 297 298 } else if (SdkCommandLine.VERB_DELETE.equals(verb) && 299 SdkCommandLine.OBJECT_AVD.equals(directObject)) { 300 deleteAvd(); 301 302 } else if (SdkCommandLine.VERB_MOVE.equals(verb) && 303 SdkCommandLine.OBJECT_AVD.equals(directObject)) { 304 moveAvd(); 305 306 } else if (verb == null && directObject == null) { 307 showSdkManagerWindow(); 308 309 } else { 310 mSdkCommandLine.printHelpAndExit(null); 311 } 312 } 313 314 /** 315 * Display the main SDK Manager app window 316 */ 317 private void showSdkManagerWindow() { 318 try { 319 MessageBoxLog errorLogger = new MessageBoxLog( 320 "SDK Manager", 321 Display.getCurrent(), 322 true /*logErrorsOnly*/); 323 324 SdkUpdaterWindow window = new SdkUpdaterWindow( 325 null /* parentShell */, 326 errorLogger, 327 mOsSdkFolder, 328 SdkInvocationContext.STANDALONE); 329 window.open(); 330 331 errorLogger.displayResult(true); 332 333 } catch (Exception e) { 334 e.printStackTrace(); 335 } 336 } 337 338 /** 339 * Display the main AVD Manager app window 340 */ 341 private void showAvdManagerWindow() { 342 try { 343 MessageBoxLog errorLogger = new MessageBoxLog( 344 "AVD Manager", 345 Display.getCurrent(), 346 true /*logErrorsOnly*/); 347 348 AvdManagerWindow window = new AvdManagerWindow( 349 null /* parentShell */, 350 errorLogger, 351 mOsSdkFolder, 352 AvdInvocationContext.STANDALONE); 353 354 window.open(); 355 356 errorLogger.displayResult(true); 357 358 } catch (Exception e) { 359 e.printStackTrace(); 360 } 361 } 362 363 private void displayRemoteSdkListNoUI() { 364 boolean force = mSdkCommandLine.getFlagForce(); 365 boolean useHttp = mSdkCommandLine.getFlagNoHttps(); 366 boolean all = mSdkCommandLine.getFlagAll(); 367 boolean extended = mSdkCommandLine.getFlagExtended(); 368 String proxyHost = mSdkCommandLine.getParamProxyHost(); 369 String proxyPort = mSdkCommandLine.getParamProxyPort(); 370 371 boolean obsolete = mSdkCommandLine.getFlagObsolete(); 372 all |= obsolete; 373 374 SdkUpdaterNoWindow upd = new SdkUpdaterNoWindow( 375 mOsSdkFolder, 376 mSdkManager, 377 mSdkLog, 378 force, 379 useHttp, 380 proxyHost, 381 proxyPort); 382 upd.listRemotePackages(all, extended); 383 384 if (obsolete) { 385 mSdkLog.printf("Note: Flag --obsolete is deprecated and will be removed in the next version.\n Please use --all instead.\n"); 386 } 387 } 388 389 /** 390 * Updates the whole SDK without any UI, just using console output. 391 */ 392 private void updateSdkNoUI() { 393 boolean force = mSdkCommandLine.getFlagForce(); 394 boolean useHttp = mSdkCommandLine.getFlagNoHttps(); 395 boolean dryMode = mSdkCommandLine.getFlagDryMode(); 396 boolean all = mSdkCommandLine.getFlagAll(); 397 String proxyHost = mSdkCommandLine.getParamProxyHost(); 398 String proxyPort = mSdkCommandLine.getParamProxyPort(); 399 400 boolean obsolete = mSdkCommandLine.getFlagObsolete(); 401 all |= obsolete; 402 403 // Check filter types. 404 Pair<String, ArrayList<String>> filterResult = 405 checkFilterValues(mSdkCommandLine.getParamFilter()); 406 if (filterResult.getFirst() != null) { 407 // We got an error. 408 errorAndExit(filterResult.getFirst()); 409 } 410 411 SdkUpdaterNoWindow upd = new SdkUpdaterNoWindow( 412 mOsSdkFolder, 413 mSdkManager, 414 mSdkLog, 415 force, 416 useHttp, 417 proxyHost, 418 proxyPort); 419 upd.updateAll(filterResult.getSecond(), all, dryMode); 420 421 if (obsolete) { 422 mSdkLog.printf("Note: Flag --obsolete is deprecated and will be removed in the next version.\n Please use --all instead.\n"); 423 } 424 } 425 426 /** 427 * Checks the values from the filter parameter and returns a tuple 428 * (error , accepted values). Either error is null and accepted values is not, 429 * or the reverse. 430 * <p/> 431 * Note that this is a quick sanity check of the --filter parameter *before* we 432 * start loading the remote repository sources. Loading the remotes takes a while 433 * so it's worth doing a quick sanity check before hand. 434 * 435 * @param filter A comma-separated list of keywords 436 * @return A pair <error string, usable values>, only one must be null and the other non-null. 437 */ 438 @VisibleForTesting(visibility=Visibility.PRIVATE) 439 Pair<String, ArrayList<String>> checkFilterValues(String filter) { 440 ArrayList<String> pkgFilter = new ArrayList<String>(); 441 442 if (filter != null && filter.length() > 0) { 443 // Available types 444 Set<String> filterTypes = new TreeSet<String>(); 445 filterTypes.addAll(Arrays.asList(SdkRepoConstants.NODES)); 446 filterTypes.addAll(Arrays.asList(SdkAddonConstants.NODES)); 447 448 for (String t : filter.split(",")) { //$NON-NLS-1$ 449 if (t == null) { 450 continue; 451 } 452 t = t.trim(); 453 if (t.length() <= 0) { 454 continue; 455 } 456 457 if (t.indexOf('-') > 0 || 458 t.equals(ToolPackage.INSTALL_ID) || 459 t.equals(PlatformToolPackage.INSTALL_ID)) { 460 // Heuristic: if the filter name contains a dash, it is probably 461 // a variable package install id. Since we haven't loaded the remote 462 // repositories we can't validate it yet, so just accept it. 463 pkgFilter.add(t); 464 continue; 465 } 466 467 if (t.replaceAll("[0-9]+", "").length() == 0) { //$NON-NLS-1$ //$NON-NLS-2$ 468 // If the filter argument *only* contains digits, accept it. 469 // It's probably an index for the remote repository list, 470 // which we can't validate yet. 471 pkgFilter.add(t); 472 continue; 473 } 474 475 if (filterTypes.contains(t)) { 476 pkgFilter.add(t); 477 continue; 478 } 479 480 return Pair.of( 481 String.format( 482 "Unknown package filter type '%1$s'.\nAccepted values are: %2$s", 483 t, 484 Arrays.toString(filterTypes.toArray())), 485 null); 486 } 487 } 488 489 return Pair.of(null, pkgFilter); 490 } 491 492 /** 493 * Returns a configured {@link ProjectCreator} instance. 494 */ 495 private ProjectCreator getProjectCreator() { 496 ProjectCreator creator = new ProjectCreator(mSdkManager, mOsSdkFolder, 497 mSdkCommandLine.isVerbose() ? OutputLevel.VERBOSE : 498 mSdkCommandLine.isSilent() ? OutputLevel.SILENT : 499 OutputLevel.NORMAL, 500 mSdkLog); 501 return creator; 502 } 503 504 505 /** 506 * Creates a new Android project based on command-line parameters 507 */ 508 private void createProject(boolean library) { 509 String directObject = library? SdkCommandLine.OBJECT_LIB_PROJECT : 510 SdkCommandLine.OBJECT_PROJECT; 511 512 // get the target and try to resolve it. 513 int targetId = resolveTargetName(mSdkCommandLine.getParamTargetId()); 514 IAndroidTarget[] targets = mSdkManager.getTargets(); 515 if (targetId == INVALID_TARGET_ID || targetId > targets.length) { 516 errorAndExit("Target id is not valid. Use '%s list targets' to get the target ids.", 517 SdkConstants.androidCmdName()); 518 } 519 IAndroidTarget target = targets[targetId - 1]; // target id is 1-based 520 521 ProjectCreator creator = getProjectCreator(); 522 523 String projectDir = getProjectLocation(mSdkCommandLine.getParamLocationPath()); 524 525 String projectName = mSdkCommandLine.getParamName(); 526 String packageName = mSdkCommandLine.getParamProjectPackage(directObject); 527 String activityName = null; 528 if (library == false) { 529 activityName = mSdkCommandLine.getParamProjectActivity(); 530 } 531 532 if (projectName != null && 533 !ProjectCreator.RE_PROJECT_NAME.matcher(projectName).matches()) { 534 errorAndExit( 535 "Project name '%1$s' contains invalid characters.\nAllowed characters are: %2$s", 536 projectName, ProjectCreator.CHARS_PROJECT_NAME); 537 return; 538 } 539 540 if (activityName != null && 541 !ProjectCreator.RE_ACTIVITY_NAME.matcher(activityName).matches()) { 542 errorAndExit( 543 "Activity name '%1$s' contains invalid characters.\nAllowed characters are: %2$s", 544 activityName, ProjectCreator.CHARS_ACTIVITY_NAME); 545 return; 546 } 547 548 if (packageName != null && 549 !ProjectCreator.RE_PACKAGE_NAME.matcher(packageName).matches()) { 550 errorAndExit( 551 "Package name '%1$s' contains invalid characters.\n" + 552 "A package name must be constitued of two Java identifiers.\n" + 553 "Each identifier allowed characters are: %2$s", 554 packageName, ProjectCreator.CHARS_PACKAGE_NAME); 555 return; 556 } 557 558 creator.createProject(projectDir, 559 projectName, 560 packageName, 561 activityName, 562 target, 563 library, 564 null /*pathToMain*/); 565 } 566 567 /** 568 * Creates a new Android test project based on command-line parameters 569 */ 570 private void createTestProject() { 571 572 String projectDir = getProjectLocation(mSdkCommandLine.getParamLocationPath()); 573 574 // first check the path of the parent project, and make sure it's valid. 575 String pathToMainProject = mSdkCommandLine.getParamTestProjectMain(); 576 577 File parentProject = new File(pathToMainProject); 578 if (parentProject.isAbsolute() == false) { 579 // if the path is not absolute, we need to resolve it based on the 580 // destination path of the project 581 try { 582 parentProject = new File(projectDir, pathToMainProject).getCanonicalFile(); 583 } catch (IOException e) { 584 errorAndExit("Unable to resolve Main project's directory: %1$s", 585 pathToMainProject); 586 return; // help Eclipse static analyzer understand we'll never execute the rest. 587 } 588 } 589 590 if (parentProject.isDirectory() == false) { 591 errorAndExit("Main project's directory does not exist: %1$s", 592 pathToMainProject); 593 return; 594 } 595 596 // now look for a manifest in there 597 File manifest = new File(parentProject, SdkConstants.FN_ANDROID_MANIFEST_XML); 598 if (manifest.isFile() == false) { 599 errorAndExit("No AndroidManifest.xml file found in the main project directory: %1$s", 600 parentProject.getAbsolutePath()); 601 return; 602 } 603 604 // now query the manifest for the package file. 605 XPath xpath = AndroidXPathFactory.newXPath(); 606 String packageName, activityName; 607 608 try { 609 packageName = xpath.evaluate("/manifest/@package", 610 new InputSource(new FileInputStream(manifest))); 611 612 mSdkLog.printf("Found main project package: %1$s\n", packageName); 613 614 // now get the name of the first activity we find 615 activityName = xpath.evaluate("/manifest/application/activity[1]/@android:name", 616 new InputSource(new FileInputStream(manifest))); 617 // xpath will return empty string when there's no match 618 if (activityName == null || activityName.length() == 0) { 619 activityName = null; 620 } else { 621 mSdkLog.printf("Found main project activity: %1$s\n", activityName); 622 } 623 } catch (FileNotFoundException e) { 624 // this shouldn't happen as we test it above. 625 errorAndExit("No AndroidManifest.xml file found in main project."); 626 return; // this is not strictly needed because errorAndExit will stop the execution, 627 // but this makes the java compiler happy, wrt to uninitialized variables. 628 } catch (XPathExpressionException e) { 629 // looks like the main manifest is not valid. 630 errorAndExit("Unable to parse main project manifest to get information."); 631 return; // this is not strictly needed because errorAndExit will stop the execution, 632 // but this makes the java compiler happy, wrt to uninitialized variables. 633 } 634 635 // now get the target hash 636 ProjectProperties p = ProjectProperties.load(parentProject.getAbsolutePath(), 637 PropertyType.PROJECT); 638 if (p == null) { 639 errorAndExit("Unable to load the main project's %1$s", 640 PropertyType.PROJECT.getFilename()); 641 return; 642 } 643 644 String targetHash = p.getProperty(ProjectProperties.PROPERTY_TARGET); 645 if (targetHash == null) { 646 errorAndExit("Couldn't find the main project target"); 647 return; 648 } 649 650 // and resolve it. 651 IAndroidTarget target = mSdkManager.getTargetFromHashString(targetHash); 652 if (target == null) { 653 errorAndExit( 654 "Unable to resolve main project target '%1$s'. You may want to install the platform in your SDK.", 655 targetHash); 656 return; 657 } 658 659 mSdkLog.printf("Found main project target: %1$s\n", target.getFullName()); 660 661 ProjectCreator creator = getProjectCreator(); 662 663 String projectName = mSdkCommandLine.getParamName(); 664 665 if (projectName != null && 666 !ProjectCreator.RE_PROJECT_NAME.matcher(projectName).matches()) { 667 errorAndExit( 668 "Project name '%1$s' contains invalid characters.\nAllowed characters are: %2$s", 669 projectName, ProjectCreator.CHARS_PROJECT_NAME); 670 return; 671 } 672 673 creator.createProject(projectDir, 674 projectName, 675 packageName, 676 activityName, 677 target, 678 false /* library*/, 679 pathToMainProject); 680 } 681 682 /** 683 * Updates an existing Android project based on command-line parameters 684 * @param library whether the project is a library project. 685 */ 686 private void updateProject(boolean library) { 687 // get the target and try to resolve it. 688 IAndroidTarget target = null; 689 String targetStr = mSdkCommandLine.getParamTargetId(); 690 // For "update project" the target parameter is optional so having null is acceptable. 691 // However if there's a value, it must be valid. 692 if (targetStr != null) { 693 IAndroidTarget[] targets = mSdkManager.getTargets(); 694 int targetId = resolveTargetName(targetStr); 695 if (targetId == INVALID_TARGET_ID || targetId > targets.length) { 696 errorAndExit("Target id '%1$s' is not valid. Use '%2$s list targets' to get the target ids.", 697 targetStr, 698 SdkConstants.androidCmdName()); 699 } 700 target = targets[targetId - 1]; // target id is 1-based 701 } 702 703 ProjectCreator creator = getProjectCreator(); 704 705 String projectDir = getProjectLocation(mSdkCommandLine.getParamLocationPath()); 706 707 String libraryPath = library ? null : 708 mSdkCommandLine.getParamProjectLibrary(SdkCommandLine.OBJECT_PROJECT); 709 710 creator.updateProject(projectDir, 711 target, 712 mSdkCommandLine.getParamName(), 713 libraryPath); 714 715 if (library == false) { 716 boolean doSubProjects = mSdkCommandLine.getParamSubProject(); 717 boolean couldHaveDone = false; 718 719 // If there are any sub-folders with a manifest, try to update them as projects 720 // too. This will take care of updating any underlying test project even if the 721 // user changed the folder name. 722 File[] files = new File(projectDir).listFiles(); 723 if (files != null) { 724 for (File dir : files) { 725 if (dir.isDirectory() && 726 new File(dir, SdkConstants.FN_ANDROID_MANIFEST_XML).isFile()) { 727 if (doSubProjects) { 728 creator.updateProject(dir.getPath(), 729 target, 730 mSdkCommandLine.getParamName(), 731 null /*libraryPath*/); 732 } else { 733 couldHaveDone = true; 734 } 735 } 736 } 737 } 738 739 if (couldHaveDone) { 740 mSdkLog.printf( 741 "It seems that there are sub-projects. If you want to update them\nplease use the --%1$s parameter.\n", 742 SdkCommandLine.KEY_SUBPROJECTS); 743 } 744 } 745 } 746 747 /** 748 * Updates an existing test project with a new path to the main project. 749 */ 750 private void updateTestProject() { 751 ProjectCreator creator = getProjectCreator(); 752 753 String projectDir = getProjectLocation(mSdkCommandLine.getParamLocationPath()); 754 755 creator.updateTestProject(projectDir, mSdkCommandLine.getParamTestProjectMain(), 756 mSdkManager); 757 } 758 759 /** 760 * Adjusts the project location to make it absolute & canonical relative to the 761 * working directory, if any. 762 * 763 * @return The project absolute path relative to {@link #mWorkDir} or the original 764 * newProjectLocation otherwise. 765 */ 766 private String getProjectLocation(String newProjectLocation) { 767 768 // If the new project location is absolute, use it as-is 769 File projectDir = new File(newProjectLocation); 770 if (projectDir.isAbsolute()) { 771 return newProjectLocation; 772 } 773 774 // if there's no working directory, just use the project location as-is. 775 if (mWorkDir == null) { 776 return newProjectLocation; 777 } 778 779 // Combine then and get an absolute canonical directory 780 try { 781 projectDir = new File(mWorkDir, newProjectLocation).getCanonicalFile(); 782 783 return projectDir.getPath(); 784 } catch (IOException e) { 785 errorAndExit("Failed to combine working directory '%1$s' with project location '%2$s': %3$s", 786 mWorkDir.getPath(), 787 newProjectLocation, 788 e.getMessage()); 789 return null; 790 } 791 } 792 793 /** 794 * Displays the list of available Targets (Platforms and Add-ons) 795 */ 796 @VisibleForTesting(visibility=Visibility.PRIVATE) 797 void displayTargetList() { 798 799 // Compact output, suitable for scripts. 800 if (mSdkCommandLine != null && mSdkCommandLine.getFlagCompact()) { 801 char eol = mSdkCommandLine.getFlagEolNull() ? '\0' : '\n'; 802 803 for (IAndroidTarget target : mSdkManager.getTargets()) { 804 mSdkLog.printf("%1$s%2$c", target.hashString(), eol); 805 } 806 807 return; 808 } 809 810 mSdkLog.printf("Available Android targets:\n"); 811 812 int index = 1; 813 for (IAndroidTarget target : mSdkManager.getTargets()) { 814 mSdkLog.printf("----------\n"); 815 mSdkLog.printf("id: %1$d or \"%2$s\"\n", index, target.hashString()); 816 mSdkLog.printf(" Name: %s\n", target.getName()); 817 if (target.isPlatform()) { 818 mSdkLog.printf(" Type: Platform\n"); 819 mSdkLog.printf(" API level: %s\n", target.getVersion().getApiString()); 820 mSdkLog.printf(" Revision: %d\n", target.getRevision()); 821 } else { 822 mSdkLog.printf(" Type: Add-On\n"); 823 mSdkLog.printf(" Vendor: %s\n", target.getVendor()); 824 mSdkLog.printf(" Revision: %d\n", target.getRevision()); 825 if (target.getDescription() != null) { 826 mSdkLog.printf(" Description: %s\n", target.getDescription()); 827 } 828 mSdkLog.printf(" Based on Android %s (API level %s)\n", 829 target.getVersionName(), target.getVersion().getApiString()); 830 831 // display the optional libraries. 832 IOptionalLibrary[] libraries = target.getOptionalLibraries(); 833 if (libraries != null) { 834 mSdkLog.printf(" Libraries:\n"); 835 for (IOptionalLibrary library : libraries) { 836 mSdkLog.printf(" * %1$s (%2$s)\n", 837 library.getName(), library.getJarName()); 838 mSdkLog.printf(" %1$s\n", library.getDescription()); 839 } 840 } 841 } 842 843 // get the target skins & ABIs 844 displaySkinList(target, " Skins: "); 845 displayAbiList (target, " ABIs : "); 846 847 if (target.getUsbVendorId() != IAndroidTarget.NO_USB_ID) { 848 mSdkLog.printf(" Adds USB support for devices (Vendor: 0x%04X)\n", 849 target.getUsbVendorId()); 850 } 851 852 index++; 853 } 854 } 855 856 /** 857 * Displays the skins valid for the given target. 858 */ 859 @VisibleForTesting(visibility=Visibility.PRIVATE) 860 void displaySkinList(IAndroidTarget target, String message) { 861 String[] skins = target.getSkins(); 862 String defaultSkin = target.getDefaultSkin(); 863 mSdkLog.printf(message); 864 if (skins != null) { 865 boolean first = true; 866 for (String skin : skins) { 867 if (first == false) { 868 mSdkLog.printf(", "); 869 } else { 870 first = false; 871 } 872 mSdkLog.printf(skin); 873 874 if (skin.equals(defaultSkin)) { 875 mSdkLog.printf(" (default)"); 876 } 877 } 878 mSdkLog.printf("\n"); 879 } else { 880 mSdkLog.printf("no skins.\n"); 881 } 882 } 883 884 /** 885 * Displays the ABIs valid for the given target. 886 */ 887 @VisibleForTesting(visibility=Visibility.PRIVATE) 888 void displayAbiList(IAndroidTarget target, String message) { 889 ISystemImage[] systemImages = target.getSystemImages(); 890 mSdkLog.printf(message); 891 if (systemImages.length > 0) { 892 boolean first = true; 893 for (ISystemImage si : systemImages) { 894 if (first == false) { 895 mSdkLog.printf(", "); 896 } else { 897 first = false; 898 } 899 mSdkLog.printf(si.getAbiType()); 900 } 901 mSdkLog.printf("\n"); 902 } else { 903 mSdkLog.printf("no ABIs.\n"); 904 } 905 } 906 907 /** 908 * Displays the list of available AVDs for the given AvdManager. 909 * 910 * @param avdManager 911 */ 912 @VisibleForTesting(visibility=Visibility.PRIVATE) 913 void displayAvdList(AvdManager avdManager) { 914 915 AvdInfo[] avds = avdManager.getValidAvds(); 916 917 // Compact output, suitable for scripts. 918 if (mSdkCommandLine != null && mSdkCommandLine.getFlagCompact()) { 919 char eol = mSdkCommandLine.getFlagEolNull() ? '\0' : '\n'; 920 921 for (int index = 0 ; index < avds.length ; index++) { 922 AvdInfo info = avds[index]; 923 mSdkLog.printf("%1$s%2$c", info.getName(), eol); 924 } 925 926 return; 927 } 928 929 mSdkLog.printf("Available Android Virtual Devices:\n"); 930 931 for (int index = 0 ; index < avds.length ; index++) { 932 AvdInfo info = avds[index]; 933 if (index > 0) { 934 mSdkLog.printf("---------\n"); 935 } 936 mSdkLog.printf(" Name: %s\n", info.getName()); 937 mSdkLog.printf(" Path: %s\n", info.getDataFolderPath()); 938 939 // get the target of the AVD 940 IAndroidTarget target = info.getTarget(); 941 if (target.isPlatform()) { 942 mSdkLog.printf(" Target: %s (API level %s)\n", target.getName(), 943 target.getVersion().getApiString()); 944 } else { 945 mSdkLog.printf(" Target: %s (%s)\n", target.getName(), target 946 .getVendor()); 947 mSdkLog.printf(" Based on Android %s (API level %s)\n", 948 target.getVersionName(), target.getVersion().getApiString()); 949 } 950 mSdkLog.printf(" ABI: %s\n", info.getAbiType()); 951 952 // display some extra values. 953 Map<String, String> properties = info.getProperties(); 954 if (properties != null) { 955 String skin = properties.get(AvdManager.AVD_INI_SKIN_NAME); 956 if (skin != null) { 957 mSdkLog.printf(" Skin: %s\n", skin); 958 } 959 String sdcard = properties.get(AvdManager.AVD_INI_SDCARD_SIZE); 960 if (sdcard == null) { 961 sdcard = properties.get(AvdManager.AVD_INI_SDCARD_PATH); 962 } 963 if (sdcard != null) { 964 mSdkLog.printf(" Sdcard: %s\n", sdcard); 965 } 966 String snapshot = properties.get(AvdManager.AVD_INI_SNAPSHOT_PRESENT); 967 if (snapshot != null) { 968 mSdkLog.printf("Snapshot: %s\n", snapshot); 969 } 970 } 971 } 972 973 // Are there some unused AVDs? 974 AvdInfo[] badAvds = avdManager.getBrokenAvds(); 975 976 if (badAvds.length == 0) { 977 return; 978 } 979 980 mSdkLog.printf("\nThe following Android Virtual Devices could not be loaded:\n"); 981 boolean needSeparator = false; 982 for (AvdInfo info : badAvds) { 983 if (needSeparator) { 984 mSdkLog.printf("---------\n"); 985 } 986 mSdkLog.printf(" Name: %s\n", info.getName() == null ? "--" : info.getName()); 987 mSdkLog.printf(" Path: %s\n", 988 info.getDataFolderPath() == null ? "--" : info.getDataFolderPath()); 989 990 String error = info.getErrorMessage(); 991 mSdkLog.printf(" Error: %s\n", error == null ? "Uknown error" : error); 992 needSeparator = true; 993 } 994 } 995 996 /** 997 * Displays the list of available AVDs. 998 */ 999 private void displayAvdList() { 1000 try { 1001 AvdManager avdManager = new AvdManager(mSdkManager, mSdkLog); 1002 displayAvdList(avdManager); 1003 } catch (AndroidLocationException e) { 1004 errorAndExit(e.getMessage()); 1005 } 1006 } 1007 1008 /** 1009 * Creates a new AVD. This is a text based creation with command line prompt. 1010 */ 1011 private void createAvd() { 1012 // find a matching target 1013 int targetId = resolveTargetName(mSdkCommandLine.getParamTargetId()); 1014 IAndroidTarget[] targets = mSdkManager.getTargets(); 1015 1016 if (targetId == INVALID_TARGET_ID || targetId > targets.length) { 1017 errorAndExit("Target id is not valid. Use '%s list targets' to get the target ids.", 1018 SdkConstants.androidCmdName()); 1019 } 1020 1021 IAndroidTarget target = targets[targetId-1]; // target id is 1-based 1022 1023 try { 1024 boolean removePrevious = mSdkCommandLine.getFlagForce(); 1025 AvdManager avdManager = new AvdManager(mSdkManager, mSdkLog); 1026 1027 String avdName = mSdkCommandLine.getParamName(); 1028 1029 if (!AvdManager.RE_AVD_NAME.matcher(avdName).matches()) { 1030 errorAndExit( 1031 "AVD name '%1$s' contains invalid characters.\nAllowed characters are: %2$s", 1032 avdName, AvdManager.CHARS_AVD_NAME); 1033 return; 1034 } 1035 1036 AvdInfo info = avdManager.getAvd(avdName, false /*validAvdOnly*/); 1037 if (info != null) { 1038 if (removePrevious) { 1039 mSdkLog.warning( 1040 "Android Virtual Device '%s' already exists and will be replaced.", 1041 avdName); 1042 } else { 1043 errorAndExit("Android Virtual Device '%s' already exists.\n" + 1044 "Use --force if you want to replace it.", 1045 avdName); 1046 return; 1047 } 1048 } 1049 1050 String paramFolderPath = mSdkCommandLine.getParamLocationPath(); 1051 File avdFolder = null; 1052 if (paramFolderPath != null) { 1053 avdFolder = new File(paramFolderPath); 1054 } else { 1055 avdFolder = AvdInfo.getDefaultAvdFolder(avdManager, avdName); 1056 } 1057 1058 // Validate skin is either default (empty) or NNNxMMM or a valid skin name. 1059 Map<String, String> skinHardwareConfig = null; 1060 String skin = mSdkCommandLine.getParamSkin(); 1061 if (skin != null && skin.length() == 0) { 1062 skin = null; 1063 } 1064 1065 if (skin != null && target != null) { 1066 boolean valid = false; 1067 // Is it a know skin name for this target? 1068 for (String s : target.getSkins()) { 1069 if (skin.equalsIgnoreCase(s)) { 1070 skin = s; // Make skin names case-insensitive. 1071 valid = true; 1072 1073 // get the hardware properties for this skin 1074 File skinFolder = avdManager.getSkinPath(skin, target); 1075 FileWrapper skinHardwareFile = new FileWrapper(skinFolder, 1076 AvdManager.HARDWARE_INI); 1077 if (skinHardwareFile.isFile()) { 1078 skinHardwareConfig = ProjectProperties.parsePropertyFile( 1079 skinHardwareFile, mSdkLog); 1080 } 1081 break; 1082 } 1083 } 1084 1085 // Is it NNNxMMM? 1086 if (!valid) { 1087 valid = AvdManager.NUMERIC_SKIN_SIZE.matcher(skin).matches(); 1088 } 1089 1090 if (!valid) { 1091 displaySkinList(target, "Valid skins: "); 1092 errorAndExit("'%s' is not a valid skin name or size (NNNxMMM)", skin); 1093 return; 1094 } 1095 } 1096 1097 String abiType = mSdkCommandLine.getParamAbi(); 1098 if (target != null && (abiType == null || abiType.length() == 0)) { 1099 ISystemImage[] systemImages = target.getSystemImages(); 1100 if (systemImages != null && systemImages.length == 1) { 1101 // Auto-select the single ABI available 1102 abiType = systemImages[0].getAbiType(); 1103 mSdkLog.printf("Auto-selecting single ABI %1$s\n", abiType); 1104 } else { 1105 displayAbiList(target, "Valid ABIs: "); 1106 errorAndExit("This platform has more than one ABI. Please specify one using --%1$s.", 1107 SdkCommandLine.KEY_ABI); 1108 } 1109 } 1110 1111 Map<String, String> hardwareConfig = null; 1112 if (target != null && target.isPlatform()) { 1113 try { 1114 hardwareConfig = promptForHardware(target, skinHardwareConfig); 1115 } catch (IOException e) { 1116 errorAndExit(e.getMessage()); 1117 } 1118 } 1119 1120 @SuppressWarnings("unused") // oldAvdInfo is never read, yet useful for debugging 1121 AvdInfo oldAvdInfo = null; 1122 if (removePrevious) { 1123 oldAvdInfo = avdManager.getAvd(avdName, false /*validAvdOnly*/); 1124 } 1125 1126 @SuppressWarnings("unused") // newAvdInfo is never read, yet useful for debugging 1127 AvdInfo newAvdInfo = avdManager.createAvd(avdFolder, 1128 avdName, 1129 target, 1130 abiType, 1131 skin, 1132 mSdkCommandLine.getParamSdCard(), 1133 hardwareConfig, 1134 mSdkCommandLine.getFlagSnapshot(), 1135 removePrevious, 1136 false, //edit existing 1137 mSdkLog); 1138 1139 } catch (AndroidLocationException e) { 1140 errorAndExit(e.getMessage()); 1141 } 1142 } 1143 1144 /** 1145 * Delete an AVD. If the AVD name is not part of the available ones look for an 1146 * invalid AVD (one not loaded due to some error) to remove it too. 1147 */ 1148 private void deleteAvd() { 1149 try { 1150 String avdName = mSdkCommandLine.getParamName(); 1151 AvdManager avdManager = new AvdManager(mSdkManager, mSdkLog); 1152 AvdInfo info = avdManager.getAvd(avdName, false /*validAvdOnly*/); 1153 1154 if (info == null) { 1155 errorAndExit("There is no Android Virtual Device named '%s'.", avdName); 1156 return; 1157 } 1158 1159 avdManager.deleteAvd(info, mSdkLog); 1160 } catch (AndroidLocationException e) { 1161 errorAndExit(e.getMessage()); 1162 } 1163 } 1164 1165 /** 1166 * Moves an AVD. 1167 */ 1168 private void moveAvd() { 1169 try { 1170 String avdName = mSdkCommandLine.getParamName(); 1171 AvdManager avdManager = new AvdManager(mSdkManager, mSdkLog); 1172 AvdInfo info = avdManager.getAvd(avdName, true /*validAvdOnly*/); 1173 1174 if (info == null) { 1175 errorAndExit("There is no valid Android Virtual Device named '%s'.", avdName); 1176 return; 1177 } 1178 1179 // This is a rename if there's a new name for the AVD 1180 String newName = mSdkCommandLine.getParamMoveNewName(); 1181 if (newName != null && newName.equals(info.getName())) { 1182 // same name, not actually a rename operation 1183 newName = null; 1184 } 1185 1186 // This is a move (of the data files) if there's a new location path 1187 String paramFolderPath = mSdkCommandLine.getParamLocationPath(); 1188 if (paramFolderPath != null) { 1189 // check if paths are the same. Use File methods to account for OS idiosyncrasies. 1190 try { 1191 File f1 = new File(paramFolderPath).getCanonicalFile(); 1192 File f2 = new File(info.getDataFolderPath()).getCanonicalFile(); 1193 if (f1.equals(f2)) { 1194 // same canonical path, so not actually a move 1195 paramFolderPath = null; 1196 } 1197 } catch (IOException e) { 1198 // Fail to resolve canonical path. Fail now since a move operation might fail 1199 // later and be harder to recover from. 1200 errorAndExit(e.getMessage()); 1201 return; 1202 } 1203 } 1204 1205 if (newName == null && paramFolderPath == null) { 1206 mSdkLog.warning("Move operation aborted: same AVD name, same canonical data path"); 1207 return; 1208 } 1209 1210 // If a rename was requested and no data move was requested, check if the original 1211 // data path is our default constructed from the AVD name. In this case we still want 1212 // to rename that folder too. 1213 if (newName != null && paramFolderPath == null) { 1214 // Compute the original data path 1215 File originalFolder = new File( 1216 AndroidLocation.getFolder() + AndroidLocation.FOLDER_AVD, 1217 info.getName() + AvdManager.AVD_FOLDER_EXTENSION); 1218 if (info.getDataFolderPath() != null && 1219 originalFolder.equals(new File(info.getDataFolderPath()))) { 1220 try { 1221 // The AVD is using the default data folder path based on the AVD name. 1222 // That folder needs to be adjusted to use the new name. 1223 File f = new File(AndroidLocation.getFolder() + AndroidLocation.FOLDER_AVD, 1224 newName + AvdManager.AVD_FOLDER_EXTENSION); 1225 paramFolderPath = f.getCanonicalPath(); 1226 } catch (IOException e) { 1227 // Fail to resolve canonical path. Fail now rather than later. 1228 errorAndExit(e.getMessage()); 1229 } 1230 } 1231 } 1232 1233 // Check for conflicts 1234 if (newName != null) { 1235 if (avdManager.getAvd(newName, false /*validAvdOnly*/) != null) { 1236 errorAndExit("There is already an AVD named '%s'.", newName); 1237 return; 1238 } 1239 1240 File ini = info.getIniFile(); 1241 if (ini.equals(AvdInfo.getDefaultIniFile(avdManager, newName))) { 1242 errorAndExit("The AVD file '%s' is in the way.", ini.getCanonicalPath()); 1243 return; 1244 } 1245 } 1246 1247 if (paramFolderPath != null && new File(paramFolderPath).exists()) { 1248 errorAndExit( 1249 "There is already a file or directory at '%s'.\nUse --path to specify a different data folder.", 1250 paramFolderPath); 1251 } 1252 1253 avdManager.moveAvd(info, newName, paramFolderPath, mSdkLog); 1254 } catch (AndroidLocationException e) { 1255 errorAndExit(e.getMessage()); 1256 } catch (IOException e) { 1257 errorAndExit(e.getMessage()); 1258 } 1259 } 1260 1261 /** 1262 * Updates a broken AVD. 1263 */ 1264 private void updateAvd() { 1265 try { 1266 String avdName = mSdkCommandLine.getParamName(); 1267 AvdManager avdManager = new AvdManager(mSdkManager, mSdkLog); 1268 avdManager.updateAvd(avdName, mSdkLog); 1269 } catch (AndroidLocationException e) { 1270 errorAndExit(e.getMessage()); 1271 } catch (IOException e) { 1272 errorAndExit(e.getMessage()); 1273 } 1274 } 1275 1276 /** 1277 * Updates adb with the USB devices declared in the SDK add-ons. 1278 */ 1279 private void updateAdb() { 1280 try { 1281 mSdkManager.updateAdb(); 1282 1283 mSdkLog.printf( 1284 "adb has been updated. You must restart adb with the following commands\n" + 1285 "\tadb kill-server\n" + 1286 "\tadb start-server\n"); 1287 } catch (AndroidLocationException e) { 1288 errorAndExit(e.getMessage()); 1289 } catch (IOException e) { 1290 errorAndExit(e.getMessage()); 1291 } 1292 } 1293 1294 1295 private void createIdentity() { 1296 try { 1297 String account = (String) mSdkCommandLine.getValue( 1298 SdkCommandLine.VERB_CREATE, 1299 SdkCommandLine.OBJECT_IDENTITY, 1300 SdkCommandLine.KEY_ACCOUNT); 1301 1302 String keystorePath = (String) mSdkCommandLine.getValue( 1303 SdkCommandLine.VERB_CREATE, 1304 SdkCommandLine.OBJECT_IDENTITY, 1305 SdkCommandLine.KEY_KEYSTORE); 1306 1307 String aliasName = (String) mSdkCommandLine.getValue( 1308 SdkCommandLine.VERB_CREATE, 1309 SdkCommandLine.OBJECT_IDENTITY, 1310 SdkCommandLine.KEY_ALIAS); 1311 1312 String keystorePass = (String) mSdkCommandLine.getValue( 1313 SdkCommandLine.VERB_CREATE, 1314 SdkCommandLine.OBJECT_IDENTITY, 1315 SdkCommandLine.KEY_STOREPASS); 1316 1317 if (keystorePass == null) { 1318 keystorePass = promptPassword("Keystore Password: ").trim(); 1319 } 1320 1321 String aliasPass = (String) mSdkCommandLine.getValue( 1322 SdkCommandLine.VERB_CREATE, 1323 SdkCommandLine.OBJECT_IDENTITY, 1324 SdkCommandLine.KEY_KEYPASS); 1325 1326 if (aliasPass == null) { 1327 aliasPass = promptPassword("Alias Password: ").trim(); 1328 } 1329 1330 MakeIdentity mi = new MakeIdentity(account, keystorePath, keystorePass, 1331 aliasName, aliasPass); 1332 1333 mi.make(System.out, mSdkLog); 1334 } catch (Exception e) { 1335 errorAndExit("Unexpected error: %s", e.getMessage()); 1336 } 1337 } 1338 1339 1340 /** 1341 * Prompts the user to setup a hardware config for a Platform-based AVD. 1342 * @throws IOException 1343 */ 1344 private Map<String, String> promptForHardware(IAndroidTarget createTarget, 1345 Map<String, String> skinHardwareConfig) throws IOException { 1346 byte[] readLineBuffer = new byte[256]; 1347 String result; 1348 String defaultAnswer = "no"; 1349 1350 mSdkLog.printf("%s is a basic Android platform.\n", createTarget.getName()); 1351 mSdkLog.printf("Do you wish to create a custom hardware profile [%s]", 1352 defaultAnswer); 1353 1354 result = readLine(readLineBuffer).trim(); 1355 // handle default: 1356 if (result.length() == 0) { 1357 result = defaultAnswer; 1358 } 1359 1360 if (getBooleanReply(result) == false) { 1361 // no custom config, return the skin hardware config in case there is one. 1362 return skinHardwareConfig; 1363 } 1364 1365 mSdkLog.printf("\n"); // empty line 1366 1367 // get the list of possible hardware properties 1368 File hardwareDefs = new File (mOsSdkFolder + File.separator + 1369 SdkConstants.OS_SDK_TOOLS_LIB_FOLDER, SdkConstants.FN_HARDWARE_INI); 1370 Map<String, HardwareProperty> hwMap = HardwareProperties.parseHardwareDefinitions( 1371 hardwareDefs, null /*sdkLog*/); 1372 1373 HashMap<String, String> map = new HashMap<String, String>(); 1374 1375 // we just want to loop on the HardwareProperties 1376 HardwareProperty[] hwProperties = hwMap.values().toArray( 1377 new HardwareProperty[hwMap.size()]); 1378 for (int i = 0 ; i < hwProperties.length ;) { 1379 HardwareProperty property = hwProperties[i]; 1380 1381 String description = property.getDescription(); 1382 if (description != null) { 1383 mSdkLog.printf("%s: %s\n", property.getAbstract(), description); 1384 } else { 1385 mSdkLog.printf("%s\n", property.getAbstract()); 1386 } 1387 1388 String defaultValue = property.getDefault(); 1389 String defaultFromSkin = skinHardwareConfig != null ? skinHardwareConfig.get( 1390 property.getName()) : null; 1391 1392 if (defaultFromSkin != null) { 1393 mSdkLog.printf("%s [%s (from skin)]:", property.getName(), defaultFromSkin); 1394 } else if (defaultValue != null) { 1395 mSdkLog.printf("%s [%s]:", property.getName(), defaultValue); 1396 } else { 1397 mSdkLog.printf("%s (%s):", property.getName(), property.getType()); 1398 } 1399 1400 result = readLine(readLineBuffer); 1401 if (result.length() == 0) { 1402 if (defaultFromSkin != null || defaultValue != null) { 1403 if (defaultFromSkin != null) { 1404 // we need to write this one in the AVD file 1405 map.put(property.getName(), defaultFromSkin); 1406 } 1407 1408 mSdkLog.printf("\n"); // empty line 1409 i++; // go to the next property if we have a valid default value. 1410 // if there's no default, we'll redo this property 1411 } 1412 continue; 1413 } 1414 1415 switch (property.getType()) { 1416 case BOOLEAN: 1417 try { 1418 if (getBooleanReply(result)) { 1419 map.put(property.getName(), "yes"); 1420 i++; // valid reply, move to next property 1421 } else { 1422 map.put(property.getName(), "no"); 1423 i++; // valid reply, move to next property 1424 } 1425 } catch (IOException e) { 1426 // display error, and do not increment i to redo this property 1427 mSdkLog.printf("\n%s\n", e.getMessage()); 1428 } 1429 break; 1430 case INTEGER: 1431 try { 1432 Integer.parseInt(result); 1433 map.put(property.getName(), result); 1434 i++; // valid reply, move to next property 1435 } catch (NumberFormatException e) { 1436 // display error, and do not increment i to redo this property 1437 mSdkLog.printf("\n%s\n", e.getMessage()); 1438 } 1439 break; 1440 case DISKSIZE: 1441 // TODO check validity 1442 map.put(property.getName(), result); 1443 i++; // valid reply, move to next property 1444 break; 1445 } 1446 1447 mSdkLog.printf("\n"); // empty line 1448 } 1449 1450 return map; 1451 } 1452 1453 /** 1454 * Reads a line from the input stream. 1455 * @param buffer 1456 * @throws IOException 1457 */ 1458 private String readLine(byte[] buffer) throws IOException { 1459 int count = System.in.read(buffer); 1460 1461 // is the input longer than the buffer? 1462 if (count == buffer.length && buffer[count-1] != 10) { 1463 // create a new temp buffer 1464 byte[] tempBuffer = new byte[256]; 1465 1466 // and read the rest 1467 String secondHalf = readLine(tempBuffer); 1468 1469 // return a concat of both 1470 return new String(buffer, 0, count) + secondHalf; 1471 } 1472 1473 // ignore end whitespace 1474 while (count > 0 && (buffer[count-1] == '\r' || buffer[count-1] == '\n')) { 1475 count--; 1476 } 1477 1478 return new String(buffer, 0, count); 1479 } 1480 1481 /** 1482 * Reads a line from the input stream, masking it as much as possible. 1483 */ 1484 private String promptPassword(String prompt) throws IOException { 1485 1486 // Setup a thread that tries to overwrite any input by 1487 // masking the last character with a space. This is quite 1488 // crude but is a documented workaround to the lack of a 1489 // proper password getter. 1490 final AtomicBoolean keepErasing = new AtomicBoolean(true); 1491 1492 Thread eraser = new Thread(new Runnable() { 1493 @Override 1494 public void run() { 1495 while (keepErasing.get()) { 1496 System.err.print("\b "); //$NON-NLS-1$. \b=Backspace 1497 try { 1498 Thread.sleep(10 /*millis*/); 1499 } catch (InterruptedException e) { 1500 // Ignore 1501 } 1502 } 1503 } 1504 }, "eraser"); //$NON-NLS-1$ 1505 1506 try { 1507 System.err.print(prompt); 1508 eraser.start(); 1509 byte[] buffer = new byte[256]; 1510 return readLine(buffer); 1511 } finally { 1512 keepErasing.set(false); 1513 try { 1514 eraser.join(); 1515 } catch (InterruptedException e) { 1516 // Ignore 1517 } 1518 } 1519 } 1520 1521 /** 1522 * Returns the boolean value represented by the string. 1523 * @throws IOException If the value is not a boolean string. 1524 */ 1525 private boolean getBooleanReply(String reply) throws IOException { 1526 1527 for (String valid : BOOLEAN_YES_REPLIES) { 1528 if (valid.equalsIgnoreCase(reply)) { 1529 return true; 1530 } 1531 } 1532 1533 for (String valid : BOOLEAN_NO_REPLIES) { 1534 if (valid.equalsIgnoreCase(reply)) { 1535 return false; 1536 } 1537 } 1538 1539 throw new IOException(String.format("%s is not a valid reply", reply)); 1540 } 1541 1542 private void errorAndExit(String format, Object...args) { 1543 mSdkLog.error(null, format, args); 1544 System.exit(1); 1545 } 1546 1547 /** 1548 * Converts a symbolic target name (such as those accepted by --target on the command-line) 1549 * to an internal target index id. A valid target name is either a numeric target id (> 0) 1550 * or a target hash string. 1551 * <p/> 1552 * If the given target can't be mapped, {@link #INVALID_TARGET_ID} (0) is returned. 1553 * It's up to the caller to output an error. 1554 * <p/> 1555 * On success, returns a value > 0. 1556 */ 1557 private int resolveTargetName(String targetName) { 1558 1559 if (targetName == null) { 1560 return INVALID_TARGET_ID; 1561 } 1562 1563 targetName = targetName.trim(); 1564 1565 // Case of an integer number 1566 if (targetName.matches("[0-9]*")) { 1567 try { 1568 int n = Integer.parseInt(targetName); 1569 return n < 1 ? INVALID_TARGET_ID : n; 1570 } catch (NumberFormatException e) { 1571 // Ignore. Should not happen. 1572 } 1573 } 1574 1575 // Let's try to find a platform or addon name. 1576 IAndroidTarget[] targets = mSdkManager.getTargets(); 1577 for (int i = 0; i < targets.length; i++) { 1578 if (targetName.equals(targets[i].hashString())) { 1579 return i + 1; 1580 } 1581 } 1582 1583 return INVALID_TARGET_ID; 1584 } 1585 } 1586