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