1 /* 2 * Copyright (C) 2011 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 17 package com.android.ide.eclipse.adt.internal.wizards.newproject; 18 19 import com.android.SdkConstants; 20 import com.android.annotations.Nullable; 21 import com.android.ide.common.xml.ManifestData; 22 import com.android.ide.common.xml.ManifestData.Activity; 23 import com.android.ide.eclipse.adt.AdtConstants; 24 import com.android.ide.eclipse.adt.internal.project.AndroidManifestHelper; 25 import com.android.ide.eclipse.adt.internal.sdk.Sdk; 26 import com.android.sdklib.IAndroidTarget; 27 import com.android.sdklib.internal.project.ProjectProperties; 28 import com.android.sdklib.internal.project.ProjectProperties.PropertyType; 29 import com.android.utils.Pair; 30 import com.android.xml.AndroidManifest; 31 32 import org.eclipse.core.resources.IProject; 33 import org.eclipse.core.runtime.Path; 34 import org.eclipse.core.runtime.Platform; 35 import org.eclipse.ui.IWorkingSet; 36 37 import java.io.File; 38 import java.util.ArrayList; 39 import java.util.List; 40 41 /** 42 * The {@link NewProjectWizardState} holds the state used by the various pages 43 * in the {@link NewProjectWizard} and its variations, and it can also be used 44 * to pass project information to the {@link NewProjectCreator}. 45 */ 46 public class NewProjectWizardState { 47 /** The mode to run the wizard in: creating test, or sample, or plain project */ 48 public Mode mode; 49 50 /** 51 * If true, the project should be created from an existing codebase (pointed 52 * to by the {@link #projectLocation} or in the case of sample projects, the 53 * {@link #chosenSample}. Otherwise, create a brand new project from scratch. 54 */ 55 public boolean useExisting; 56 57 /** 58 * Whether new projects should be created into the default project location 59 * (e.g. in the Eclipse workspace) or not 60 */ 61 public boolean useDefaultLocation = true; 62 63 /** The build target SDK */ 64 public IAndroidTarget target; 65 /** True if the user has manually modified the target */ 66 public boolean targetModifiedByUser; 67 68 /** The location to store projects into */ 69 public File projectLocation = new File(Platform.getLocation().toOSString()); 70 /** True if the project location name has been manually edited by the user */ 71 public boolean projectLocationModifiedByUser; 72 73 /** The name of the project */ 74 public String projectName = ""; //$NON-NLS-1$ 75 /** True if the project name has been manually edited by the user */ 76 public boolean projectNameModifiedByUser; 77 78 /** The application name */ 79 public String applicationName; 80 /** True if the application name has been manually edited by the user */ 81 public boolean applicationNameModifiedByUser; 82 83 /** The package path */ 84 public String packageName; 85 /** True if the package name has been manually edited by the user */ 86 public boolean packageNameModifiedByUser; 87 88 /** True if a new activity should be created */ 89 public boolean createActivity; 90 91 /** The name of the new activity to be created */ 92 public String activityName; 93 /** True if the activity name has been manually edited by the user */ 94 public boolean activityNameModifiedByUser; 95 96 /** The minimum SDK version to use with the project (may be null or blank) */ 97 public String minSdk; 98 /** True if the minimum SDK version has been manually edited by the user */ 99 public boolean minSdkModifiedByUser; 100 /** 101 * A list of paths to each of the available samples for the current SDK. 102 * The pair is (String: sample display name => File: sample directory). 103 * Note we want a list, not a map since we might have duplicates. 104 * */ 105 public List<Pair<String, File>> samples = new ArrayList<Pair<String, File>>(); 106 /** Path to the currently chosen sample */ 107 public File chosenSample; 108 109 /** The name of the source folder, relative to the project root */ 110 public String sourceFolder = SdkConstants.FD_SOURCES; 111 /** The set of chosen working sets to use when creating the project */ 112 public IWorkingSet[] workingSets = new IWorkingSet[0]; 113 114 /** 115 * A reference to a different project that the current test project will be 116 * testing. 117 */ 118 public IProject testedProject; 119 /** 120 * If true, this test project should be testing itself, otherwise it will be 121 * testing the project pointed to by {@link #testedProject}. 122 */ 123 public boolean testingSelf; 124 125 // NOTE: These apply only to creating paired projects; when isTest is true 126 // we're using 127 // the normal fields above 128 /** 129 * If true, create a test project along with this plain project which will 130 * be testing the plain project. (This flag only applies when creating 131 * normal projects.) 132 */ 133 public boolean createPairProject; 134 /** 135 * The application name of the test application (only applies when 136 * {@link #createPairProject} is true) 137 */ 138 public String testApplicationName; 139 /** 140 * True if the testing application name has been modified by the user (only 141 * applies when {@link #createPairProject} is true) 142 */ 143 public boolean testApplicationNameModified; 144 /** 145 * The package name of the test application (only applies when 146 * {@link #createPairProject} is true) 147 */ 148 public String testPackageName; 149 /** 150 * True if the testing package name has been modified by the user (only 151 * applies when {@link #createPairProject} is true) 152 */ 153 public boolean testPackageModified; 154 /** 155 * The project name of the test project (only applies when 156 * {@link #createPairProject} is true) 157 */ 158 public String testProjectName; 159 /** 160 * True if the testing project name has been modified by the user (only 161 * applies when {@link #createPairProject} is true) 162 */ 163 public boolean testProjectModified; 164 /** Package name of the tested app */ 165 public String testTargetPackageName; 166 167 /** 168 * Copy project into workspace? This flag only applies when importing 169 * projects (creating projects from existing source) 170 */ 171 public boolean copyIntoWorkspace; 172 173 /** 174 * List of projects to be imported. Null if not importing projects. 175 */ 176 @Nullable 177 public List<ImportedProject> importProjects; 178 179 /** 180 * Creates a new {@link NewProjectWizardState} 181 * 182 * @param mode the mode to run the wizard in 183 */ 184 public NewProjectWizardState(Mode mode) { 185 this.mode = mode; 186 if (mode == Mode.SAMPLE) { 187 useExisting = true; 188 } else if (mode == Mode.TEST) { 189 createActivity = false; 190 } 191 } 192 193 /** 194 * Extract information (package name, application name, minimum SDK etc) from 195 * the given Android project. 196 * 197 * @param path the path to the project to extract information from 198 */ 199 public void extractFromAndroidManifest(Path path) { 200 String osPath = path.append(SdkConstants.FN_ANDROID_MANIFEST_XML).toOSString(); 201 if (!(new File(osPath).exists())) { 202 return; 203 } 204 205 ManifestData manifestData = AndroidManifestHelper.parseForData(osPath); 206 if (manifestData == null) { 207 return; 208 } 209 210 String newPackageName = null; 211 Activity activity = null; 212 String newActivityName = null; 213 String minSdkVersion = null; 214 try { 215 newPackageName = manifestData.getPackage(); 216 minSdkVersion = manifestData.getMinSdkVersionString(); 217 218 // try to get the first launcher activity. If none, just take the first activity. 219 activity = manifestData.getLauncherActivity(); 220 if (activity == null) { 221 Activity[] activities = manifestData.getActivities(); 222 if (activities != null && activities.length > 0) { 223 activity = activities[0]; 224 } 225 } 226 } catch (Exception e) { 227 // ignore exceptions 228 } 229 230 if (newPackageName != null && newPackageName.length() > 0) { 231 packageName = newPackageName; 232 } 233 234 if (activity != null) { 235 newActivityName = AndroidManifest.extractActivityName(activity.getName(), 236 newPackageName); 237 } 238 239 if (newActivityName != null && newActivityName.length() > 0) { 240 activityName = newActivityName; 241 // we are "importing" an existing activity, not creating a new one 242 createActivity = false; 243 244 // If project name and application names are empty, use the activity 245 // name as a default. If the activity name has dots, it's a part of a 246 // package specification and only the last identifier must be used. 247 if (newActivityName.indexOf('.') != -1) { 248 String[] ids = newActivityName.split(AdtConstants.RE_DOT); 249 newActivityName = ids[ids.length - 1]; 250 } 251 if (projectName == null || projectName.length() == 0 || 252 !projectNameModifiedByUser) { 253 projectName = newActivityName; 254 projectNameModifiedByUser = false; 255 } 256 if (applicationName == null || applicationName.length() == 0 || 257 !applicationNameModifiedByUser) { 258 applicationNameModifiedByUser = false; 259 applicationName = newActivityName; 260 } 261 } else { 262 activityName = ""; //$NON-NLS-1$ 263 264 // There is no activity name to use to fill in the project and application 265 // name. However if there's a package name, we can use this as a base. 266 if (newPackageName != null && newPackageName.length() > 0) { 267 // Package name is a java identifier, so it's most suitable for 268 // an application name. 269 270 if (applicationName == null || applicationName.length() == 0 || 271 !applicationNameModifiedByUser) { 272 applicationName = newPackageName; 273 } 274 275 // For the project name, remove any dots 276 newPackageName = newPackageName.replace('.', '_'); 277 if (projectName == null || projectName.length() == 0 || 278 !projectNameModifiedByUser) { 279 projectName = newPackageName; 280 } 281 282 } 283 } 284 285 if (mode == Mode.ANY && useExisting) { 286 updateSdkTargetToMatchProject(path.toFile()); 287 } 288 289 minSdk = minSdkVersion; 290 minSdkModifiedByUser = false; 291 } 292 293 /** 294 * Try to find an SDK Target that matches the current MinSdkVersion. 295 * 296 * There can be multiple targets with the same sdk api version, so don't change 297 * it if it's already at the right version. Otherwise pick the first target 298 * that matches. 299 */ 300 public void updateSdkTargetToMatchMinSdkVersion() { 301 IAndroidTarget currentTarget = target; 302 if (currentTarget != null && currentTarget.getVersion().equals(minSdk)) { 303 return; 304 } 305 306 Sdk sdk = Sdk.getCurrent(); 307 if (sdk != null) { 308 IAndroidTarget[] targets = sdk.getTargets(); 309 for (IAndroidTarget t : targets) { 310 if (t.getVersion().equals(minSdk)) { 311 target = t; 312 return; 313 } 314 } 315 } 316 } 317 318 /** 319 * Updates the SDK to reflect the SDK required by the project at the given 320 * location 321 * 322 * @param location the location of the project 323 */ 324 public void updateSdkTargetToMatchProject(File location) { 325 // Select the target matching the manifest's sdk or build properties, if any 326 IAndroidTarget foundTarget = null; 327 // This is the target currently in the UI 328 IAndroidTarget currentTarget = target; 329 String projectPath = location.getPath(); 330 331 // If there's a current target defined, we do not allow to change it when 332 // operating in the create-from-sample mode -- since the available sample list 333 // is tied to the current target, so changing it would invalidate the project we're 334 // trying to load in the first place. 335 if (!targetModifiedByUser) { 336 ProjectProperties p = ProjectProperties.load(projectPath, 337 PropertyType.PROJECT); 338 if (p != null) { 339 String v = p.getProperty(ProjectProperties.PROPERTY_TARGET); 340 IAndroidTarget desiredTarget = Sdk.getCurrent().getTargetFromHashString(v); 341 // We can change the current target if: 342 // - we found a new desired target 343 // - there is no current target 344 // - or the current target can't run the desired target 345 if (desiredTarget != null && 346 (currentTarget == null || !desiredTarget.canRunOn(currentTarget))) { 347 foundTarget = desiredTarget; 348 } 349 } 350 351 Sdk sdk = Sdk.getCurrent(); 352 IAndroidTarget[] targets = null; 353 if (sdk != null) { 354 targets = sdk.getTargets(); 355 } 356 if (targets == null) { 357 targets = new IAndroidTarget[0]; 358 } 359 360 if (foundTarget == null && minSdk != null) { 361 // Otherwise try to match the requested min-sdk-version if we find an 362 // exact match, regardless of the currently selected target. 363 for (IAndroidTarget existingTarget : targets) { 364 if (existingTarget != null && 365 existingTarget.getVersion().equals(minSdk)) { 366 foundTarget = existingTarget; 367 break; 368 } 369 } 370 } 371 372 if (foundTarget == null) { 373 // Or last attempt, try to match a sample project location and use it 374 // if we find an exact match, regardless of the currently selected target. 375 for (IAndroidTarget existingTarget : targets) { 376 if (existingTarget != null && 377 projectPath.startsWith(existingTarget.getLocation())) { 378 foundTarget = existingTarget; 379 break; 380 } 381 } 382 } 383 } 384 385 if (foundTarget != null) { 386 target = foundTarget; 387 } 388 } 389 390 /** 391 * Type of project being offered/created by the wizard 392 */ 393 public enum Mode { 394 /** Create a sample project. Testing options are not presented. */ 395 SAMPLE, 396 397 /** 398 * Create a test project, either testing itself or some other project. 399 * Note that even if in the {@link #ANY} mode, a test project can be 400 * created as a *paired* project with the main project, so this flag 401 * only means that we are creating *just* a test project 402 */ 403 TEST, 404 405 /** 406 * Create an Android project, which can be a plain project, optionally 407 * with a paired test project, or a sample project (the first page 408 * contains toggles for choosing which 409 */ 410 ANY; 411 } 412 } 413