Home | History | Annotate | Download | only in sdkmanager
      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.prefs.AndroidLocation;
     20 import com.android.prefs.AndroidLocation.AndroidLocationException;
     21 import com.android.sdklib.IAndroidTarget;
     22 import com.android.sdklib.ISdkLog;
     23 import com.android.sdklib.SdkConstants;
     24 import com.android.sdklib.SdkManager;
     25 import com.android.sdklib.IAndroidTarget.IOptionalLibrary;
     26 import com.android.sdklib.internal.avd.AvdManager;
     27 import com.android.sdklib.internal.avd.HardwareProperties;
     28 import com.android.sdklib.internal.avd.AvdManager.AvdInfo;
     29 import com.android.sdklib.internal.avd.HardwareProperties.HardwareProperty;
     30 import com.android.sdklib.internal.project.ProjectCreator;
     31 import com.android.sdklib.internal.project.ProjectProperties;
     32 import com.android.sdklib.internal.project.ProjectCreator.OutputLevel;
     33 import com.android.sdklib.internal.project.ProjectProperties.PropertyType;
     34 import com.android.sdklib.xml.AndroidXPathFactory;
     35 import com.android.sdkmanager.internal.repository.AboutPage;
     36 import com.android.sdkmanager.internal.repository.SettingsPage;
     37 import com.android.sdkuilib.internal.repository.LocalPackagesPage;
     38 import com.android.sdkuilib.internal.widgets.MessageBoxLog;
     39 import com.android.sdkuilib.repository.UpdaterWindow;
     40 
     41 import org.eclipse.swt.widgets.Display;
     42 import org.xml.sax.InputSource;
     43 
     44 import java.io.File;
     45 import java.io.FileInputStream;
     46 import java.io.FileNotFoundException;
     47 import java.io.IOException;
     48 import java.util.HashMap;
     49 import java.util.Map;
     50 
     51 import javax.xml.xpath.XPath;
     52 import javax.xml.xpath.XPathExpressionException;
     53 
     54 /**
     55  * Main class for the 'android' application.
     56  */
     57 public class Main {
     58 
     59     /** Java property that defines the location of the sdk/tools directory. */
     60     public final static String TOOLSDIR = "com.android.sdkmanager.toolsdir";
     61     /** Java property that defines the working directory. On Windows the current working directory
     62      *  is actually the tools dir, in which case this is used to get the original CWD. */
     63     private final static String WORKDIR = "com.android.sdkmanager.workdir";
     64 
     65     /** Value returned by {@link #resolveTargetName(String)} when the target id does not match. */
     66     private final static int INVALID_TARGET_ID = 0;
     67 
     68     private final static String[] BOOLEAN_YES_REPLIES = new String[] { "yes", "y" };
     69     private final static String[] BOOLEAN_NO_REPLIES = new String[] { "no", "n" };
     70 
     71     /** Path to the SDK folder. This is the parent of {@link #TOOLSDIR}. */
     72     private String mOsSdkFolder;
     73     /** Logger object. Use this to print normal output, warnings or errors. */
     74     private ISdkLog mSdkLog;
     75     /** The SDK manager parses the SDK folder and gives access to the content. */
     76     private SdkManager mSdkManager;
     77     /** Command-line processor with options specific to SdkManager. */
     78     private SdkCommandLine mSdkCommandLine;
     79     /** The working directory, either null or set to an existing absolute canonical directory. */
     80     private File mWorkDir;
     81 
     82     public static void main(String[] args) {
     83         new Main().run(args);
     84     }
     85 
     86     /**
     87      * Runs the sdk manager app
     88      */
     89     private void run(String[] args) {
     90         createLogger();
     91         init();
     92         mSdkCommandLine.parseArgs(args);
     93         parseSdk();
     94         doAction();
     95     }
     96 
     97     /**
     98      * Creates the {@link #mSdkLog} object.
     99      * This must be done before {@link #init()} as it will be used to report errors.
    100      * This logger prints to the attached console.
    101      */
    102     private void createLogger() {
    103         mSdkLog = new ISdkLog() {
    104             public void error(Throwable t, String errorFormat, Object... args) {
    105                 if (errorFormat != null) {
    106                     System.err.printf("Error: " + errorFormat, args);
    107                     if (!errorFormat.endsWith("\n")) {
    108                         System.err.printf("\n");
    109                     }
    110                 }
    111                 if (t != null) {
    112                     System.err.printf("Error: %s\n", t.getMessage());
    113                 }
    114             }
    115 
    116             public void warning(String warningFormat, Object... args) {
    117                 if (mSdkCommandLine.isVerbose()) {
    118                     System.out.printf("Warning: " + warningFormat, args);
    119                     if (!warningFormat.endsWith("\n")) {
    120                         System.out.printf("\n");
    121                     }
    122                 }
    123             }
    124 
    125             public void printf(String msgFormat, Object... args) {
    126                 System.out.printf(msgFormat, args);
    127             }
    128         };
    129     }
    130 
    131     /**
    132      * Init the application by making sure the SDK path is available and
    133      * doing basic parsing of the SDK.
    134      */
    135     private void init() {
    136         mSdkCommandLine = new SdkCommandLine(mSdkLog);
    137 
    138         // We get passed a property for the tools dir
    139         String toolsDirProp = System.getProperty(TOOLSDIR);
    140         if (toolsDirProp == null) {
    141             // for debugging, it's easier to override using the process environment
    142             toolsDirProp = System.getenv(TOOLSDIR);
    143         }
    144 
    145         if (toolsDirProp != null) {
    146             // got back a level for the SDK folder
    147             File tools;
    148             if (toolsDirProp.length() > 0) {
    149                 tools = new File(toolsDirProp);
    150                 mOsSdkFolder = tools.getParent();
    151             } else {
    152                 try {
    153                     tools = new File(".").getCanonicalFile();
    154                     mOsSdkFolder = tools.getParent();
    155                 } catch (IOException e) {
    156                     // Will print an error below since mSdkFolder is not defined
    157                 }
    158             }
    159         }
    160 
    161         if (mOsSdkFolder == null) {
    162             errorAndExit("The tools directory property is not set, please make sure you are executing %1$s",
    163                 SdkConstants.androidCmdName());
    164         }
    165 
    166         // We might get passed a property for the working directory
    167         // Either it is a valid directory and mWorkDir is set to it's absolute canonical value
    168         // or mWorkDir remains null.
    169         String workDirProp = System.getProperty(WORKDIR);
    170         if (workDirProp == null) {
    171             workDirProp = System.getenv(WORKDIR);
    172         }
    173         if (workDirProp != null) {
    174             // This should be a valid directory
    175             mWorkDir = new File(workDirProp);
    176             try {
    177                 mWorkDir = mWorkDir.getCanonicalFile().getAbsoluteFile();
    178             } catch (IOException e) {
    179                 mWorkDir = null;
    180             }
    181             if (mWorkDir == null || !mWorkDir.isDirectory()) {
    182                 errorAndExit("The working directory does not seem to be valid: '%1$s", workDirProp);
    183             }
    184         }
    185     }
    186 
    187     /**
    188      * Does the basic SDK parsing required for all actions
    189      */
    190     private void parseSdk() {
    191         mSdkManager = SdkManager.createManager(mOsSdkFolder, mSdkLog);
    192 
    193         if (mSdkManager == null) {
    194             errorAndExit("Unable to parse SDK content.");
    195         }
    196     }
    197 
    198     /**
    199      * Actually do an action...
    200      */
    201     private void doAction() {
    202         String verb = mSdkCommandLine.getVerb();
    203         String directObject = mSdkCommandLine.getDirectObject();
    204 
    205         if (SdkCommandLine.VERB_LIST.equals(verb)) {
    206             // list action.
    207             if (SdkCommandLine.OBJECT_TARGET.equals(directObject)) {
    208                 displayTargetList();
    209             } else if (SdkCommandLine.OBJECT_AVD.equals(directObject)) {
    210                 displayAvdList();
    211             } else {
    212                 displayTargetList();
    213                 displayAvdList();
    214             }
    215 
    216         } else if (SdkCommandLine.VERB_CREATE.equals(verb)) {
    217             if (SdkCommandLine.OBJECT_AVD.equals(directObject)) {
    218                 createAvd();
    219             } else if (SdkCommandLine.OBJECT_PROJECT.equals(directObject)) {
    220                 createProject(false /*library*/);
    221             } else if (SdkCommandLine.OBJECT_TEST_PROJECT.equals(directObject)) {
    222                 createTestProject();
    223             } else if (SdkCommandLine.OBJECT_LIB_PROJECT.equals(directObject)) {
    224                 createProject(true /*library*/);
    225             }
    226         } else if (SdkCommandLine.VERB_UPDATE.equals(verb)) {
    227             if (SdkCommandLine.OBJECT_AVD.equals(directObject)) {
    228                 updateAvd();
    229             } else if (SdkCommandLine.OBJECT_PROJECT.equals(directObject)) {
    230                 updateProject(false /*library*/);
    231             } else if (SdkCommandLine.OBJECT_TEST_PROJECT.equals(directObject)) {
    232                 updateTestProject();
    233             } else if (SdkCommandLine.OBJECT_LIB_PROJECT.equals(directObject)) {
    234                 updateProject(true /*library*/);
    235             } else if (SdkCommandLine.OBJECT_SDK.equals(directObject)) {
    236                 showMainWindow(true /*autoUpdate*/);
    237             } else if (SdkCommandLine.OBJECT_ADB.equals(directObject)) {
    238                 updateAdb();
    239             }
    240         } else if (SdkCommandLine.VERB_DELETE.equals(verb) &&
    241                 SdkCommandLine.OBJECT_AVD.equals(directObject)) {
    242             deleteAvd();
    243 
    244         } else if (SdkCommandLine.VERB_MOVE.equals(verb) &&
    245                 SdkCommandLine.OBJECT_AVD.equals(directObject)) {
    246             moveAvd();
    247 
    248         } else if (verb == null && directObject == null) {
    249             showMainWindow(false /*autoUpdate*/);
    250 
    251         } else {
    252             mSdkCommandLine.printHelpAndExit(null);
    253         }
    254     }
    255 
    256     /**
    257      * Display the main SdkManager app window
    258      */
    259     private void showMainWindow(boolean autoUpdate) {
    260         try {
    261             // display a message talking about the command line version
    262             System.out.printf("No command line parameters provided, launching UI.\n" +
    263                     "See 'android --help' for operations from the command line.\n");
    264 
    265             MessageBoxLog errorLogger = new MessageBoxLog(
    266                     "SDK Manager",
    267                     Display.getCurrent(),
    268                     true /*logErrorsOnly*/);
    269 
    270             UpdaterWindow window = new UpdaterWindow(
    271                     null /* parentShell */,
    272                     errorLogger,
    273                     mOsSdkFolder,
    274                     false /*userCanChangeSdkRoot*/);
    275             window.registerPage("Settings", SettingsPage.class);
    276             window.registerPage("About", AboutPage.class);
    277             if (autoUpdate) {
    278                 window.setInitialPage(LocalPackagesPage.class);
    279                 window.setRequestAutoUpdate(true);
    280             }
    281             window.open();
    282 
    283             errorLogger.displayResult(true);
    284 
    285         } catch (Exception e) {
    286             e.printStackTrace();
    287         }
    288     }
    289 
    290     /**
    291      * Creates a new Android project based on command-line parameters
    292      */
    293     private void createProject(boolean library) {
    294         String directObject = library? SdkCommandLine.OBJECT_LIB_PROJECT :
    295                 SdkCommandLine.OBJECT_PROJECT;
    296 
    297         // get the target and try to resolve it.
    298         int targetId = resolveTargetName(mSdkCommandLine.getParamTargetId());
    299         IAndroidTarget[] targets = mSdkManager.getTargets();
    300         if (targetId == INVALID_TARGET_ID || targetId > targets.length) {
    301             errorAndExit("Target id is not valid. Use '%s list targets' to get the target ids.",
    302                     SdkConstants.androidCmdName());
    303         }
    304         IAndroidTarget target = targets[targetId - 1];  // target id is 1-based
    305 
    306         ProjectCreator creator = new ProjectCreator(mSdkManager, mOsSdkFolder,
    307                 mSdkCommandLine.isVerbose() ? OutputLevel.VERBOSE :
    308                     mSdkCommandLine.isSilent() ? OutputLevel.SILENT :
    309                         OutputLevel.NORMAL,
    310                 mSdkLog);
    311 
    312         String projectDir = getProjectLocation(mSdkCommandLine.getParamLocationPath());
    313 
    314         String projectName = mSdkCommandLine.getParamName();
    315         String packageName = mSdkCommandLine.getParamProjectPackage(directObject);
    316         String activityName = null;
    317         if (library == false) {
    318             activityName = mSdkCommandLine.getParamProjectActivity();
    319         }
    320 
    321         if (projectName != null &&
    322                 !ProjectCreator.RE_PROJECT_NAME.matcher(projectName).matches()) {
    323             errorAndExit(
    324                 "Project name '%1$s' contains invalid characters.\nAllowed characters are: %2$s",
    325                 projectName, ProjectCreator.CHARS_PROJECT_NAME);
    326             return;
    327         }
    328 
    329 
    330         if (activityName != null &&
    331                 !ProjectCreator.RE_ACTIVITY_NAME.matcher(activityName).matches()) {
    332             errorAndExit(
    333                 "Activity name '%1$s' contains invalid characters.\nAllowed characters are: %2$s",
    334                 activityName, ProjectCreator.CHARS_ACTIVITY_NAME);
    335             return;
    336         }
    337 
    338         if (packageName != null &&
    339                 !ProjectCreator.RE_PACKAGE_NAME.matcher(packageName).matches()) {
    340             errorAndExit(
    341                 "Package name '%1$s' contains invalid characters.\n" +
    342                 "A package name must be constitued of two Java identifiers.\n" +
    343                 "Each identifier allowed characters are: %2$s",
    344                 packageName, ProjectCreator.CHARS_PACKAGE_NAME);
    345             return;
    346         }
    347 
    348         creator.createProject(projectDir,
    349                 projectName,
    350                 packageName,
    351                 activityName,
    352                 target,
    353                 library,
    354                 null /*pathToMain*/);
    355     }
    356 
    357     /**
    358      * Creates a new Android test project based on command-line parameters
    359      */
    360     private void createTestProject() {
    361 
    362         String projectDir = getProjectLocation(mSdkCommandLine.getParamLocationPath());
    363 
    364         // first check the path of the parent project, and make sure it's valid.
    365         String pathToMainProject = mSdkCommandLine.getParamTestProjectMain();
    366 
    367         File parentProject = new File(pathToMainProject);
    368         if (parentProject.isAbsolute() == false) {
    369             // if the path is not absolute, we need to resolve it based on the
    370             // destination path of the project
    371             try {
    372                 parentProject = new File(projectDir, pathToMainProject).getCanonicalFile();
    373             } catch (IOException e) {
    374                 errorAndExit("Unable to resolve Main project's directory: %1$s",
    375                         pathToMainProject);
    376                 return; // help Eclipse static analyzer understand we'll never execute the rest.
    377             }
    378         }
    379 
    380         if (parentProject.isDirectory() == false) {
    381             errorAndExit("Main project's directory does not exist: %1$s",
    382                     pathToMainProject);
    383             return;
    384         }
    385 
    386         // now look for a manifest in there
    387         File manifest = new File(parentProject, SdkConstants.FN_ANDROID_MANIFEST_XML);
    388         if (manifest.isFile() == false) {
    389             errorAndExit("No AndroidManifest.xml file found in the main project directory: %1$s",
    390                     parentProject.getAbsolutePath());
    391             return;
    392         }
    393 
    394         // now query the manifest for the package file.
    395         XPath xpath = AndroidXPathFactory.newXPath();
    396         String packageName, activityName;
    397 
    398         try {
    399             packageName = xpath.evaluate("/manifest/@package",
    400                     new InputSource(new FileInputStream(manifest)));
    401 
    402             mSdkLog.printf("Found main project package: %1$s\n", packageName);
    403 
    404             // now get the name of the first activity we find
    405             activityName = xpath.evaluate("/manifest/application/activity[1]/@android:name",
    406                     new InputSource(new FileInputStream(manifest)));
    407             // xpath will return empty string when there's no match
    408             if (activityName == null || activityName.length() == 0) {
    409                 activityName = null;
    410             } else {
    411                 mSdkLog.printf("Found main project activity: %1$s\n", activityName);
    412             }
    413         } catch (FileNotFoundException e) {
    414             // this shouldn't happen as we test it above.
    415             errorAndExit("No AndroidManifest.xml file found in main project.");
    416             return; // this is not strictly needed because errorAndExit will stop the execution,
    417             // but this makes the java compiler happy, wrt to uninitialized variables.
    418         } catch (XPathExpressionException e) {
    419             // looks like the main manifest is not valid.
    420             errorAndExit("Unable to parse main project manifest to get information.");
    421             return; // this is not strictly needed because errorAndExit will stop the execution,
    422                     // but this makes the java compiler happy, wrt to uninitialized variables.
    423         }
    424 
    425         // now get the target hash
    426         ProjectProperties p = ProjectProperties.load(parentProject.getAbsolutePath(),
    427                 PropertyType.DEFAULT);
    428         if (p == null) {
    429             errorAndExit("Unable to load the main project's %1$s",
    430                     PropertyType.DEFAULT.getFilename());
    431             return;
    432         }
    433 
    434         String targetHash = p.getProperty(ProjectProperties.PROPERTY_TARGET);
    435         if (targetHash == null) {
    436             errorAndExit("Couldn't find the main project target");
    437             return;
    438         }
    439 
    440         // and resolve it.
    441         IAndroidTarget target = mSdkManager.getTargetFromHashString(targetHash);
    442         if (target == null) {
    443             errorAndExit(
    444                     "Unable to resolve main project target '%1$s'. You may want to install the platform in your SDK.",
    445                     targetHash);
    446             return;
    447         }
    448 
    449         mSdkLog.printf("Found main project target: %1$s\n", target.getFullName());
    450 
    451         ProjectCreator creator = new ProjectCreator(mSdkManager, mOsSdkFolder,
    452                 mSdkCommandLine.isVerbose() ? OutputLevel.VERBOSE :
    453                     mSdkCommandLine.isSilent() ? OutputLevel.SILENT :
    454                         OutputLevel.NORMAL,
    455                 mSdkLog);
    456 
    457         String projectName = mSdkCommandLine.getParamName();
    458 
    459         if (projectName != null &&
    460                 !ProjectCreator.RE_PROJECT_NAME.matcher(projectName).matches()) {
    461             errorAndExit(
    462                 "Project name '%1$s' contains invalid characters.\nAllowed characters are: %2$s",
    463                 projectName, ProjectCreator.CHARS_PROJECT_NAME);
    464             return;
    465         }
    466 
    467         creator.createProject(projectDir,
    468                 projectName,
    469                 packageName,
    470                 activityName,
    471                 target,
    472                 false /* library*/,
    473                 pathToMainProject);
    474     }
    475 
    476 
    477     /**
    478      * Updates an existing Android project based on command-line parameters
    479      * @param library whether the project is a library project.
    480      */
    481     private void updateProject(boolean library) {
    482         // get the target and try to resolve it.
    483         IAndroidTarget target = null;
    484         String targetStr = mSdkCommandLine.getParamTargetId();
    485         // For "update project" the target parameter is optional so having null is acceptable.
    486         // However if there's a value, it must be valid.
    487         if (targetStr != null) {
    488             IAndroidTarget[] targets = mSdkManager.getTargets();
    489             int targetId = resolveTargetName(targetStr);
    490             if (targetId == INVALID_TARGET_ID || targetId > targets.length) {
    491                 errorAndExit("Target id '%1$s' is not valid. Use '%2$s list targets' to get the target ids.",
    492                         targetStr,
    493                         SdkConstants.androidCmdName());
    494             }
    495             target = targets[targetId - 1];  // target id is 1-based
    496         }
    497 
    498         ProjectCreator creator = new ProjectCreator(mSdkManager, mOsSdkFolder,
    499                 mSdkCommandLine.isVerbose() ? OutputLevel.VERBOSE :
    500                     mSdkCommandLine.isSilent() ? OutputLevel.SILENT :
    501                         OutputLevel.NORMAL,
    502                 mSdkLog);
    503 
    504         String projectDir = getProjectLocation(mSdkCommandLine.getParamLocationPath());
    505 
    506         String libraryPath = mSdkCommandLine.getParamProjectLibrary(
    507                 library ? SdkCommandLine.OBJECT_LIB_PROJECT : SdkCommandLine.OBJECT_PROJECT);
    508 
    509         creator.updateProject(projectDir,
    510                 target,
    511                 mSdkCommandLine.getParamName(),
    512                 libraryPath);
    513 
    514         if (library == false) {
    515             boolean doSubProjects = mSdkCommandLine.getParamSubProject();
    516             boolean couldHaveDone = false;
    517 
    518             // If there are any sub-folders with a manifest, try to update them as projects
    519             // too. This will take care of updating any underlying test project even if the
    520             // user changed the folder name.
    521             File[] files = new File(projectDir).listFiles();
    522             if (files != null) {
    523                 for (File dir : files) {
    524                     if (dir.isDirectory() &&
    525                             new File(dir, SdkConstants.FN_ANDROID_MANIFEST_XML).isFile()) {
    526                         if (doSubProjects) {
    527                             creator.updateProject(dir.getPath(),
    528                                     target,
    529                                     mSdkCommandLine.getParamName(),
    530                                     null /*libraryPath*/);
    531                         } else {
    532                             couldHaveDone = true;
    533                         }
    534                     }
    535                 }
    536             }
    537 
    538             if (couldHaveDone) {
    539                 mSdkLog.printf(
    540                         "It seems that there are sub-projects. If you want to update them\nplease use the --%1$s parameter.\n",
    541                         SdkCommandLine.KEY_SUBPROJECTS);
    542             }
    543         }
    544     }
    545 
    546     /**
    547      * Updates an existing test project with a new path to the main project.
    548      */
    549     private void updateTestProject() {
    550         ProjectCreator creator = new ProjectCreator(mSdkManager, mOsSdkFolder,
    551                 mSdkCommandLine.isVerbose() ? OutputLevel.VERBOSE :
    552                     mSdkCommandLine.isSilent() ? OutputLevel.SILENT :
    553                         OutputLevel.NORMAL,
    554                 mSdkLog);
    555 
    556         String projectDir = getProjectLocation(mSdkCommandLine.getParamLocationPath());
    557 
    558         creator.updateTestProject(projectDir, mSdkCommandLine.getParamTestProjectMain(),
    559                 mSdkManager);
    560     }
    561 
    562     /**
    563      * Adjusts the project location to make it absolute & canonical relative to the
    564      * working directory, if any.
    565      *
    566      * @return The project absolute path relative to {@link #mWorkDir} or the original
    567      *         newProjectLocation otherwise.
    568      */
    569     private String getProjectLocation(String newProjectLocation) {
    570 
    571         // If the new project location is absolute, use it as-is
    572         File projectDir = new File(newProjectLocation);
    573         if (projectDir.isAbsolute()) {
    574             return newProjectLocation;
    575         }
    576 
    577         // if there's no working directory, just use the project location as-is.
    578         if (mWorkDir == null) {
    579             return newProjectLocation;
    580         }
    581 
    582         // Combine then and get an absolute canonical directory
    583         try {
    584             projectDir = new File(mWorkDir, newProjectLocation).getCanonicalFile();
    585 
    586             return projectDir.getPath();
    587         } catch (IOException e) {
    588             errorAndExit("Failed to combine working directory '%1$s' with project location '%2$s': %3$s",
    589                     mWorkDir.getPath(),
    590                     newProjectLocation,
    591                     e.getMessage());
    592             return null;
    593         }
    594     }
    595 
    596     /**
    597      * Displays the list of available Targets (Platforms and Add-ons)
    598      */
    599     private void displayTargetList() {
    600         mSdkLog.printf("Available Android targets:\n");
    601 
    602         int index = 1;
    603         for (IAndroidTarget target : mSdkManager.getTargets()) {
    604             mSdkLog.printf("id: %1$d or \"%2$s\"\n", index, target.hashString());
    605             mSdkLog.printf("     Name: %s\n", target.getName());
    606             if (target.isPlatform()) {
    607                 mSdkLog.printf("     Type: Platform\n");
    608                 mSdkLog.printf("     API level: %s\n", target.getVersion().getApiString());
    609                 mSdkLog.printf("     Revision: %d\n", target.getRevision());
    610             } else {
    611                 mSdkLog.printf("     Type: Add-On\n");
    612                 mSdkLog.printf("     Vendor: %s\n", target.getVendor());
    613                 mSdkLog.printf("     Revision: %d\n", target.getRevision());
    614                 if (target.getDescription() != null) {
    615                     mSdkLog.printf("     Description: %s\n", target.getDescription());
    616                 }
    617                 mSdkLog.printf("     Based on Android %s (API level %s)\n",
    618                         target.getVersionName(), target.getVersion().getApiString());
    619 
    620                 // display the optional libraries.
    621                 IOptionalLibrary[] libraries = target.getOptionalLibraries();
    622                 if (libraries != null) {
    623                     mSdkLog.printf("     Libraries:\n");
    624                     for (IOptionalLibrary library : libraries) {
    625                         mSdkLog.printf("      * %1$s (%2$s)\n",
    626                                 library.getName(), library.getJarName());
    627                         mSdkLog.printf(String.format(
    628                                 "          %1$s\n", library.getDescription()));
    629                     }
    630                 }
    631             }
    632 
    633             // get the target skins
    634             displaySkinList(target, "     Skins: ");
    635 
    636             if (target.getUsbVendorId() != IAndroidTarget.NO_USB_ID) {
    637                 mSdkLog.printf("     Adds USB support for devices (Vendor: 0x%04X)\n",
    638                         target.getUsbVendorId());
    639             }
    640 
    641             index++;
    642         }
    643     }
    644 
    645     /**
    646      * Displays the skins valid for the given target.
    647      */
    648     private void displaySkinList(IAndroidTarget target, String message) {
    649         String[] skins = target.getSkins();
    650         String defaultSkin = target.getDefaultSkin();
    651         mSdkLog.printf(message);
    652         if (skins != null) {
    653             boolean first = true;
    654             for (String skin : skins) {
    655                 if (first == false) {
    656                     mSdkLog.printf(", ");
    657                 } else {
    658                     first = false;
    659                 }
    660                 mSdkLog.printf(skin);
    661 
    662                 if (skin.equals(defaultSkin)) {
    663                     mSdkLog.printf(" (default)");
    664                 }
    665             }
    666             mSdkLog.printf("\n");
    667         } else {
    668             mSdkLog.printf("no skins.\n");
    669         }
    670     }
    671 
    672     /**
    673      * Displays the list of available AVDs.
    674      */
    675     private void displayAvdList() {
    676         try {
    677             AvdManager avdManager = new AvdManager(mSdkManager, mSdkLog);
    678 
    679             mSdkLog.printf("Available Android Virtual Devices:\n");
    680 
    681             AvdInfo[] avds = avdManager.getValidAvds();
    682             for (int index = 0 ; index < avds.length ; index++) {
    683                 AvdInfo info = avds[index];
    684                 if (index > 0) {
    685                     mSdkLog.printf("---------\n");
    686                 }
    687                 mSdkLog.printf("    Name: %s\n", info.getName());
    688                 mSdkLog.printf("    Path: %s\n", info.getPath());
    689 
    690                 // get the target of the AVD
    691                 IAndroidTarget target = info.getTarget();
    692                 if (target.isPlatform()) {
    693                     mSdkLog.printf("  Target: %s (API level %s)\n", target.getName(),
    694                             target.getVersion().getApiString());
    695                 } else {
    696                     mSdkLog.printf("  Target: %s (%s)\n", target.getName(), target
    697                             .getVendor());
    698                     mSdkLog.printf("          Based on Android %s (API level %s)\n",
    699                             target.getVersionName(), target.getVersion().getApiString());
    700                 }
    701 
    702                 // display some extra values.
    703                 Map<String, String> properties = info.getProperties();
    704                 if (properties != null) {
    705                     String skin = properties.get(AvdManager.AVD_INI_SKIN_NAME);
    706                     if (skin != null) {
    707                         mSdkLog.printf("    Skin: %s\n", skin);
    708                     }
    709                     String sdcard = properties.get(AvdManager.AVD_INI_SDCARD_SIZE);
    710                     if (sdcard == null) {
    711                         sdcard = properties.get(AvdManager.AVD_INI_SDCARD_PATH);
    712                     }
    713                     if (sdcard != null) {
    714                         mSdkLog.printf("  Sdcard: %s\n", sdcard);
    715                     }
    716                 }
    717             }
    718 
    719             // Are there some unused AVDs?
    720             AvdInfo[] badAvds = avdManager.getBrokenAvds();
    721 
    722             if (badAvds.length == 0) {
    723                 return;
    724             }
    725 
    726             mSdkLog.printf("\nThe following Android Virtual Devices could not be loaded:\n");
    727             boolean needSeparator = false;
    728             for (AvdInfo info : badAvds) {
    729                 if (needSeparator) {
    730                     mSdkLog.printf("---------\n");
    731                 }
    732                 mSdkLog.printf("    Name: %s\n", info.getName() == null ? "--" : info.getName());
    733                 mSdkLog.printf("    Path: %s\n", info.getPath() == null ? "--" : info.getPath());
    734 
    735                 String error = info.getErrorMessage();
    736                 mSdkLog.printf("   Error: %s\n", error == null ? "Uknown error" : error);
    737                 needSeparator = true;
    738             }
    739         } catch (AndroidLocationException e) {
    740             errorAndExit(e.getMessage());
    741         }
    742     }
    743 
    744     /**
    745      * Creates a new AVD. This is a text based creation with command line prompt.
    746      */
    747     private void createAvd() {
    748         // find a matching target
    749         int targetId = resolveTargetName(mSdkCommandLine.getParamTargetId());
    750         IAndroidTarget[] targets = mSdkManager.getTargets();
    751 
    752         if (targetId == INVALID_TARGET_ID || targetId > targets.length) {
    753             errorAndExit("Target id is not valid. Use '%s list targets' to get the target ids.",
    754                     SdkConstants.androidCmdName());
    755         }
    756 
    757         IAndroidTarget target = targets[targetId-1]; // target id is 1-based
    758 
    759         try {
    760             boolean removePrevious = mSdkCommandLine.getFlagForce();
    761             AvdManager avdManager = new AvdManager(mSdkManager, mSdkLog);
    762 
    763             String avdName = mSdkCommandLine.getParamName();
    764 
    765             if (!AvdManager.RE_AVD_NAME.matcher(avdName).matches()) {
    766                 errorAndExit(
    767                     "AVD name '%1$s' contains invalid characters.\nAllowed characters are: %2$s",
    768                     avdName, AvdManager.CHARS_AVD_NAME);
    769                 return;
    770             }
    771 
    772             AvdInfo info = avdManager.getAvd(avdName, false /*validAvdOnly*/);
    773             if (info != null) {
    774                 if (removePrevious) {
    775                     mSdkLog.warning(
    776                             "Android Virtual Device '%s' already exists and will be replaced.",
    777                             avdName);
    778                 } else {
    779                     errorAndExit("Android Virtual Device '%s' already exists.\n" +
    780                                  "Use --force if you want to replace it.",
    781                                  avdName);
    782                     return;
    783                 }
    784             }
    785 
    786             String paramFolderPath = mSdkCommandLine.getParamLocationPath();
    787             File avdFolder = null;
    788             if (paramFolderPath != null) {
    789                 avdFolder = new File(paramFolderPath);
    790             } else {
    791                 avdFolder = new File(AndroidLocation.getFolder() + AndroidLocation.FOLDER_AVD,
    792                         avdName + AvdManager.AVD_FOLDER_EXTENSION);
    793             }
    794 
    795             // Validate skin is either default (empty) or NNNxMMM or a valid skin name.
    796             Map<String, String> skinHardwareConfig = null;
    797             String skin = mSdkCommandLine.getParamSkin();
    798             if (skin != null && skin.length() == 0) {
    799                 skin = null;
    800             }
    801 
    802             if (skin != null && target != null) {
    803                 boolean valid = false;
    804                 // Is it a know skin name for this target?
    805                 for (String s : target.getSkins()) {
    806                     if (skin.equalsIgnoreCase(s)) {
    807                         skin = s;  // Make skin names case-insensitive.
    808                         valid = true;
    809 
    810                         // get the hardware properties for this skin
    811                         File skinFolder = avdManager.getSkinPath(skin, target);
    812                         File skinHardwareFile = new File(skinFolder, AvdManager.HARDWARE_INI);
    813                         if (skinHardwareFile.isFile()) {
    814                             skinHardwareConfig = SdkManager.parsePropertyFile(
    815                                     skinHardwareFile, mSdkLog);
    816                         }
    817                         break;
    818                     }
    819                 }
    820 
    821                 // Is it NNNxMMM?
    822                 if (!valid) {
    823                     valid = AvdManager.NUMERIC_SKIN_SIZE.matcher(skin).matches();
    824                 }
    825 
    826                 if (!valid) {
    827                     displaySkinList(target, "Valid skins: ");
    828                     errorAndExit("'%s' is not a valid skin name or size (NNNxMMM)", skin);
    829                     return;
    830                 }
    831             }
    832 
    833             Map<String, String> hardwareConfig = null;
    834             if (target != null && target.isPlatform()) {
    835                 try {
    836                     hardwareConfig = promptForHardware(target, skinHardwareConfig);
    837                 } catch (IOException e) {
    838                     errorAndExit(e.getMessage());
    839                 }
    840             }
    841 
    842             @SuppressWarnings("unused") // oldAvdInfo is never read, yet useful for debugging
    843             AvdInfo oldAvdInfo = null;
    844             if (removePrevious) {
    845                 oldAvdInfo = avdManager.getAvd(avdName, false /*validAvdOnly*/);
    846             }
    847 
    848             @SuppressWarnings("unused") // newAvdInfo is never read, yet useful for debugging
    849             AvdInfo newAvdInfo = avdManager.createAvd(avdFolder,
    850                     avdName,
    851                     target,
    852                     skin,
    853                     mSdkCommandLine.getParamSdCard(),
    854                     hardwareConfig,
    855                     removePrevious,
    856                     mSdkLog);
    857 
    858         } catch (AndroidLocationException e) {
    859             errorAndExit(e.getMessage());
    860         }
    861     }
    862 
    863     /**
    864      * Delete an AVD. If the AVD name is not part of the available ones look for an
    865      * invalid AVD (one not loaded due to some error) to remove it too.
    866      */
    867     private void deleteAvd() {
    868         try {
    869             String avdName = mSdkCommandLine.getParamName();
    870             AvdManager avdManager = new AvdManager(mSdkManager, mSdkLog);
    871             AvdInfo info = avdManager.getAvd(avdName, false /*validAvdOnly*/);
    872 
    873             if (info == null) {
    874                 errorAndExit("There is no Android Virtual Device named '%s'.", avdName);
    875                 return;
    876             }
    877 
    878             avdManager.deleteAvd(info, mSdkLog);
    879         } catch (AndroidLocationException e) {
    880             errorAndExit(e.getMessage());
    881         }
    882     }
    883 
    884     /**
    885      * Moves an AVD.
    886      */
    887     private void moveAvd() {
    888         try {
    889             String avdName = mSdkCommandLine.getParamName();
    890             AvdManager avdManager = new AvdManager(mSdkManager, mSdkLog);
    891             AvdInfo info = avdManager.getAvd(avdName, true /*validAvdOnly*/);
    892 
    893             if (info == null) {
    894                 errorAndExit("There is no valid Android Virtual Device named '%s'.", avdName);
    895                 return;
    896             }
    897 
    898             // This is a rename if there's a new name for the AVD
    899             String newName = mSdkCommandLine.getParamMoveNewName();
    900             if (newName != null && newName.equals(info.getName())) {
    901                 // same name, not actually a rename operation
    902                 newName = null;
    903             }
    904 
    905             // This is a move (of the data files) if there's a new location path
    906             String paramFolderPath = mSdkCommandLine.getParamLocationPath();
    907             if (paramFolderPath != null) {
    908                 // check if paths are the same. Use File methods to account for OS idiosyncrasies.
    909                 try {
    910                     File f1 = new File(paramFolderPath).getCanonicalFile();
    911                     File f2 = new File(info.getPath()).getCanonicalFile();
    912                     if (f1.equals(f2)) {
    913                         // same canonical path, so not actually a move
    914                         paramFolderPath = null;
    915                     }
    916                 } catch (IOException e) {
    917                     // Fail to resolve canonical path. Fail now since a move operation might fail
    918                     // later and be harder to recover from.
    919                     errorAndExit(e.getMessage());
    920                     return;
    921                 }
    922             }
    923 
    924             if (newName == null && paramFolderPath == null) {
    925                 mSdkLog.warning("Move operation aborted: same AVD name, same canonical data path");
    926                 return;
    927             }
    928 
    929             // If a rename was requested and no data move was requested, check if the original
    930             // data path is our default constructed from the AVD name. In this case we still want
    931             // to rename that folder too.
    932             if (newName != null && paramFolderPath == null) {
    933                 // Compute the original data path
    934                 File originalFolder = new File(
    935                         AndroidLocation.getFolder() + AndroidLocation.FOLDER_AVD,
    936                         info.getName() + AvdManager.AVD_FOLDER_EXTENSION);
    937                 if (originalFolder.equals(info.getPath())) {
    938                     try {
    939                         // The AVD is using the default data folder path based on the AVD name.
    940                         // That folder needs to be adjusted to use the new name.
    941                         File f = new File(AndroidLocation.getFolder() + AndroidLocation.FOLDER_AVD,
    942                                      newName + AvdManager.AVD_FOLDER_EXTENSION);
    943                         paramFolderPath = f.getCanonicalPath();
    944                     } catch (IOException e) {
    945                         // Fail to resolve canonical path. Fail now rather than later.
    946                         errorAndExit(e.getMessage());
    947                     }
    948                 }
    949             }
    950 
    951             // Check for conflicts
    952             if (newName != null) {
    953                 if (avdManager.getAvd(newName, false /*validAvdOnly*/) != null) {
    954                     errorAndExit("There is already an AVD named '%s'.", newName);
    955                     return;
    956                 }
    957 
    958                 File ini = info.getIniFile();
    959                 if (ini.equals(AvdInfo.getIniFile(newName))) {
    960                     errorAndExit("The AVD file '%s' is in the way.", ini.getCanonicalPath());
    961                     return;
    962                 }
    963             }
    964 
    965             if (paramFolderPath != null && new File(paramFolderPath).exists()) {
    966                 errorAndExit(
    967                         "There is already a file or directory at '%s'.\nUse --path to specify a different data folder.",
    968                         paramFolderPath);
    969             }
    970 
    971             avdManager.moveAvd(info, newName, paramFolderPath, mSdkLog);
    972         } catch (AndroidLocationException e) {
    973             errorAndExit(e.getMessage());
    974         } catch (IOException e) {
    975             errorAndExit(e.getMessage());
    976         }
    977     }
    978 
    979     /**
    980      * Updates a broken AVD.
    981      */
    982     private void updateAvd() {
    983         try {
    984             String avdName = mSdkCommandLine.getParamName();
    985             AvdManager avdManager = new AvdManager(mSdkManager, mSdkLog);
    986             avdManager.updateAvd(avdName, mSdkLog);
    987         } catch (AndroidLocationException e) {
    988             errorAndExit(e.getMessage());
    989         } catch (IOException e) {
    990             errorAndExit(e.getMessage());
    991         }
    992     }
    993 
    994     /**
    995      * Updates adb with the USB devices declared in the SDK add-ons.
    996      */
    997     private void updateAdb() {
    998         try {
    999             mSdkManager.updateAdb();
   1000 
   1001             mSdkLog.printf(
   1002                     "adb has been updated. You must restart adb with the following commands\n" +
   1003                     "\tadb kill-server\n" +
   1004                     "\tadb start-server\n");
   1005         } catch (AndroidLocationException e) {
   1006             errorAndExit(e.getMessage());
   1007         } catch (IOException e) {
   1008             errorAndExit(e.getMessage());
   1009         }
   1010     }
   1011 
   1012     /**
   1013      * Prompts the user to setup a hardware config for a Platform-based AVD.
   1014      * @throws IOException
   1015      */
   1016     private Map<String, String> promptForHardware(IAndroidTarget createTarget,
   1017             Map<String, String> skinHardwareConfig) throws IOException {
   1018         byte[] readLineBuffer = new byte[256];
   1019         String result;
   1020         String defaultAnswer = "no";
   1021 
   1022         mSdkLog.printf("%s is a basic Android platform.\n", createTarget.getName());
   1023         mSdkLog.printf("Do you wish to create a custom hardware profile [%s]",
   1024                 defaultAnswer);
   1025 
   1026         result = readLine(readLineBuffer).trim();
   1027         // handle default:
   1028         if (result.length() == 0) {
   1029             result = defaultAnswer;
   1030         }
   1031 
   1032         if (getBooleanReply(result) == false) {
   1033             // no custom config, return the skin hardware config in case there is one.
   1034             return skinHardwareConfig;
   1035         }
   1036 
   1037         mSdkLog.printf("\n"); // empty line
   1038 
   1039         // get the list of possible hardware properties
   1040         File hardwareDefs = new File (mOsSdkFolder + File.separator +
   1041                 SdkConstants.OS_SDK_TOOLS_LIB_FOLDER, SdkConstants.FN_HARDWARE_INI);
   1042         Map<String, HardwareProperty> hwMap = HardwareProperties.parseHardwareDefinitions(
   1043                 hardwareDefs, null /*sdkLog*/);
   1044 
   1045         HashMap<String, String> map = new HashMap<String, String>();
   1046 
   1047         // we just want to loop on the HardwareProperties
   1048         HardwareProperty[] hwProperties = hwMap.values().toArray(
   1049                 new HardwareProperty[hwMap.size()]);
   1050         for (int i = 0 ; i < hwProperties.length ;) {
   1051             HardwareProperty property = hwProperties[i];
   1052 
   1053             String description = property.getDescription();
   1054             if (description != null) {
   1055                 mSdkLog.printf("%s: %s\n", property.getAbstract(), description);
   1056             } else {
   1057                 mSdkLog.printf("%s\n", property.getAbstract());
   1058             }
   1059 
   1060             String defaultValue = property.getDefault();
   1061             String defaultFromSkin = skinHardwareConfig != null ? skinHardwareConfig.get(
   1062                     property.getName()) : null;
   1063 
   1064             if (defaultFromSkin != null) {
   1065                 mSdkLog.printf("%s [%s (from skin)]:", property.getName(), defaultFromSkin);
   1066             } else if (defaultValue != null) {
   1067                 mSdkLog.printf("%s [%s]:", property.getName(), defaultValue);
   1068             } else {
   1069                 mSdkLog.printf("%s (%s):", property.getName(), property.getType());
   1070             }
   1071 
   1072             result = readLine(readLineBuffer);
   1073             if (result.length() == 0) {
   1074                 if (defaultFromSkin != null || defaultValue != null) {
   1075                     if (defaultFromSkin != null) {
   1076                         // we need to write this one in the AVD file
   1077                         map.put(property.getName(), defaultFromSkin);
   1078                     }
   1079 
   1080                     mSdkLog.printf("\n"); // empty line
   1081                     i++; // go to the next property if we have a valid default value.
   1082                          // if there's no default, we'll redo this property
   1083                 }
   1084                 continue;
   1085             }
   1086 
   1087             switch (property.getType()) {
   1088                 case BOOLEAN:
   1089                     try {
   1090                         if (getBooleanReply(result)) {
   1091                             map.put(property.getName(), "yes");
   1092                             i++; // valid reply, move to next property
   1093                         } else {
   1094                             map.put(property.getName(), "no");
   1095                             i++; // valid reply, move to next property
   1096                         }
   1097                     } catch (IOException e) {
   1098                         // display error, and do not increment i to redo this property
   1099                         mSdkLog.printf("\n%s\n", e.getMessage());
   1100                     }
   1101                     break;
   1102                 case INTEGER:
   1103                     try {
   1104                         Integer.parseInt(result);
   1105                         map.put(property.getName(), result);
   1106                         i++; // valid reply, move to next property
   1107                     } catch (NumberFormatException e) {
   1108                         // display error, and do not increment i to redo this property
   1109                         mSdkLog.printf("\n%s\n", e.getMessage());
   1110                     }
   1111                     break;
   1112                 case DISKSIZE:
   1113                     // TODO check validity
   1114                     map.put(property.getName(), result);
   1115                     i++; // valid reply, move to next property
   1116                     break;
   1117             }
   1118 
   1119             mSdkLog.printf("\n"); // empty line
   1120         }
   1121 
   1122         return map;
   1123     }
   1124 
   1125     /**
   1126      * Reads the line from the input stream.
   1127      * @param buffer
   1128      * @throws IOException
   1129      */
   1130     private String readLine(byte[] buffer) throws IOException {
   1131         int count = System.in.read(buffer);
   1132 
   1133         // is the input longer than the buffer?
   1134         if (count == buffer.length && buffer[count-1] != 10) {
   1135             // create a new temp buffer
   1136             byte[] tempBuffer = new byte[256];
   1137 
   1138             // and read the rest
   1139             String secondHalf = readLine(tempBuffer);
   1140 
   1141             // return a concat of both
   1142             return new String(buffer, 0, count) + secondHalf;
   1143         }
   1144 
   1145         // ignore end whitespace
   1146         while (count > 0 && (buffer[count-1] == '\r' || buffer[count-1] == '\n')) {
   1147             count--;
   1148         }
   1149 
   1150         return new String(buffer, 0, count);
   1151     }
   1152 
   1153     /**
   1154      * Returns the boolean value represented by the string.
   1155      * @throws IOException If the value is not a boolean string.
   1156      */
   1157     private boolean getBooleanReply(String reply) throws IOException {
   1158 
   1159         for (String valid : BOOLEAN_YES_REPLIES) {
   1160             if (valid.equalsIgnoreCase(reply)) {
   1161                 return true;
   1162             }
   1163         }
   1164 
   1165         for (String valid : BOOLEAN_NO_REPLIES) {
   1166             if (valid.equalsIgnoreCase(reply)) {
   1167                 return false;
   1168             }
   1169         }
   1170 
   1171         throw new IOException(String.format("%s is not a valid reply", reply));
   1172     }
   1173 
   1174     private void errorAndExit(String format, Object...args) {
   1175         mSdkLog.error(null, format, args);
   1176         System.exit(1);
   1177     }
   1178 
   1179     /**
   1180      * Converts a symbolic target name (such as those accepted by --target on the command-line)
   1181      * to an internal target index id. A valid target name is either a numeric target id (> 0)
   1182      * or a target hash string.
   1183      * <p/>
   1184      * If the given target can't be mapped, {@link #INVALID_TARGET_ID} (0) is returned.
   1185      * It's up to the caller to output an error.
   1186      * <p/>
   1187      * On success, returns a value > 0.
   1188      */
   1189     private int resolveTargetName(String targetName) {
   1190 
   1191         if (targetName == null) {
   1192             return INVALID_TARGET_ID;
   1193         }
   1194 
   1195         targetName = targetName.trim();
   1196 
   1197         // Case of an integer number
   1198         if (targetName.matches("[0-9]*")) {
   1199             try {
   1200                 int n = Integer.parseInt(targetName);
   1201                 return n < 1 ? INVALID_TARGET_ID : n;
   1202             } catch (NumberFormatException e) {
   1203                 // Ignore. Should not happen.
   1204             }
   1205         }
   1206 
   1207         // Let's try to find a platform or addon name.
   1208         IAndroidTarget[] targets = mSdkManager.getTargets();
   1209         for (int i = 0; i < targets.length; i++) {
   1210             if (targetName.equals(targets[i].hashString())) {
   1211                 return i + 1;
   1212             }
   1213         }
   1214 
   1215         return INVALID_TARGET_ID;
   1216     }
   1217 }
   1218