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