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