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