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