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 org.eclipse.core.resources.IResource.DEPTH_INFINITE;
19 
20 import com.android.SdkConstants;
21 import com.android.annotations.NonNull;
22 import com.android.annotations.VisibleForTesting;
23 import com.android.assetstudiolib.GraphicGenerator;
24 import com.android.ide.eclipse.adt.AdtPlugin;
25 import com.android.ide.eclipse.adt.AdtUtils;
26 import com.android.ide.eclipse.adt.internal.actions.AddSupportJarAction;
27 import com.android.ide.eclipse.adt.internal.assetstudio.AssetType;
28 import com.android.ide.eclipse.adt.internal.assetstudio.ConfigureAssetSetPage;
29 import com.android.ide.eclipse.adt.internal.assetstudio.CreateAssetSetWizardState;
30 import com.android.ide.eclipse.adt.internal.project.BaseProjectHelper;
31 import com.android.ide.eclipse.adt.internal.project.ProjectHelper;
32 import com.android.ide.eclipse.adt.internal.wizards.newproject.NewProjectCreator;
33 import com.android.ide.eclipse.adt.internal.wizards.newproject.NewProjectCreator.ProjectPopulator;
34 
35 import org.eclipse.core.resources.IProject;
36 import org.eclipse.core.resources.IWorkspaceRoot;
37 import org.eclipse.core.resources.ResourcesPlugin;
38 import org.eclipse.core.runtime.CoreException;
39 import org.eclipse.core.runtime.IProgressMonitor;
40 import org.eclipse.core.runtime.NullProgressMonitor;
41 import org.eclipse.jdt.core.IJavaProject;
42 import org.eclipse.jface.operation.IRunnableWithProgress;
43 import org.eclipse.jface.viewers.IStructuredSelection;
44 import org.eclipse.jface.wizard.IWizardPage;
45 import org.eclipse.jface.wizard.WizardPage;
46 import org.eclipse.ltk.core.refactoring.Change;
47 import org.eclipse.ltk.core.refactoring.CompositeChange;
48 import org.eclipse.swt.graphics.RGB;
49 import org.eclipse.ui.IWorkbench;
50 
51 import java.io.File;
52 import java.lang.reflect.InvocationTargetException;
53 import java.util.HashMap;
54 import java.util.List;
55 import java.util.Map;
56 import java.util.Set;
57 
58 /**
59  * Wizard for creating new projects
60  */
61 public class NewProjectWizard extends TemplateWizard {
62     private static final String PARENT_ACTIVITY_CLASS = "parentActivityClass";  //$NON-NLS-1$
63     private static final String ACTIVITY_TITLE = "activityTitle";  //$NON-NLS-1$
64     static final String IS_LAUNCHER = "isLauncher";                //$NON-NLS-1$
65     static final String IS_NEW_PROJECT = "isNewProject";           //$NON-NLS-1$
66     static final String IS_LIBRARY_PROJECT = "isLibraryProject";   //$NON-NLS-1$
67     static final String ATTR_COPY_ICONS = "copyIcons";             //$NON-NLS-1$
68     static final String ATTR_TARGET_API = "targetApi";             //$NON-NLS-1$
69     static final String ATTR_MIN_API = "minApi";                   //$NON-NLS-1$
70     static final String ATTR_MIN_BUILD_API = "minBuildApi";        //$NON-NLS-1$
71     static final String ATTR_BUILD_API = "buildApi";               //$NON-NLS-1$
72     static final String ATTR_REVISION = "revision";                //$NON-NLS-1$
73     static final String ATTR_MIN_API_LEVEL = "minApiLevel";        //$NON-NLS-1$
74     static final String ATTR_PACKAGE_NAME = "packageName";         //$NON-NLS-1$
75     static final String ATTR_APP_TITLE = "appTitle";               //$NON-NLS-1$
76     static final String CATEGORY_PROJECTS = "projects";            //$NON-NLS-1$
77     static final String CATEGORY_ACTIVITIES = "activities";        //$NON-NLS-1$
78     static final String CATEGORY_OTHER = "other";                  //$NON-NLS-1$
79     static final String ATTR_APP_COMPAT = "appCompat";             //$NON-NLS-1$
80     /**
81      * Reserved file name for the launcher icon, resolves to the xhdpi version
82      *
83      * @see CreateAssetSetWizardState#getImage
84      */
85     public static final String DEFAULT_LAUNCHER_ICON = "launcher_icon";   //$NON-NLS-1$
86 
87     private NewProjectPage mMainPage;
88     private ProjectContentsPage mContentsPage;
89     private ActivityPage mActivityPage;
90     private NewTemplatePage mTemplatePage;
91     private NewProjectWizardState mValues;
92     /** The project being created */
93     private IProject mProject;
94 
95     @Override
init(IWorkbench workbench, IStructuredSelection selection)96     public void init(IWorkbench workbench, IStructuredSelection selection) {
97         super.init(workbench, selection);
98 
99         setWindowTitle("New Android Application");
100 
101         mValues = new NewProjectWizardState();
102         mMainPage = new NewProjectPage(mValues);
103         mContentsPage = new ProjectContentsPage(mValues);
104         mContentsPage.init(selection, AdtUtils.getActivePart());
105         mActivityPage = new ActivityPage(mValues, true, true);
106         mActivityPage.setLauncherActivitiesOnly(true);
107     }
108 
109     @Override
addPages()110     public void addPages() {
111         super.addPages();
112         addPage(mMainPage);
113         addPage(mContentsPage);
114         addPage(mActivityPage);
115     }
116 
117     @Override
getNextPage(IWizardPage page)118     public IWizardPage getNextPage(IWizardPage page) {
119         if (page == mMainPage) {
120             return mContentsPage;
121         }
122 
123         if (page == mContentsPage) {
124             if (mValues.createIcon) {
125                 // Bundle asset studio wizard to create the launcher icon
126                 CreateAssetSetWizardState iconState = mValues.iconState;
127                 iconState.type = AssetType.LAUNCHER;
128                 iconState.outputName = "ic_launcher"; //$NON-NLS-1$
129                 iconState.background = new RGB(0xff, 0xff, 0xff);
130                 iconState.foreground = new RGB(0x33, 0xb6, 0xea);
131                 iconState.trim = true;
132 
133                 // ADT 20: White icon with blue shape
134                 //iconState.shape = GraphicGenerator.Shape.CIRCLE;
135                 //iconState.sourceType = CreateAssetSetWizardState.SourceType.CLIPART;
136                 //iconState.clipartName = "user.png"; //$NON-NLS-1$
137                 //iconState.padding = 10;
138 
139                 // ADT 21: Use the platform packaging icon, but allow user to customize it
140                 iconState.sourceType = CreateAssetSetWizardState.SourceType.IMAGE;
141                 iconState.imagePath = new File(DEFAULT_LAUNCHER_ICON);
142                 iconState.shape = GraphicGenerator.Shape.NONE;
143                 iconState.padding = 0;
144 
145                 WizardPage p = getIconPage(mValues.iconState);
146                 p.setTitle("Configure Launcher Icon");
147                 return p;
148             } else {
149                 if (mValues.createActivity) {
150                     return mActivityPage;
151                 } else {
152                     return null;
153                 }
154             }
155         }
156 
157         if (page == mIconPage) {
158             return mActivityPage;
159         }
160 
161         if (page == mActivityPage && mValues.createActivity) {
162             if (mTemplatePage == null) {
163                 NewTemplateWizardState activityValues = mValues.activityValues;
164 
165                 // Initialize the *default* activity name based on what we've derived
166                 // from the project name
167                 activityValues.defaults.put("activityName", mValues.activityName);
168 
169                 // Hide those parameters that the template requires but that we don't want to
170                 // ask the users about, since we will supply these values from the rest
171                 // of the new project wizard.
172                 Set<String> hidden = activityValues.hidden;
173                 hidden.add(ATTR_PACKAGE_NAME);
174                 hidden.add(ATTR_APP_TITLE);
175                 hidden.add(ATTR_MIN_API);
176                 hidden.add(ATTR_MIN_API_LEVEL);
177                 hidden.add(ATTR_TARGET_API);
178                 hidden.add(ATTR_BUILD_API);
179                 hidden.add(IS_LAUNCHER);
180                 // Don't ask about hierarchical parent activities in new projects where there
181                 // can't possibly be any
182                 hidden.add(PARENT_ACTIVITY_CLASS);
183                 hidden.add(ACTIVITY_TITLE); // Not used for the first activity in the project
184 
185                 mTemplatePage = new NewTemplatePage(activityValues, false);
186                 addPage(mTemplatePage);
187             }
188             mTemplatePage.setCustomMinSdk(mValues.minSdkLevel, mValues.getBuildApi());
189             return mTemplatePage;
190         }
191 
192         if (page == mTemplatePage) {
193             TemplateMetadata template = mValues.activityValues.getTemplateHandler().getTemplate();
194             if (template != null
195                     && !InstallDependencyPage.isInstalled(template.getDependencies())) {
196                 return getDependencyPage(template, true);
197             }
198         }
199 
200         if (page == mTemplatePage || !mValues.createActivity && page == mActivityPage
201                 || page == getDependencyPage(null, false)) {
202             return null;
203         }
204 
205         return super.getNextPage(page);
206     }
207 
208     @Override
canFinish()209     public boolean canFinish() {
210         // Deal with lazy creation of some pages: these may not be in the page-list yet
211         // since they are constructed lazily, so consider that option here.
212         if (mValues.createIcon && (mIconPage == null || !mIconPage.isPageComplete())) {
213             return false;
214         }
215         if (mValues.createActivity && (mTemplatePage == null || !mTemplatePage.isPageComplete())) {
216             return false;
217         }
218 
219         // Override super behavior (which just calls isPageComplete() on each of the pages)
220         // to special case the template and icon pages since we want to skip them if
221         // the appropriate flags are not set.
222         for (IWizardPage page : getPages()) {
223             if (page == mTemplatePage && !mValues.createActivity) {
224                 continue;
225             }
226             if (page == mIconPage && !mValues.createIcon) {
227                 continue;
228             }
229             if (!page.isPageComplete()) {
230                 return false;
231             }
232         }
233 
234         return true;
235     }
236 
237     @Override
238     @NonNull
getProject()239     protected IProject getProject() {
240         return mProject;
241     }
242 
243     @Override
244     @NonNull
getFilesToOpen()245     protected List<String> getFilesToOpen() {
246         return mValues.template.getFilesToOpen();
247     }
248 
249     @VisibleForTesting
getValues()250     NewProjectWizardState getValues() {
251         return mValues;
252     }
253 
254     @VisibleForTesting
setValues(NewProjectWizardState values)255     void setValues(NewProjectWizardState values) {
256         mValues = values;
257     }
258 
259     @Override
computeChanges()260     protected List<Change> computeChanges() {
261         final TemplateHandler template = mValues.template;
262         // We'll be merging in an activity template, but don't create *~ backup files
263         // of the merged files (such as the manifest file) in that case.
264         // (NOTE: After the change from direct file manipulation to creating a list of Change
265         // objects, this no longer applies - but the code is kept around a little while longer
266         // in case we want to generate change objects that makes backups of merged files)
267         template.setBackupMergedFiles(false);
268 
269         // Generate basic output skeleton
270         Map<String, Object> paramMap = new HashMap<String, Object>();
271         addProjectInfo(paramMap);
272         TemplateHandler.addDirectoryParameters(paramMap, getProject());
273         // We don't know at this point whether the activity is going to need
274         // AppCompat so we just assume that it will.
275         if (mValues.createActivity && mValues.minSdkLevel < 14) {
276             paramMap.put(ATTR_APP_COMPAT, true);
277             getFinalizingActions().add(new Runnable() {
278                 @Override
279                 public void run() {
280                     AddSupportJarAction.installAppCompatLibrary(mProject, true);
281                 }
282             });
283         }
284 
285         return template.render(mProject, paramMap);
286     }
287 
288     @Override
performFinish(final IProgressMonitor monitor)289     protected boolean performFinish(final IProgressMonitor monitor)
290             throws InvocationTargetException {
291         try {
292             IWorkspaceRoot root = ResourcesPlugin.getWorkspace().getRoot();
293             String name = mValues.projectName;
294             mProject = root.getProject(name);
295 
296             final TemplateHandler template = mValues.template;
297             // We'll be merging in an activity template, but don't create *~ backup files
298             // of the merged files (such as the manifest file) in that case.
299             template.setBackupMergedFiles(false);
300 
301             ProjectPopulator projectPopulator = new ProjectPopulator() {
302                 @Override
303                 public void populate(IProject project) throws InvocationTargetException {
304                     // Copy in the proguard file; templates don't provide this one.
305                     // add the default proguard config
306                     File libFolder = new File(AdtPlugin.getOsSdkToolsFolder(),
307                             SdkConstants.FD_LIB);
308                     try {
309                         assert project == mProject;
310                         NewProjectCreator.addLocalFile(project,
311                                 new File(libFolder, SdkConstants.FN_PROJECT_PROGUARD_FILE),
312                                 // Write ProGuard config files with the extension .pro which
313                                 // is what is used in the ProGuard documentation and samples
314                                 SdkConstants.FN_PROJECT_PROGUARD_FILE,
315                                 new NullProgressMonitor());
316                     } catch (Exception e) {
317                         AdtPlugin.log(e, null);
318                     }
319 
320                     try {
321                         mProject.refreshLocal(DEPTH_INFINITE, new NullProgressMonitor());
322                     } catch (CoreException e) {
323                         AdtPlugin.log(e, null);
324                     }
325 
326                     // Render the project template
327                     List<Change> changes = computeChanges();
328                     if (!changes.isEmpty()) {
329                         monitor.beginTask("Creating project...", changes.size());
330                         try {
331                             CompositeChange composite = new CompositeChange("",
332                                     changes.toArray(new Change[changes.size()]));
333                             composite.perform(monitor);
334                         } catch (CoreException e) {
335                             AdtPlugin.log(e, null);
336                             throw new InvocationTargetException(e);
337                         } finally {
338                             monitor.done();
339                         }
340                     }
341 
342                     if (mValues.createIcon) { // TODO: Set progress
343                         generateIcons(mProject);
344                     }
345 
346                     // Render the embedded activity template template
347                     if (mValues.createActivity) {
348                         final TemplateHandler activityTemplate =
349                                 mValues.activityValues.getTemplateHandler();
350                         // We'll be merging in an activity template, but don't create
351                         // *~ backup files of the merged files (such as the manifest file)
352                         // in that case.
353                         activityTemplate.setBackupMergedFiles(false);
354                         generateActivity(template, project, monitor);
355                     }
356                 }
357             };
358 
359             NewProjectCreator.create(monitor, mProject, mValues.target, projectPopulator,
360                     mValues.isLibrary, mValues.projectLocation, mValues.workingSets);
361 
362             // For new projects, ensure that we're actually using the preferred compliance,
363             // not just the default one
364             IJavaProject javaProject = BaseProjectHelper.getJavaProject(mProject);
365             if (javaProject != null) {
366                 ProjectHelper.enforcePreferredCompilerCompliance(javaProject);
367             }
368 
369             try {
370                 mProject.refreshLocal(DEPTH_INFINITE, new NullProgressMonitor());
371             } catch (CoreException e) {
372                 AdtPlugin.log(e, null);
373             }
374 
375             List<Runnable> finalizingTasks = getFinalizingActions();
376             for (Runnable r : finalizingTasks) {
377                 r.run();
378             }
379 
380             return true;
381         } catch (Exception ioe) {
382             AdtPlugin.log(ioe, null);
383             return false;
384         }
385     }
386 
387     /**
388      * Generate custom icons into the project based on the asset studio wizard state
389      */
generateIcons(final IProject newProject)390     private void generateIcons(final IProject newProject) {
391         // Generate the custom icons
392         assert mValues.createIcon;
393         ConfigureAssetSetPage.generateIcons(newProject, mValues.iconState, false, mIconPage);
394     }
395 
396     /**
397      * Generate the activity: Pre-populate information about the project the
398      * activity needs but that we don't need to ask about when creating a new
399      * project
400      */
generateActivity(TemplateHandler projectTemplate, IProject project, IProgressMonitor monitor)401     private void generateActivity(TemplateHandler projectTemplate, IProject project,
402             IProgressMonitor monitor) throws InvocationTargetException {
403         assert mValues.createActivity;
404         NewTemplateWizardState activityValues = mValues.activityValues;
405         Map<String, Object> parameters = activityValues.parameters;
406 
407         addProjectInfo(parameters);
408 
409         parameters.put(IS_NEW_PROJECT, true);
410         parameters.put(IS_LIBRARY_PROJECT, mValues.isLibrary);
411         // Ensure that activities created as part of a new project are marked as
412         // launcher activities
413         parameters.put(IS_LAUNCHER, true);
414         TemplateHandler.addDirectoryParameters(parameters, project);
415 
416         TemplateHandler activityTemplate = activityValues.getTemplateHandler();
417         activityTemplate.setBackupMergedFiles(false);
418         List<Change> changes = activityTemplate.render(project, parameters);
419         if (!changes.isEmpty()) {
420             monitor.beginTask("Creating template...", changes.size());
421             try {
422                 CompositeChange composite = new CompositeChange("",
423                         changes.toArray(new Change[changes.size()]));
424                 composite.perform(monitor);
425             } catch (CoreException e) {
426                 AdtPlugin.log(e, null);
427                 throw new InvocationTargetException(e);
428             } finally {
429                 monitor.done();
430             }
431         }
432 
433         List<String> filesToOpen = activityTemplate.getFilesToOpen();
434         projectTemplate.getFilesToOpen().addAll(filesToOpen);
435 
436         List<Runnable> finalizingActions = activityTemplate.getFinalizingActions();
437         projectTemplate.getFinalizingActions().addAll(finalizingActions);
438     }
439 
addProjectInfo(Map<String, Object> parameters)440     private void addProjectInfo(Map<String, Object> parameters) {
441         parameters.put(ATTR_PACKAGE_NAME, mValues.packageName);
442         parameters.put(ATTR_APP_TITLE, mValues.applicationName);
443         parameters.put(ATTR_MIN_API, mValues.minSdk);
444         parameters.put(ATTR_MIN_API_LEVEL, mValues.minSdkLevel);
445         parameters.put(ATTR_TARGET_API, mValues.targetSdkLevel);
446         parameters.put(ATTR_BUILD_API, mValues.target.getVersion().getApiLevel());
447         parameters.put(ATTR_COPY_ICONS, !mValues.createIcon);
448         parameters.putAll(mValues.parameters);
449     }
450 
451     @Override
452     @NonNull
getFinalizingActions()453     protected List<Runnable> getFinalizingActions() {
454         return mValues.template.getFinalizingActions();
455     }
456 }
457