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.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