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