• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 
main(String[] args)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)
setSdkManager(SdkManager sdkManager)104     void setSdkManager(SdkManager sdkManager) {
105         mSdkManager = sdkManager;
106     }
107 
108     /**
109      * Runs the sdk manager app
110      */
run(String[] args)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      */
createLogger()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 */
setLogger(ISdkLog logger)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      */
init()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      */
parseSdk()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      */
doAction()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      */
showSdkManagerWindow()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      */
showAvdManagerWindow()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 
displayRemoteSdkListNoUI()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      */
updateSdkNoUI()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)
checkFilterValues(String filter)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      */
getProjectCreator()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      */
createProject(boolean library)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      */
createTestProject()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      */
updateProject(boolean library)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      */
updateTestProject()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      */
getProjectLocation(String newProjectLocation)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)
displayTargetList()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)
displaySkinList(IAndroidTarget target, String message)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)
displayAbiList(IAndroidTarget target, String message)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)
displayAvdList(AvdManager avdManager)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      */
displayAvdList()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      */
createAvd()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      */
deleteAvd()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      */
moveAvd()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      */
updateAvd()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      */
updateAdb()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 
createIdentity()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      */
promptForHardware(IAndroidTarget createTarget, Map<String, String> skinHardwareConfig)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      */
readLine(byte[] buffer)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      */
promptPassword(String prompt)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      */
getBooleanReply(String reply)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 
errorAndExit(String format, Object...args)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      */
resolveTargetName(String targetName)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