Home | History | Annotate | Download | only in templates
      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
     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
    110     public void addPages() {
    111         super.addPages();
    112         addPage(mMainPage);
    113         addPage(mContentsPage);
    114         addPage(mActivityPage);
    115     }
    116 
    117     @Override
    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
    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
    239     protected IProject getProject() {
    240         return mProject;
    241     }
    242 
    243     @Override
    244     @NonNull
    245     protected List<String> getFilesToOpen() {
    246         return mValues.template.getFilesToOpen();
    247     }
    248 
    249     @VisibleForTesting
    250     NewProjectWizardState getValues() {
    251         return mValues;
    252     }
    253 
    254     @VisibleForTesting
    255     void setValues(NewProjectWizardState values) {
    256         mValues = values;
    257     }
    258 
    259     @Override
    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
    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      */
    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      */
    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 
    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
    453     protected List<Runnable> getFinalizingActions() {
    454         return mValues.template.getFinalizingActions();
    455     }
    456 }
    457