1 /*
2  * Copyright (C) 2012 The Android Open Source Project
3  *
4  * Licensed under the Eclipse Public License, Version 1.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.eclipse.org/org/documents/epl-v10.php
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 package com.android.ide.eclipse.adt.internal.wizards.templates;
17 
18 import static com.android.SdkConstants.CURRENT_PLATFORM;
19 import static com.android.SdkConstants.FD_TOOLS;
20 import static com.android.SdkConstants.PLATFORM_WINDOWS;
21 import static com.android.ide.eclipse.adt.internal.wizards.templates.NewProjectWizard.ATTR_MIN_API;
22 import static com.android.ide.eclipse.adt.internal.wizards.templates.NewProjectWizard.ATTR_MIN_BUILD_API;
23 import static com.android.ide.eclipse.adt.internal.wizards.templates.TemplateHandler.ATTR_ID;
24 
25 import com.android.annotations.NonNull;
26 import com.android.annotations.Nullable;
27 import com.android.sdklib.SdkVersionInfo;
28 import com.android.ide.eclipse.adt.AdtPlugin;
29 import com.android.ide.eclipse.adt.AdtUtils;
30 import com.android.ide.eclipse.adt.internal.lint.EclipseLintClient;
31 import com.android.ide.eclipse.adt.internal.preferences.AdtPrefs;
32 import com.android.ide.eclipse.adt.internal.sdk.Sdk;
33 import com.android.ide.eclipse.tests.SdkLoadingTestCase;
34 import com.android.sdklib.IAndroidTarget;
35 import com.android.utils.GrabProcessOutput;
36 import com.android.utils.GrabProcessOutput.IProcessOutput;
37 import com.android.utils.GrabProcessOutput.Wait;
38 import com.android.tools.lint.checks.ManifestDetector;
39 import com.android.tools.lint.checks.SecurityDetector;
40 import com.android.tools.lint.client.api.Configuration;
41 import com.android.tools.lint.client.api.DefaultConfiguration;
42 import com.android.tools.lint.client.api.JavaParser;
43 import com.android.tools.lint.client.api.LintClient;
44 import com.android.tools.lint.client.api.LintDriver;
45 import com.android.tools.lint.client.api.XmlParser;
46 import com.android.tools.lint.detector.api.Category;
47 import com.android.tools.lint.detector.api.Context;
48 import com.android.tools.lint.detector.api.Issue;
49 import com.android.tools.lint.detector.api.Location;
50 import com.android.tools.lint.detector.api.Project;
51 import com.android.tools.lint.detector.api.Scope;
52 import com.android.tools.lint.detector.api.Severity;
53 import com.android.tools.lint.detector.api.TextFormat;
54 import com.google.common.base.Charsets;
55 import com.google.common.base.Stopwatch;
56 import com.google.common.collect.Lists;
57 import com.google.common.collect.Sets;
58 import com.google.common.io.Files;
59 
60 import org.eclipse.core.resources.IProject;
61 import org.eclipse.core.runtime.CoreException;
62 import org.eclipse.core.runtime.IPath;
63 import org.eclipse.core.runtime.IProgressMonitor;
64 import org.eclipse.core.runtime.IStatus;
65 import org.eclipse.core.runtime.NullProgressMonitor;
66 import org.eclipse.core.runtime.Platform;
67 import org.eclipse.core.runtime.QualifiedName;
68 import org.eclipse.core.runtime.Status;
69 import org.eclipse.core.runtime.jobs.Job;
70 import org.eclipse.ltk.core.refactoring.Change;
71 import org.eclipse.ltk.core.refactoring.CompositeChange;
72 import org.w3c.dom.Element;
73 
74 import java.io.File;
75 import java.io.IOException;
76 import java.lang.reflect.InvocationTargetException;
77 import java.util.ArrayList;
78 import java.util.Collections;
79 import java.util.List;
80 import java.util.Set;
81 
82 /**
83  * Unit tests for template instantiation.
84  * <p>
85  * Note: This test can take multiple hours to run!
86  *
87  * <p>
88  * TODO: Test all permutations of variables (it currently just varies one at a time with the
89  *    rest of the defaults)
90  * TODO: Test trying to change strings arguments (currently just varies enums and booleans)
91  * TODO: Test adding multiple instances of the templates (to look for resource conflicts)
92  */
93 @SuppressWarnings("javadoc")
94 public class TemplateHandlerTest extends SdkLoadingTestCase {
95     /**
96      * Flag used to quickly check each template once (for one version), to get
97      * quicker feedback on whether something is broken instead of waiting for
98      * all the versions for each template first
99      */
100     private static final boolean TEST_FEWER_API_VERSIONS = true;
101     private static final boolean TEST_JUST_ONE_MIN_SDK = false;
102     private static final boolean TEST_JUST_ONE_BUILD_TARGET = true;
103     private static final boolean TEST_JUST_ONE_TARGET_SDK_VERSION = true;
104     private QualifiedName ERROR_KEY = new QualifiedName(AdtPlugin.PLUGIN_ID, "JobErrorKey");
105     private static int sCount = 0;
106     /**
107      * If true, check this template with all the interesting (
108      * {@link #isInterestingApiLevel(int)}) api versions
109      */
110     private boolean mApiSensitiveTemplate;
111     /**
112      * Set of templates already tested with separate unit test; remainder is
113      * checked in {@link #testCreateRemainingProjects()}
114      */
115     private static final Set<File> sProjectTestedSeparately = Sets.newHashSet();
116     /**
117      * Set of templates already tested with separate unit test; remainder is
118      * checked in {@link #testCreateRemainingTemplates()}
119      */
120     private static final Set<File> sTemplateTestedSeparately = Sets.newHashSet();
121 
122     @Override
setUp()123     protected void setUp() throws Exception {
124         super.setUp();
125         mApiSensitiveTemplate = true;
126     }
127 
128     /**
129      * Is the given api level interesting for testing purposes? This is used to
130      * skip gaps, such that we for example only check say api 8, 9, 11, 14, etc
131      * -- versions where the <b>templates</b> are doing conditional changes. To
132      * be EXTRA comprehensive, occasionally try returning true unconditionally
133      * here to test absolutely everything.
134      */
isInterestingApiLevel(int api)135     private boolean isInterestingApiLevel(int api) {
136         // For templates that aren't API sensitive, only test with API = 16
137         if (!mApiSensitiveTemplate) {
138             return api == 16;
139         }
140 
141         switch (api) {
142             case 1:
143             case 8:
144                 return true;
145             case 11:
146                 return true;
147             case 14:
148                 return true;
149             case 9:
150             case 16:
151                 return !TEST_FEWER_API_VERSIONS;
152             default:
153                 return false;
154         }
155     }
156 
testNewBlankProject()157     public void testNewBlankProject() throws Exception {
158         Stopwatch stopwatch = Stopwatch.createUnstarted();
159         stopwatch.start();
160         checkProjectWithActivity(null);
161         stopwatch.stop();
162         System.out.println("Checked blank project successfully in "
163                 + stopwatch.toString());
164     }
165 
testNewBlankActivity()166     public void testNewBlankActivity() throws Exception {
167         checkCreateTemplate("activities", "BlankActivity");
168     }
169 
testBlankActivityInProject()170     public void testBlankActivityInProject() throws Exception {
171         checkCreateActivityInProject("BlankActivity");
172     }
173 
testNewMasterDetailFlow()174     public void testNewMasterDetailFlow() throws Exception {
175         checkCreateTemplate("activities", "MasterDetailFlow");
176     }
177 
testMasterDetailFlowInProject()178     public void testMasterDetailFlowInProject() throws Exception {
179         checkCreateActivityInProject("MasterDetailFlow");
180     }
181 
testNewFullscreen()182     public void testNewFullscreen() throws Exception {
183         checkCreateTemplate("activities", "FullscreenActivity");
184     }
185 
testFullscreenInProject()186     public void testFullscreenInProject() throws Exception {
187         checkCreateActivityInProject("FullscreenActivity");
188     }
189 
testNewLoginActivity()190     public void testNewLoginActivity() throws Exception {
191         checkCreateTemplate("activities", "LoginActivity");
192     }
193 
testLoginActivityInProject()194     public void testLoginActivityInProject() throws Exception {
195         checkCreateActivityInProject("MasterDetailFlow");
196     }
197 
testNewSettingsActivity()198     public void testNewSettingsActivity() throws Exception {
199         checkCreateTemplate("activities", "SettingsActivity");
200     }
201 
testSettingsActivityInProject()202     public void testSettingsActivityInProject() throws Exception {
203         checkCreateActivityInProject("SettingsActivity");
204     }
205 
testNewBroadcastReceiver()206     public void testNewBroadcastReceiver() throws Exception {
207         // No need to try this template with multiple platforms, one is adequate
208         mApiSensitiveTemplate = false;
209         checkCreateTemplate("other", "BroadcastReceiver");
210     }
211 
testNewContentProvider()212     public void testNewContentProvider() throws Exception {
213         mApiSensitiveTemplate = false;
214         checkCreateTemplate("other", "ContentProvider");
215     }
216 
testNewCustomView()217     public void testNewCustomView() throws Exception {
218         mApiSensitiveTemplate = false;
219         checkCreateTemplate("other", "CustomView");
220     }
221 
testNewService()222     public void testNewService() throws Exception {
223         mApiSensitiveTemplate = false;
224         checkCreateTemplate("other", "Service");
225     }
226 
testCreateRemainingTemplates()227     public void testCreateRemainingTemplates() throws Exception {
228         sCount = 0;
229         long begin = System.currentTimeMillis();
230         TemplateManager manager = new TemplateManager();
231         List<File> other = manager.getTemplates("other");
232         for (File templateFile : other) {
233             if (sTemplateTestedSeparately.contains(templateFile)) {
234                 continue;
235             }
236             checkTemplate(templateFile);
237         }
238         // Also try creating templates, not as part of creating a project
239         List<File> activities = manager.getTemplates("activities");
240         for (File templateFile : activities) {
241             if (sTemplateTestedSeparately.contains(templateFile)) {
242                 continue;
243             }
244             checkTemplate(templateFile);
245         }
246         long end = System.currentTimeMillis();
247         System.out.println("Successfully checked " + sCount + " template permutations in "
248                 + ((end - begin) / (1000 * 60)) + " minutes");
249     }
250 
testCreateRemainingProjects()251     public void testCreateRemainingProjects() throws Exception {
252         sCount = 0;
253         long begin = System.currentTimeMillis();
254         TemplateManager manager = new TemplateManager();
255         List<File> templates = manager.getTemplates("activities");
256         for (File activityFile : templates) {
257             if (sTemplateTestedSeparately.contains(activityFile)) {
258                 continue;
259             }
260             checkProjectWithActivity(activityFile.getName());
261         }
262         long end = System.currentTimeMillis();
263         System.out.println("Successfully checked " + sCount + " project permutations in "
264                 + ((end - begin) / (1000 * 60)) + " minutes");
265     }
266 
267     // ---- Test support code below ----
268 
checkCreateActivityInProject(String activityName)269     private void checkCreateActivityInProject(String activityName) throws Exception {
270         Stopwatch stopwatch = Stopwatch.createUnstarted();
271         stopwatch.start();
272         File templateFile = findTemplate("activities", activityName);
273         sProjectTestedSeparately.add(templateFile);
274         checkProjectWithActivity(templateFile.getName());
275         stopwatch.stop();
276         System.out.println("Checked " + templateFile.getName() + " successfully in "
277                 + stopwatch.toString());
278     }
279 
checkCreateTemplate(String category, String name)280     private void checkCreateTemplate(String category, String name) throws Exception {
281         Stopwatch stopwatch = Stopwatch.createUnstarted();
282         stopwatch.start();
283         File templateFile = findTemplate(category, name);
284         assertNotNull(templateFile);
285         sTemplateTestedSeparately.add(templateFile);
286         checkTemplate(templateFile);
287         stopwatch.stop();
288         System.out.println("Checked " + templateFile.getName() + " successfully in "
289                 + stopwatch.toString());
290     }
291 
findTemplate(String category, String name)292     private static File findTemplate(String category, String name) {
293         File templateRootFolder = TemplateManager.getTemplateRootFolder();
294         assertNotNull(templateRootFolder);
295         File file = new File(templateRootFolder, category + File.separator + name);
296         assertTrue(file.getPath(), file.exists());
297         return file;
298     }
299 
checkTemplate(File templateFile)300     private void checkTemplate(File templateFile) throws Exception {
301         NewProjectWizardState values = new NewProjectWizardState();
302         values.applicationName = "My Application";
303         values.packageName = "my.pkg2";
304 
305         values.isLibrary = false;
306         values.createIcon = false;
307         values.useDefaultLocation = true;
308         values.createActivity = false;
309 
310         String projectNameBase = "MyTemplateProject_" + templateFile.getName();
311         values.projectName = projectNameBase;
312         values.createActivity = false;
313 
314         // Create the new template
315 
316         NewTemplateWizardState state = new NewTemplateWizardState();
317         state.setTemplateLocation(templateFile);
318         state.minSdkLevel = values.minSdkLevel;
319 
320         // Iterate over all (valid) combinations of build target, minSdk and targetSdk
321         IAndroidTarget[] targets = Sdk.getCurrent().getTargets();
322         for (int i = targets.length - 1; i >= 0; i--) {
323             IAndroidTarget target = targets[i];
324             if (!target.isPlatform()) {
325                 continue;
326             }
327             if (!isInterestingApiLevel(target.getVersion().getApiLevel())) {
328                 continue;
329             }
330 
331             for (int minSdk = 1;
332                      minSdk <= SdkVersionInfo.HIGHEST_KNOWN_API;
333                      minSdk++) {
334                 // Don't bother checking *every* single minSdk, just pick some interesting ones
335                 if (!isInterestingApiLevel(minSdk)) {
336                     continue;
337                 }
338 
339                 for (int targetSdk = minSdk;
340                          targetSdk <= SdkVersionInfo.HIGHEST_KNOWN_API;
341                          targetSdk++) {
342                     if (!isInterestingApiLevel(targetSdk)) {
343                         continue;
344                     }
345 
346                     // Make sure this template is supported with these versions
347                     IStatus status = values.template.validateTemplate(
348                             minSdk, target.getVersion().getApiLevel());
349                     if (status != null && !status.isOK()) {
350                         continue;
351                     }
352 
353                     // Also make sure activity is enabled for these versions
354                     status = state.getTemplateHandler().validateTemplate(
355                             minSdk, target.getVersion().getApiLevel());
356                     if (status != null && !status.isOK()) {
357                         continue;
358                     }
359 
360                     // Iterate over all new new project templates
361 
362                     // should I try all options of theme with all platforms?
363                     // or just try all platforms, with one setting for each?
364                     // doesn't seem like I need to multiply
365                     // just pick the best setting that applies instead for each platform
366                     List<Parameter> parameters = values.template.getTemplate().getParameters();
367                 projectParameters:
368                     for (Parameter parameter : parameters) {
369                         List<Element> options = parameter.getOptions();
370                         if (parameter.type == Parameter.Type.ENUM) {
371                             for (Element element : options) {
372                                 Option option = Option.get(element);
373                                 String optionId = option.id;
374                                 int optionMinSdk = option.minSdk;
375                                 int optionMinBuildApi = option.minBuild;
376                                 if (optionMinSdk <= minSdk &&
377                                         optionMinBuildApi <= target.getVersion().getApiLevel()) {
378                                     values.parameters.put(parameter.id, optionId);
379                                     if (parameter.id.equals("baseTheme")) {
380                                         String base = projectNameBase + "_min_" + minSdk
381                                                 + "_target_" + targetSdk
382                                                 + "_build_" + target.getVersion().getApiLevel()
383                                                 + "_theme_" + optionId;
384                                         System.out.println("checking base " + base);
385 
386                                         checkApiTarget(minSdk, targetSdk, target, values, base,
387                                                 state);
388                                         break projectParameters;
389                                     }
390                                 }
391                             }
392                         }
393                     }
394 
395                     if (TEST_JUST_ONE_TARGET_SDK_VERSION) {
396                         break;
397                     }
398                 }
399 
400                 if (TEST_JUST_ONE_MIN_SDK) {
401                     break;
402                 }
403             }
404 
405             if (TEST_JUST_ONE_BUILD_TARGET) {
406                 break;
407             }
408         }
409     }
410 
checkProjectWithActivity(String activity)411     private void checkProjectWithActivity(String activity) throws Exception {
412         NewProjectWizardState values = new NewProjectWizardState();
413         values.applicationName = "My Application";
414         values.packageName = "my.pkg";
415 
416         values.isLibrary = false;
417         values.createIcon = false;
418         values.useDefaultLocation = true;
419 
420         // These are basically unused; passed as defaults
421         values.activityName = activity == null ? "Blank" : activity;
422         values.activityTitle = "My Activity Title";
423 
424         String projectNameBase = "MyProject_" + values.activityName;
425         values.projectName = projectNameBase;
426 
427         values.createActivity = activity != null;
428         NewTemplateWizardState activityValues = values.activityValues;
429         assertNotNull(activityValues);
430         activityValues.minSdkLevel = values.minSdkLevel;
431 
432 
433         // Iterate over all (valid) combinations of build target, minSdk and targetSdk
434         IAndroidTarget[] targets = Sdk.getCurrent().getTargets();
435         for (int i = targets.length - 1; i >= 0; i--) {
436             IAndroidTarget target = targets[i];
437             if (!target.isPlatform()) {
438                 continue;
439             }
440             if (!isInterestingApiLevel(target.getVersion().getApiLevel())) {
441                 continue;
442             }
443 
444             for (int minSdk = 1;
445                      minSdk <= SdkVersionInfo.HIGHEST_KNOWN_API;
446                      minSdk++) {
447                 // Don't bother checking *every* single minSdk, just pick some interesting ones
448                 if (!isInterestingApiLevel(minSdk)) {
449                     continue;
450                 }
451 
452                 for (int targetSdk = minSdk;
453                          targetSdk <= SdkVersionInfo.HIGHEST_KNOWN_API;
454                          targetSdk++) {
455                     if (!isInterestingApiLevel(targetSdk)) {
456                         continue;
457                     }
458 
459                     // Make sure this template is supported with these versions
460                     IStatus status = values.template.validateTemplate(
461                             values.minSdkLevel, values.getBuildApi());
462                     if (status != null && !status.isOK()) {
463                         continue;
464                     }
465 
466                     // Also make sure activity is enabled for these versions
467                     status = values.activityValues.getTemplateHandler().validateTemplate(
468                             values.minSdkLevel, values.getBuildApi());
469                     if (status != null && !status.isOK()) {
470                         continue;
471                     }
472 
473                     // Iterate over all new new project templates
474 
475                     // should I try all options of theme with all platforms?
476                     // or just try all platforms, with one setting for each?
477                     // doesn't seem like I need to multiply
478                     // just pick the best setting that applies instead for each platform
479                     List<Parameter> parameters = values.template.getTemplate().getParameters();
480                     for (Parameter parameter : parameters) {
481                         List<Element> options = parameter.getOptions();
482                         if (parameter.type == Parameter.Type.ENUM) {
483                             for (Element element : options) {
484                                 Option option = Option.get(element);
485                                 String optionId = option.id;
486                                 int optionMinSdk = option.minSdk;
487                                 int optionMinBuildApi = option.minBuild;
488                                 if (optionMinSdk <= minSdk &&
489                                         optionMinBuildApi <= target.getVersion().getApiLevel()) {
490                                     values.parameters.put(parameter.id, optionId);
491                                     if (parameter.id.equals("baseTheme")) {
492                                         String base = projectNameBase + "_min_" + minSdk
493                                                 + "_target_" + targetSdk
494                                                 + "_build_" + target.getVersion().getApiLevel()
495                                                 + "_theme_" + optionId;
496                                         System.out.println("checking base " + base);
497 
498                                         checkApiTarget(minSdk, targetSdk, target, values, base,
499                                                 null);
500 
501                                     }
502                                 }
503                             }
504                         }
505                     }
506 
507                     if (TEST_JUST_ONE_TARGET_SDK_VERSION) {
508                         break;
509                     }
510                 }
511 
512                 if (TEST_JUST_ONE_MIN_SDK) {
513                     break;
514                 }
515             }
516 
517             if (TEST_JUST_ONE_BUILD_TARGET) {
518                 break;
519             }
520         }
521     }
522 
checkApiTarget( int minSdk, int targetSdk, @NonNull IAndroidTarget target, @NonNull NewProjectWizardState projectValues, @NonNull String projectNameBase, @Nullable NewTemplateWizardState templateValues)523     private void checkApiTarget(
524             int minSdk,
525             int targetSdk,
526             @NonNull IAndroidTarget target,
527             @NonNull NewProjectWizardState projectValues,
528             @NonNull String projectNameBase,
529             @Nullable NewTemplateWizardState templateValues)
530             throws Exception {
531         NewTemplateWizardState values =
532                 projectValues.createActivity ? projectValues.activityValues : templateValues;
533 
534         projectValues.minSdk = Integer.toString(minSdk);
535         projectValues.minSdkLevel = minSdk;
536         projectValues.targetSdkLevel = targetSdk;
537         projectValues.target = target;
538 
539         if (values == null) {
540             checkProject(projectValues, templateValues);
541             return;
542         }
543 
544         // Next check all other parameters, cycling through booleans and enums.
545         TemplateHandler templateHandler = values.getTemplateHandler();
546         TemplateMetadata template = templateHandler.getTemplate();
547         assertNotNull(template);
548         List<Parameter> parameters = template.getParameters();
549 
550         if (!projectValues.createActivity) {
551             for (Parameter parameter : parameters) {
552                 values.parameters.put(parameter.id, parameter.value);
553             }
554         }
555 
556         for (Parameter parameter : parameters) {
557             if (parameter.type == Parameter.Type.SEPARATOR
558                     || parameter.type == Parameter.Type.STRING) {
559                 // TODO: Consider whether we should attempt some strings here
560                 continue;
561             }
562 
563             // The initial (default value); revert to this one after cycling,
564             Object initial = values.parameters.get(parameter.id);
565 
566             if (parameter.type == Parameter.Type.ENUM) {
567                 List<Element> options = parameter.getOptions();
568                 for (Element element : options) {
569                     Option option = Option.get(element);
570                     String optionId = option.id;
571                     int optionMinSdk = option.minSdk;
572                     int optionMinBuildApi = option.minBuild;
573                     if (projectValues.minSdkLevel >= optionMinSdk &&
574                             projectValues.getBuildApi() >= optionMinBuildApi) {
575                         values.parameters.put(parameter.id, optionId);
576                         projectValues.projectName = projectNameBase + "_" + parameter.id
577                                 + "_" + optionId;
578                         checkProject(projectValues, templateValues);
579                     }
580                 }
581             } else {
582                 assert parameter.type == Parameter.Type.BOOLEAN;
583                 if (parameter.id.equals("isLauncher") && projectValues.createActivity) {
584                     // Skipping this one: always true when launched from new project
585                     continue;
586                 }
587                 boolean value = false;
588                 values.parameters.put(parameter.id, value);
589                 projectValues.projectName = projectNameBase + "_" + parameter.id
590                         + "_" + value;
591                 checkProject(projectValues, templateValues);
592 
593                 value = true;
594                 values.parameters.put(parameter.id, value);
595                 projectValues.projectName = projectNameBase + "_" + parameter.id
596                         + "_" + value;
597                 checkProject(projectValues, templateValues);
598             }
599 
600             values.parameters.put(parameter.id, initial);
601         }
602     }
603 
604     private final class OutputGrabber implements IProcessOutput {
605         private final List<String> output = Lists.newArrayList();
606         private final List<String> error = Lists.newArrayList();
607 
608         @Override
out(@ullable String line)609         public void out(@Nullable String line) {
610             if (line != null) {
611                 output.add(line);
612             }
613         }
614 
615         @Override
err(@ullable String line)616         public void err(@Nullable String line) {
617             if (line != null) {
618                 error.add(line);
619             }
620         }
621 
622         @NonNull
getOutput()623         private List<String> getOutput() {
624             return output;
625         }
626 
627         @NonNull
getError()628         private List<String> getError() {
629             return error;
630         }
631     }
632 
633     private static class Option {
634         private String id;
635         private int minSdk;
636         private int minBuild;
637 
Option(String id, int minSdk, int minBuild)638         public Option(String id, int minSdk, int minBuild) {
639             this.id = id;
640             this.minSdk = minSdk;
641             this.minBuild = minBuild;
642         }
643 
get(Element option)644         private static Option get(Element option) {
645             String optionId = option.getAttribute(ATTR_ID);
646             String minApiString = option.getAttribute(ATTR_MIN_API);
647             int optionMinSdk = 1;
648             if (minApiString != null && !minApiString.isEmpty()) {
649                 try {
650                     optionMinSdk = Integer.parseInt(minApiString);
651                 } catch (NumberFormatException nufe) {
652                     // Templates aren't allowed to contain codenames, should
653                     // always be an integer
654                     AdtPlugin.log(nufe, null);
655                     optionMinSdk = 1;
656                 }
657             }
658             String minBuildApiString = option.getAttribute(ATTR_MIN_BUILD_API);
659             int optionMinBuildApi = 1;
660             if (minBuildApiString != null && !minBuildApiString.isEmpty()) {
661                 try {
662                     optionMinBuildApi = Integer.parseInt(minBuildApiString);
663                 } catch (NumberFormatException nufe) {
664                     // Templates aren't allowed to contain codenames, should
665                     // always be an integer
666                     AdtPlugin.log(nufe, null);
667                     optionMinBuildApi = 1;
668                 }
669             }
670 
671 
672             return new Option(optionId, optionMinSdk, optionMinBuildApi);
673         }
674     }
675 
checkProject( @onNull NewProjectWizardState projectValues, @Nullable NewTemplateWizardState templateValues)676     private void checkProject(
677             @NonNull NewProjectWizardState projectValues,
678             @Nullable NewTemplateWizardState templateValues) throws Exception {
679         NewTemplateWizardState values =
680                 projectValues.createActivity ? projectValues.activityValues : templateValues;
681         if (values != null) { // if not, creating blank project
682             // Validate that a template is only being used in a context it is compatible with!
683             IStatus status = values.getTemplateHandler().validateTemplate(
684                     projectValues.minSdkLevel, projectValues.getBuildApi());
685             if (status != null && !status.isOK()) {
686                 fail(status.toString());
687             }
688         }
689 
690         assertNotNull(projectValues.projectName);
691         projectValues.projectName = AdtUtils.getUniqueProjectName(projectValues.projectName, "");
692         IPath workspace = Platform.getLocation();
693         String projectLocation = workspace.append(projectValues.projectName).toOSString();
694         projectValues.projectLocation = projectLocation;
695 
696         // Create project with the given parameter map
697         final IProject project = createProject(projectValues);
698         assertNotNull(project);
699 
700         if (templateValues != null) {
701             templateValues.project = project;
702             List<Change> changes = templateValues.computeChanges();
703             if (!changes.isEmpty()) {
704                 try {
705                     CompositeChange composite = new CompositeChange("",
706                             changes.toArray(new Change[changes.size()]));
707                     composite.perform(new NullProgressMonitor());
708                 } catch (CoreException e) {
709                     fail(e.getLocalizedMessage());
710                 }
711             }
712         }
713 
714         // Project creation has some async hooks so don't attempt to build it *right* away
715         Job job = new Job("Validate project") {
716             @Override
717             protected IStatus run(IProgressMonitor monitor) {
718                 try {
719                     ensureValidProject(this, project);
720                     return Status.OK_STATUS;
721                 } catch (Exception e) {
722                     fail(e.toString());
723                 }
724                 return null;
725             }
726         };
727         job.schedule(1000);
728         job.join();
729         Object property = job.getProperty(ERROR_KEY);
730         assertNull(property);
731     }
732 
createProject(NewProjectWizardState values)733     private IProject createProject(NewProjectWizardState values) throws InvocationTargetException {
734         NewProjectWizard wizard = new NewProjectWizard();
735         wizard.setValues(values);
736         wizard.performFinish(new NullProgressMonitor());
737 
738         if (TemplateHandler.sMostRecentException != null) {
739             fail(values.projectName + ": " + TemplateHandler.sMostRecentException.toString());
740         }
741 
742         IProject project = wizard.getProject();
743         assertNotNull(project);
744         assertTrue(project.exists());
745         System.out.println("Created project " + project + " : " + AdtUtils.getAbsolutePath(project));
746         return project;
747     }
748 
ensureValidProject(@onNull Job job, @NonNull IProject project)749     private void ensureValidProject(@NonNull Job job, @NonNull IProject project) throws Exception {
750         System.out.println("Begin build error check");
751         ensureNoBuildErrors(job, project);
752         System.out.println("Finished build error check");
753 
754         System.out.println("Begin lint check");
755         ensureNoLintWarnings(job, project);
756         System.out.println("Finished lint check");
757 
758         sCount++;
759     }
760 
ensureNoLintWarnings(final Job job, IProject project)761     private void ensureNoLintWarnings(final Job job, IProject project) {
762         System.setProperty("com.android.tools.lint.bindir", AdtPrefs.getPrefs().getOsSdkFolder()
763                 + File.separator + FD_TOOLS);
764 
765         LintDriver driver = new LintDriver(EclipseLintClient.getRegistry(), new LintClient() {
766             @Override
767             public void report(@NonNull Context context,
768                     @NonNull Issue issue, @NonNull Severity severity,
769                     @Nullable Location location, @NonNull String message, @NonNull TextFormat format) {
770                 String s = "Found lint error: " + issue.getId() + ": " + message + " at " + location;
771                 job.setProperty(ERROR_KEY, s);
772                 fail(s);
773             }
774 
775             @Override
776             public Configuration getConfiguration(@NonNull Project p) {
777                 return new DefaultConfiguration(this, p, null, new File("dummy.xml")) {
778                     @Override
779                     public boolean isEnabled(@NonNull Issue issue) {
780                         // Doesn't work: hangs in unit test context, something about
781                         // loading native libs.
782                         if (issue.getCategory() == Category.ICONS){
783                             return false;
784                         }
785 
786                         if (issue == ManifestDetector.TARGET_NEWER) {
787                             // Don't complain about targetSdk < latest: we're deliberately
788                             // testing that (to make sure templates compile etc in compat
789                             // mode)
790                             return false;
791                         }
792 
793                         if (issue == SecurityDetector.EXPORTED_SERVICE
794                                 || issue == SecurityDetector.EXPORTED_PROVIDER
795                                 || issue == SecurityDetector.EXPORTED_RECEIVER) {
796                             // Don't complain about missing permissions when exporting: the
797                             // unit test is deliberately turning on exported
798                             return false;
799                         }
800 
801                         return true;
802                     }
803                 };
804             }
805 
806             @Override
807             @NonNull
808             public String readFile(@NonNull File file) {
809                 try {
810                     return Files.toString(file, Charsets.UTF_8);
811                 } catch (IOException e) {
812                     fail(e.toString() + " for " + file.getPath());
813                     return "";
814                 }
815             }
816 
817             @Override
818             public void log(@NonNull Severity severity, @Nullable Throwable exception,
819                     @Nullable String format, @Nullable Object... args) {
820                 if (exception != null) {
821                     exception.printStackTrace();
822                 }
823                 if (format != null) {
824                     if (args != null) {
825                         System.err.println("Log: " + String.format(format, args));
826                     } else {
827                         System.err.println("Unexpected log message " + format);
828                     }
829                 }
830             }
831 
832             @Override
833             @Nullable
834             public JavaParser getJavaParser(@Nullable Project project) {
835                 return new EclipseLintClient(null, null, null, false).getJavaParser(project);
836             }
837 
838             @Override
839             public XmlParser getXmlParser() {
840                 return new EclipseLintClient(null, null, null, false).getXmlParser();
841             }
842         });
843         File projectDir = AdtUtils.getAbsolutePath(project).toFile();
844         assertNotNull(projectDir);
845         assertTrue(projectDir.getPath(), projectDir.isDirectory());
846         driver.analyze(Collections.singletonList(projectDir), Scope.ALL);
847     }
848 
849     // Wait for test build support.
850     // This is copied from {@link SampleProjectTest}
851 
ensureNoBuildErrors(final Job job, final IProject project)852     private void ensureNoBuildErrors(final Job job, final IProject project) throws Exception {
853         File projectDir = AdtUtils.getAbsolutePath(project).toFile();
854 
855         // Checking the build in Eclipse doesn't work well, because of asynchronous issues
856         // (it looks like not all necessary changes are applied, and even adding waits works
857         // unpredictably.)
858         //
859         // So instead we do it via the command line.
860         // First add ant support:
861         //     $ android update project -p .
862         // Then we run ant and look at the exit code to make sure it worked.
863 
864         List<String> command = new ArrayList<String>();
865         command.add(AdtPlugin.getOsSdkToolsFolder() + "android" +
866                 (CURRENT_PLATFORM == PLATFORM_WINDOWS ? ".bat" : ""));
867         command.add("update");
868         command.add("project");
869         command.add("-p");
870         command.add(projectDir.getPath());
871 
872         // launch the command line process
873         Process process = Runtime.getRuntime().exec(command.toArray(new String[command.size()]));
874 
875 
876         OutputGrabber processOutput = new OutputGrabber();
877         int status = GrabProcessOutput.grabProcessOutput(
878                 process,
879                 Wait.WAIT_FOR_READERS, // we really want to make sure we get all the output!
880                 processOutput);
881         if (status != 0) {
882             fail(processOutput.getOutput().toString() + processOutput.getError().toString());
883         }
884         assertEquals(0, status);
885 
886         // Run ant
887         String antCmd = "ant" + (CURRENT_PLATFORM == PLATFORM_WINDOWS ? ".bat" : "");
888         String antTarget = "debug";
889         process = Runtime.getRuntime().exec(antCmd + " " + antTarget, null, projectDir);
890         processOutput = new OutputGrabber();
891         status = GrabProcessOutput.grabProcessOutput(
892                 process,
893                 Wait.WAIT_FOR_READERS, // we really want to make sure we get all the output!
894                 processOutput);
895         if (status != 0) {
896             fail(processOutput.getOutput().toString() + processOutput.getError().toString());
897         }
898         assertEquals(0, status);
899         System.out.println("Ant succeeded (code=" + status + ")");
900     }
901 }
902