aboutsummaryrefslogtreecommitdiff
path: root/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/project
diff options
context:
space:
mode:
Diffstat (limited to 'eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/project')
-rw-r--r--eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/project/AndroidClasspathContainer.java69
-rw-r--r--eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/project/AndroidClasspathContainerInitializer.java823
-rw-r--r--eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/project/AndroidClasspathContainerPage.java200
-rw-r--r--eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/project/AndroidExportNature.java96
-rw-r--r--eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/project/AndroidManifestHelper.java203
-rw-r--r--eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/project/AndroidNature.java299
-rw-r--r--eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/project/ApkInstallManager.java278
-rw-r--r--eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/project/BaseClasspathContainerInitializer.java103
-rw-r--r--eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/project/BaseProjectHelper.java527
-rw-r--r--eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/project/ExportHelper.java448
-rw-r--r--eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/project/FixLaunchConfig.java156
-rw-r--r--eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/project/FolderDecorator.java109
-rw-r--r--eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/project/LibraryClasspathContainerInitializer.java641
-rw-r--r--eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/project/ProjectChooserHelper.java304
-rw-r--r--eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/project/ProjectHelper.java1153
-rw-r--r--eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/project/SupportLibraryHelper.java176
-rw-r--r--eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/project/XmlErrorHandler.java175
17 files changed, 5760 insertions, 0 deletions
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/project/AndroidClasspathContainer.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/project/AndroidClasspathContainer.java
new file mode 100644
index 000000000..475dd5a44
--- /dev/null
+++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/project/AndroidClasspathContainer.java
@@ -0,0 +1,69 @@
+/*
+ * Copyright (C) 2007 The Android Open Source Project
+ *
+ * Licensed under the Eclipse Public License, Version 1.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.eclipse.org/org/documents/epl-v10.php
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.ide.eclipse.adt.internal.project;
+
+import org.eclipse.core.runtime.IPath;
+import org.eclipse.jdt.core.IClasspathContainer;
+import org.eclipse.jdt.core.IClasspathEntry;
+
+/**
+ * Classpath container for the Android projects.
+ * This supports both the System classpath and the library dependencies.
+ */
+class AndroidClasspathContainer implements IClasspathContainer {
+
+ private final IClasspathEntry[] mClasspathEntry;
+ private final IPath mContainerPath;
+ private final String mName;
+ private final int mKind;
+
+ /**
+ * Constructs the container with the {@link IClasspathEntry} representing the android
+ * framework jar file and the container id
+ * @param entries the entries representing the android framework and optional libraries.
+ * @param path the path containing the classpath container id.
+ * @param name the name of the container to display.
+ * @param the container kind. Can be {@link IClasspathContainer#K_DEFAULT_SYSTEM} or
+ * {@link IClasspathContainer#K_APPLICATION}
+ */
+ AndroidClasspathContainer(IClasspathEntry[] entries, IPath path, String name, int kind) {
+ mClasspathEntry = entries;
+ mContainerPath = path;
+ mName = name;
+ mKind = kind;
+ }
+
+ @Override
+ public IClasspathEntry[] getClasspathEntries() {
+ return mClasspathEntry;
+ }
+
+ @Override
+ public String getDescription() {
+ return mName;
+ }
+
+ @Override
+ public int getKind() {
+ return mKind;
+ }
+
+ @Override
+ public IPath getPath() {
+ return mContainerPath;
+ }
+}
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/project/AndroidClasspathContainerInitializer.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/project/AndroidClasspathContainerInitializer.java
new file mode 100644
index 000000000..f9382c5ae
--- /dev/null
+++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/project/AndroidClasspathContainerInitializer.java
@@ -0,0 +1,823 @@
+/*
+ * Copyright (C) 2007 The Android Open Source Project
+ *
+ * Licensed under the Eclipse Public License, Version 1.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.eclipse.org/org/documents/epl-v10.php
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.ide.eclipse.adt.internal.project;
+
+import com.android.SdkConstants;
+import com.android.ide.common.sdk.LoadStatus;
+import com.android.ide.eclipse.adt.AdtConstants;
+import com.android.ide.eclipse.adt.AdtPlugin;
+import com.android.ide.eclipse.adt.internal.sdk.ProjectState;
+import com.android.ide.eclipse.adt.internal.sdk.Sdk;
+import com.android.sdklib.AndroidVersion;
+import com.android.sdklib.IAndroidTarget;
+import com.android.sdklib.IAndroidTarget.IOptionalLibrary;
+import com.google.common.collect.Maps;
+import com.google.common.io.Closeables;
+
+import org.eclipse.core.resources.IProject;
+import org.eclipse.core.resources.IWorkspaceRoot;
+import org.eclipse.core.resources.ResourcesPlugin;
+import org.eclipse.core.runtime.CoreException;
+import org.eclipse.core.runtime.FileLocator;
+import org.eclipse.core.runtime.IPath;
+import org.eclipse.core.runtime.NullProgressMonitor;
+import org.eclipse.core.runtime.Path;
+import org.eclipse.core.runtime.Platform;
+import org.eclipse.jdt.core.IAccessRule;
+import org.eclipse.jdt.core.IClasspathAttribute;
+import org.eclipse.jdt.core.IClasspathContainer;
+import org.eclipse.jdt.core.IClasspathEntry;
+import org.eclipse.jdt.core.IJavaModel;
+import org.eclipse.jdt.core.IJavaProject;
+import org.eclipse.jdt.core.JavaCore;
+import org.eclipse.jdt.core.JavaModelException;
+import org.osgi.framework.Bundle;
+
+import java.io.File;
+import java.io.IOException;
+import java.io.InputStream;
+import java.net.URI;
+import java.net.URISyntaxException;
+import java.net.URL;
+import java.net.URLConnection;
+import java.util.ArrayList;
+import java.util.HashSet;
+import java.util.Map;
+import java.util.regex.Pattern;
+
+/**
+ * Classpath container initializer responsible for binding {@link AndroidClasspathContainer} to
+ * {@link IProject}s. This removes the hard-coded path to the android.jar.
+ */
+public class AndroidClasspathContainerInitializer extends BaseClasspathContainerInitializer {
+
+ public static final String NULL_API_URL = "<null>"; //$NON-NLS-1$
+
+ public static final String SOURCES_ZIP = "/sources.zip"; //$NON-NLS-1$
+
+ public static final String COM_ANDROID_IDE_ECLIPSE_ADT_SOURCE =
+ "com.android.ide.eclipse.source"; //$NON-NLS-1$
+
+ private static final String ANDROID_API_REFERENCE =
+ "http://developer.android.com/reference/"; //$NON-NLS-1$
+
+ private final static String PROPERTY_ANDROID_API = "androidApi"; //$NON-NLS-1$
+
+ private final static String PROPERTY_ANDROID_SOURCE = "androidSource"; //$NON-NLS-1$
+
+ /** path separator to store multiple paths in a single property. This is guaranteed to not
+ * be in a path.
+ */
+ private final static String PATH_SEPARATOR = "\u001C"; //$NON-NLS-1$
+
+ private final static String PROPERTY_CONTAINER_CACHE = "androidContainerCache"; //$NON-NLS-1$
+ private final static String PROPERTY_TARGET_NAME = "androidTargetCache"; //$NON-NLS-1$
+ private final static String CACHE_VERSION = "01"; //$NON-NLS-1$
+ private final static String CACHE_VERSION_SEP = CACHE_VERSION + PATH_SEPARATOR;
+
+ private final static int CACHE_INDEX_JAR = 0;
+ private final static int CACHE_INDEX_SRC = 1;
+ private final static int CACHE_INDEX_DOCS_URI = 2;
+ private final static int CACHE_INDEX_OPT_DOCS_URI = 3;
+ private final static int CACHE_INDEX_ADD_ON_START = CACHE_INDEX_OPT_DOCS_URI;
+
+ public AndroidClasspathContainerInitializer() {
+ // pass
+ }
+
+ /**
+ * Binds a classpath container to a {@link IClasspathContainer} for a given project,
+ * or silently fails if unable to do so.
+ * @param containerPath the container path that is the container id.
+ * @param project the project to bind
+ */
+ @Override
+ public void initialize(IPath containerPath, IJavaProject project) throws CoreException {
+ if (AdtConstants.CONTAINER_FRAMEWORK.equals(containerPath.toString())) {
+ IClasspathContainer container = allocateAndroidContainer(project);
+ if (container != null) {
+ JavaCore.setClasspathContainer(new Path(AdtConstants.CONTAINER_FRAMEWORK),
+ new IJavaProject[] { project },
+ new IClasspathContainer[] { container },
+ new NullProgressMonitor());
+ }
+ }
+ }
+
+ /**
+ * Updates the {@link IJavaProject} objects with new android framework container. This forces
+ * JDT to recompile them.
+ * @param androidProjects the projects to update.
+ * @return <code>true</code> if success, <code>false</code> otherwise.
+ */
+ static boolean updateProjects(IJavaProject[] androidProjects) {
+ try {
+ // Allocate a new AndroidClasspathContainer, and associate it to the android framework
+ // container id for each projects.
+ // By providing a new association between a container id and a IClasspathContainer,
+ // this forces the JDT to query the IClasspathContainer for new IClasspathEntry (with
+ // IClasspathContainer#getClasspathEntries()), and therefore force recompilation of
+ // the projects.
+ int projectCount = androidProjects.length;
+
+ IClasspathContainer[] containers = new IClasspathContainer[projectCount];
+ for (int i = 0 ; i < projectCount; i++) {
+ containers[i] = allocateAndroidContainer(androidProjects[i]);
+ }
+
+ // give each project their new container in one call.
+ JavaCore.setClasspathContainer(
+ new Path(AdtConstants.CONTAINER_FRAMEWORK),
+ androidProjects, containers, new NullProgressMonitor());
+
+ return true;
+ } catch (JavaModelException e) {
+ return false;
+ }
+ }
+
+ /**
+ * Allocates and returns an {@link AndroidClasspathContainer} object with the proper
+ * path to the framework jar file.
+ * @param javaProject The java project that will receive the container.
+ */
+ private static IClasspathContainer allocateAndroidContainer(IJavaProject javaProject) {
+ final IProject iProject = javaProject.getProject();
+
+ String markerMessage = null;
+ boolean outputToConsole = true;
+ IAndroidTarget target = null;
+
+ try {
+ AdtPlugin plugin = AdtPlugin.getDefault();
+ if (plugin == null) { // This is totally weird, but I've seen it happen!
+ return null;
+ }
+
+ synchronized (Sdk.getLock()) {
+ boolean sdkIsLoaded = plugin.getSdkLoadStatus() == LoadStatus.LOADED;
+
+ // check if the project has a valid target.
+ ProjectState state = Sdk.getProjectState(iProject);
+ if (state == null) {
+ // looks like the project state (project.properties) couldn't be read!
+ markerMessage = String.format(
+ "Project has no %1$s file! Edit the project properties to set one.",
+ SdkConstants.FN_PROJECT_PROPERTIES);
+ } else {
+ // this might be null if the sdk is not yet loaded.
+ target = state.getTarget();
+
+ // if we are loaded and the target is non null, we create a valid
+ // ClassPathContainer
+ if (sdkIsLoaded && target != null) {
+ // check the renderscript support mode. If support mode is enabled,
+ // target API must be 18+
+ if (!state.getRenderScriptSupportMode() ||
+ target.getVersion().getApiLevel() >= 18) {
+ // first make sure the target has loaded its data
+ Sdk.getCurrent().checkAndLoadTargetData(target, null /*project*/);
+
+ String targetName = target.getClasspathName();
+
+ return new AndroidClasspathContainer(
+ createClasspathEntries(iProject, target, targetName),
+ new Path(AdtConstants.CONTAINER_FRAMEWORK),
+ targetName,
+ IClasspathContainer.K_DEFAULT_SYSTEM);
+ } else {
+ markerMessage = "Renderscript support mode requires compilation target API to be 18+.";
+ }
+ } else {
+ // In case of error, we'll try different thing to provide the best error message
+ // possible.
+ // Get the project's target's hash string (if it exists)
+ String hashString = state.getTargetHashString();
+
+ if (hashString == null || hashString.length() == 0) {
+ // if there is no hash string we only show this if the SDK is loaded.
+ // For a project opened at start-up with no target, this would be displayed
+ // twice, once when the project is opened, and once after the SDK has
+ // finished loading.
+ // By testing the sdk is loaded, we only show this once in the console.
+ if (sdkIsLoaded) {
+ markerMessage = String.format(
+ "Project has no target set. Edit the project properties to set one.");
+ }
+ } else if (sdkIsLoaded) {
+ markerMessage = String.format(
+ "Unable to resolve target '%s'", hashString);
+ } else {
+ // this is the case where there is a hashString but the SDK is not yet
+ // loaded and therefore we can't get the target yet.
+ // We check if there is a cache of the needed information.
+ AndroidClasspathContainer container = getContainerFromCache(iProject,
+ target);
+
+ if (container == null) {
+ // either the cache was wrong (ie folder does not exists anymore), or
+ // there was no cache. In this case we need to make sure the project
+ // is resolved again after the SDK is loaded.
+ plugin.setProjectToResolve(javaProject);
+
+ markerMessage = String.format(
+ "Unable to resolve target '%s' until the SDK is loaded.",
+ hashString);
+
+ // let's not log this one to the console as it will happen at
+ // every boot, and it's expected. (we do keep the error marker though).
+ outputToConsole = false;
+
+ } else {
+ // we created a container from the cache, so we register the project
+ // to be checked for cache validity once the SDK is loaded
+ plugin.setProjectToCheck(javaProject);
+
+ // and return the container
+ return container;
+ }
+ }
+ }
+ }
+
+ // return a dummy container to replace the one we may have had before.
+ // It'll be replaced by the real when if/when the target is resolved if/when the
+ // SDK finishes loading.
+ return new IClasspathContainer() {
+ @Override
+ public IClasspathEntry[] getClasspathEntries() {
+ return new IClasspathEntry[0];
+ }
+
+ @Override
+ public String getDescription() {
+ return "Unable to get system library for the project";
+ }
+
+ @Override
+ public int getKind() {
+ return IClasspathContainer.K_DEFAULT_SYSTEM;
+ }
+
+ @Override
+ public IPath getPath() {
+ return null;
+ }
+ };
+ }
+ } finally {
+ processError(iProject, markerMessage, AdtConstants.MARKER_TARGET, outputToConsole);
+ }
+ }
+
+ /**
+ * Creates and returns an array of {@link IClasspathEntry} objects for the android
+ * framework and optional libraries.
+ * <p/>This references the OS path to the android.jar and the
+ * java doc directory. This is dynamically created when a project is opened,
+ * and never saved in the project itself, so there's no risk of storing an
+ * obsolete path.
+ * The method also stores the paths used to create the entries in the project persistent
+ * properties. A new {@link AndroidClasspathContainer} can be created from the stored path
+ * using the {@link #getContainerFromCache(IProject)} method.
+ * @param project
+ * @param target The target that contains the libraries.
+ * @param targetName
+ */
+ private static IClasspathEntry[] createClasspathEntries(IProject project,
+ IAndroidTarget target, String targetName) {
+
+ // get the path from the target
+ String[] paths = getTargetPaths(target);
+
+ // create the classpath entry from the paths
+ IClasspathEntry[] entries = createClasspathEntriesFromPaths(paths, target);
+
+ // paths now contains all the path required to recreate the IClasspathEntry with no
+ // target info. We encode them in a single string, with each path separated by
+ // OS path separator.
+ StringBuilder sb = new StringBuilder(CACHE_VERSION);
+ for (String p : paths) {
+ sb.append(PATH_SEPARATOR);
+ sb.append(p);
+ }
+
+ // store this in a project persistent property
+ ProjectHelper.saveStringProperty(project, PROPERTY_CONTAINER_CACHE, sb.toString());
+ ProjectHelper.saveStringProperty(project, PROPERTY_TARGET_NAME, targetName);
+
+ return entries;
+ }
+
+ /**
+ * Generates an {@link AndroidClasspathContainer} from the project cache, if possible.
+ */
+ private static AndroidClasspathContainer getContainerFromCache(IProject project,
+ IAndroidTarget target) {
+ // get the cached info from the project persistent properties.
+ String cache = ProjectHelper.loadStringProperty(project, PROPERTY_CONTAINER_CACHE);
+ String targetNameCache = ProjectHelper.loadStringProperty(project, PROPERTY_TARGET_NAME);
+ if (cache == null || targetNameCache == null) {
+ return null;
+ }
+
+ // the first 2 chars must match CACHE_VERSION. The 3rd char is the normal separator.
+ if (cache.startsWith(CACHE_VERSION_SEP) == false) {
+ return null;
+ }
+
+ cache = cache.substring(CACHE_VERSION_SEP.length());
+
+ // the cache contains multiple paths, separated by a character guaranteed to not be in
+ // the path (\u001C).
+ // The first 3 are for android.jar (jar, source, doc), the rest are for the optional
+ // libraries and should contain at least one doc and a jar (if there are any libraries).
+ // Therefore, the path count should be 3 or 5+
+ String[] paths = cache.split(Pattern.quote(PATH_SEPARATOR));
+ if (paths.length < 3 || paths.length == 4) {
+ return null;
+ }
+
+ // now we check the paths actually exist.
+ // There's an exception: If the source folder for android.jar does not exist, this is
+ // not a problem, so we skip it.
+ // Also paths[CACHE_INDEX_DOCS_URI] is a URI to the javadoc, so we test it a
+ // bit differently.
+ try {
+ if (new File(paths[CACHE_INDEX_JAR]).exists() == false ||
+ new File(new URI(paths[CACHE_INDEX_DOCS_URI])).exists() == false) {
+ return null;
+ }
+
+ // check the path for the add-ons, if they exist.
+ if (paths.length > CACHE_INDEX_ADD_ON_START) {
+
+ // check the docs path separately from the rest of the paths as it's a URI.
+ if (new File(new URI(paths[CACHE_INDEX_OPT_DOCS_URI])).exists() == false) {
+ return null;
+ }
+
+ // now just check the remaining paths.
+ for (int i = CACHE_INDEX_ADD_ON_START + 1; i < paths.length; i++) {
+ String path = paths[i];
+ if (path.length() > 0) {
+ File f = new File(path);
+ if (f.exists() == false) {
+ return null;
+ }
+ }
+ }
+ }
+ } catch (URISyntaxException e) {
+ return null;
+ }
+
+ IClasspathEntry[] entries = createClasspathEntriesFromPaths(paths, target);
+
+ return new AndroidClasspathContainer(entries,
+ new Path(AdtConstants.CONTAINER_FRAMEWORK),
+ targetNameCache, IClasspathContainer.K_DEFAULT_SYSTEM);
+ }
+
+ /**
+ * Generates an array of {@link IClasspathEntry} from a set of paths.
+ * @see #getTargetPaths(IAndroidTarget)
+ */
+ private static IClasspathEntry[] createClasspathEntriesFromPaths(String[] paths,
+ IAndroidTarget target) {
+ ArrayList<IClasspathEntry> list = new ArrayList<IClasspathEntry>();
+
+ // First, we create the IClasspathEntry for the framework.
+ // now add the android framework to the class path.
+ // create the path object.
+ IPath androidLib = new Path(paths[CACHE_INDEX_JAR]);
+
+ IPath androidSrc = null;
+ String androidSrcOsPath = null;
+ IWorkspaceRoot root = ResourcesPlugin.getWorkspace().getRoot();
+ if (target != null) {
+ androidSrcOsPath =
+ ProjectHelper.loadStringProperty(root, getAndroidSourceProperty(target));
+ }
+ if (androidSrcOsPath != null && androidSrcOsPath.trim().length() > 0) {
+ androidSrc = new Path(androidSrcOsPath);
+ }
+ if (androidSrc == null) {
+ androidSrc = new Path(paths[CACHE_INDEX_SRC]);
+ File androidSrcFile = new File(paths[CACHE_INDEX_SRC]);
+ if (!androidSrcFile.isDirectory()) {
+ androidSrc = null;
+ }
+ }
+
+ if (androidSrc == null && target != null) {
+ Bundle bundle = getSourceBundle();
+
+ if (bundle != null) {
+ AndroidVersion version = target.getVersion();
+ String apiString = version.getApiString();
+ String sourcePath = apiString + SOURCES_ZIP;
+ URL sourceURL = bundle.getEntry(sourcePath);
+ if (sourceURL != null) {
+ URL url = null;
+ try {
+ url = FileLocator.resolve(sourceURL);
+ } catch (IOException ignore) {
+ }
+ if (url != null) {
+ androidSrcOsPath = url.getFile();
+ if (new File(androidSrcOsPath).isFile()) {
+ androidSrc = new Path(androidSrcOsPath);
+ }
+ }
+ }
+ }
+ }
+
+ // create the java doc link.
+ String androidApiURL = ProjectHelper.loadStringProperty(root, PROPERTY_ANDROID_API);
+ String apiURL = null;
+ if (androidApiURL != null && testURL(androidApiURL)) {
+ apiURL = androidApiURL;
+ } else {
+ if (testURL(paths[CACHE_INDEX_DOCS_URI])) {
+ apiURL = paths[CACHE_INDEX_DOCS_URI];
+ } else if (testURL(ANDROID_API_REFERENCE)) {
+ apiURL = ANDROID_API_REFERENCE;
+ }
+ }
+
+ IClasspathAttribute[] attributes = null;
+ if (apiURL != null && !NULL_API_URL.equals(apiURL)) {
+ IClasspathAttribute cpAttribute = JavaCore.newClasspathAttribute(
+ IClasspathAttribute.JAVADOC_LOCATION_ATTRIBUTE_NAME, apiURL);
+ attributes = new IClasspathAttribute[] {
+ cpAttribute
+ };
+ }
+ // create the access rule to restrict access to classes in
+ // com.android.internal
+ IAccessRule accessRule = JavaCore.newAccessRule(new Path("com/android/internal/**"), //$NON-NLS-1$
+ IAccessRule.K_NON_ACCESSIBLE);
+
+ IClasspathEntry frameworkClasspathEntry = JavaCore.newLibraryEntry(androidLib,
+ androidSrc, // source attachment path
+ null, // default source attachment root path.
+ new IAccessRule[] { accessRule },
+ attributes,
+ false // not exported.
+ );
+
+ list.add(frameworkClasspathEntry);
+
+ // now deal with optional libraries
+ if (paths.length >= 5) {
+ String docPath = paths[CACHE_INDEX_OPT_DOCS_URI];
+ int i = 4;
+ while (i < paths.length) {
+ Path jarPath = new Path(paths[i++]);
+
+ attributes = null;
+ if (docPath.length() > 0) {
+ attributes = new IClasspathAttribute[] {
+ JavaCore.newClasspathAttribute(
+ IClasspathAttribute.JAVADOC_LOCATION_ATTRIBUTE_NAME, docPath)
+ };
+ }
+
+ IClasspathEntry entry = JavaCore.newLibraryEntry(
+ jarPath,
+ null, // source attachment path
+ null, // default source attachment root path.
+ null,
+ attributes,
+ false // not exported.
+ );
+ list.add(entry);
+ }
+ }
+
+ if (apiURL != null) {
+ ProjectHelper.saveStringProperty(root, PROPERTY_ANDROID_API, apiURL);
+ }
+ if (androidSrc != null && target != null) {
+ ProjectHelper.saveStringProperty(root, getAndroidSourceProperty(target),
+ androidSrc.toOSString());
+ }
+ return list.toArray(new IClasspathEntry[list.size()]);
+ }
+
+ private static Bundle getSourceBundle() {
+ String bundleId = System.getProperty(COM_ANDROID_IDE_ECLIPSE_ADT_SOURCE,
+ COM_ANDROID_IDE_ECLIPSE_ADT_SOURCE);
+ Bundle bundle = Platform.getBundle(bundleId);
+ return bundle;
+ }
+
+ private static String getAndroidSourceProperty(IAndroidTarget target) {
+ if (target == null) {
+ return null;
+ }
+ String androidSourceProperty = PROPERTY_ANDROID_SOURCE + "_"
+ + target.getVersion().getApiString();
+ return androidSourceProperty;
+ }
+
+ /**
+ * Cache results for testURL: Some are expensive to compute, and this is
+ * called repeatedly (perhaps for each open project)
+ */
+ private static final Map<String, Boolean> sRecentUrlValidCache =
+ Maps.newHashMapWithExpectedSize(4);
+
+ @SuppressWarnings("resource") // Eclipse does not handle Closeables#closeQuietly
+ private static boolean testURL(String androidApiURL) {
+ Boolean cached = sRecentUrlValidCache.get(androidApiURL);
+ if (cached != null) {
+ return cached.booleanValue();
+ }
+ boolean valid = false;
+ InputStream is = null;
+ try {
+ URL testURL = new URL(androidApiURL);
+ URLConnection connection = testURL.openConnection();
+ // Only try for 5 seconds (though some implementations ignore this flag)
+ connection.setConnectTimeout(5000);
+ connection.setReadTimeout(5000);
+ is = connection.getInputStream();
+ valid = true;
+ } catch (Exception ignore) {
+ } finally {
+ Closeables.closeQuietly(is);
+ }
+
+ sRecentUrlValidCache.put(androidApiURL, valid);
+
+ return valid;
+ }
+
+ /**
+ * Checks the projects' caches. If the cache was valid, the project is removed from the list.
+ * @param projects the list of projects to check.
+ */
+ public static void checkProjectsCache(ArrayList<IJavaProject> projects) {
+ Sdk currentSdk = Sdk.getCurrent();
+ int i = 0;
+ projectLoop: while (i < projects.size()) {
+ IJavaProject javaProject = projects.get(i);
+ IProject iProject = javaProject.getProject();
+
+ // check if the project is opened
+ if (iProject.isOpen() == false) {
+ // remove from the list
+ // we do not increment i in this case.
+ projects.remove(i);
+
+ continue;
+ }
+
+ // project that have been resolved before the sdk was loaded
+ // will have a ProjectState where the IAndroidTarget is null
+ // so we load the target now that the SDK is loaded.
+ IAndroidTarget target = currentSdk.loadTargetAndBuildTools(
+ Sdk.getProjectState(iProject));
+ if (target == null) {
+ // this is really not supposed to happen. This would mean there are cached paths,
+ // but project.properties was deleted. Keep the project in the list to force
+ // a resolve which will display the error.
+ i++;
+ continue;
+ }
+
+ String[] targetPaths = getTargetPaths(target);
+
+ // now get the cached paths
+ String cache = ProjectHelper.loadStringProperty(iProject, PROPERTY_CONTAINER_CACHE);
+ if (cache == null) {
+ // this should not happen. We'll force resolve again anyway.
+ i++;
+ continue;
+ }
+
+ String[] cachedPaths = cache.split(Pattern.quote(PATH_SEPARATOR));
+ if (cachedPaths.length < 3 || cachedPaths.length == 4) {
+ // paths length is wrong. simply resolve the project again
+ i++;
+ continue;
+ }
+
+ // Now we compare the paths. The first 4 can be compared directly.
+ // because of case sensitiveness we need to use File objects
+
+ if (targetPaths.length != cachedPaths.length) {
+ // different paths, force resolve again.
+ i++;
+ continue;
+ }
+
+ // compare the main paths (android.jar, main sources, main javadoc)
+ if (new File(targetPaths[CACHE_INDEX_JAR]).equals(
+ new File(cachedPaths[CACHE_INDEX_JAR])) == false ||
+ new File(targetPaths[CACHE_INDEX_SRC]).equals(
+ new File(cachedPaths[CACHE_INDEX_SRC])) == false ||
+ new File(targetPaths[CACHE_INDEX_DOCS_URI]).equals(
+ new File(cachedPaths[CACHE_INDEX_DOCS_URI])) == false) {
+ // different paths, force resolve again.
+ i++;
+ continue;
+ }
+
+ if (cachedPaths.length > CACHE_INDEX_OPT_DOCS_URI) {
+ // compare optional libraries javadoc
+ if (new File(targetPaths[CACHE_INDEX_OPT_DOCS_URI]).equals(
+ new File(cachedPaths[CACHE_INDEX_OPT_DOCS_URI])) == false) {
+ // different paths, force resolve again.
+ i++;
+ continue;
+ }
+
+ // testing the optional jar files is a little bit trickier.
+ // The order is not guaranteed to be identical.
+ // From a previous test, we do know however that there is the same number.
+ // The number of libraries should be low enough that we can simply go through the
+ // lists manually.
+ targetLoop: for (int tpi = 4 ; tpi < targetPaths.length; tpi++) {
+ String targetPath = targetPaths[tpi];
+
+ // look for a match in the other array
+ for (int cpi = 4 ; cpi < cachedPaths.length; cpi++) {
+ if (new File(targetPath).equals(new File(cachedPaths[cpi]))) {
+ // found a match. Try the next targetPath
+ continue targetLoop;
+ }
+ }
+
+ // if we stop here, we haven't found a match, which means there's a
+ // discrepancy in the libraries. We force a resolve.
+ i++;
+ continue projectLoop;
+ }
+ }
+
+ // at the point the check passes, and we can remove the project from the list.
+ // we do not increment i in this case.
+ projects.remove(i);
+ }
+ }
+
+ /**
+ * Returns the paths necessary to create the {@link IClasspathEntry} for this targets.
+ * <p/>The paths are always in the same order.
+ * <ul>
+ * <li>Path to android.jar</li>
+ * <li>Path to the source code for android.jar</li>
+ * <li>Path to the javadoc for the android platform</li>
+ * </ul>
+ * Additionally, if there are optional libraries, the array will contain:
+ * <ul>
+ * <li>Path to the libraries javadoc</li>
+ * <li>Path to the first .jar file</li>
+ * <li>(more .jar as needed)</li>
+ * </ul>
+ */
+ private static String[] getTargetPaths(IAndroidTarget target) {
+ ArrayList<String> paths = new ArrayList<String>();
+
+ // first, we get the path for android.jar
+ // The order is: android.jar, source folder, docs folder
+ paths.add(target.getPath(IAndroidTarget.ANDROID_JAR));
+ paths.add(target.getPath(IAndroidTarget.SOURCES));
+ paths.add(AdtPlugin.getUrlDoc());
+
+ // now deal with optional libraries.
+ IOptionalLibrary[] libraries = target.getOptionalLibraries();
+ if (libraries != null) {
+ // all the optional libraries use the same javadoc, so we start with this
+ String targetDocPath = target.getPath(IAndroidTarget.DOCS);
+ if (targetDocPath != null) {
+ paths.add(ProjectHelper.getJavaDocPath(targetDocPath));
+ } else {
+ // we add an empty string, to always have the same count.
+ paths.add("");
+ }
+
+ // because different libraries could use the same jar file, we make sure we add
+ // each jar file only once.
+ HashSet<String> visitedJars = new HashSet<String>();
+ for (IOptionalLibrary library : libraries) {
+ String jarPath = library.getJarPath();
+ if (visitedJars.contains(jarPath) == false) {
+ visitedJars.add(jarPath);
+ paths.add(jarPath);
+ }
+ }
+ }
+
+ return paths.toArray(new String[paths.size()]);
+ }
+
+ @Override
+ public boolean canUpdateClasspathContainer(IPath containerPath, IJavaProject project) {
+ return true;
+ }
+
+ @Override
+ public void requestClasspathContainerUpdate(IPath containerPath, IJavaProject project,
+ IClasspathContainer containerSuggestion) throws CoreException {
+ AdtPlugin plugin = AdtPlugin.getDefault();
+
+ synchronized (Sdk.getLock()) {
+ boolean sdkIsLoaded = plugin.getSdkLoadStatus() == LoadStatus.LOADED;
+
+ // check if the project has a valid target.
+ IAndroidTarget target = null;
+ if (sdkIsLoaded) {
+ target = Sdk.getCurrent().getTarget(project.getProject());
+ }
+ if (sdkIsLoaded && target != null) {
+ String[] paths = getTargetPaths(target);
+ IPath android_lib = new Path(paths[CACHE_INDEX_JAR]);
+ IClasspathEntry[] entries = containerSuggestion.getClasspathEntries();
+ for (int i = 0; i < entries.length; i++) {
+ IClasspathEntry entry = entries[i];
+ if (entry.getEntryKind() == IClasspathEntry.CPE_LIBRARY) {
+ IPath entryPath = entry.getPath();
+
+ if (entryPath != null) {
+ if (entryPath.equals(android_lib)) {
+ IPath entrySrcPath = entry.getSourceAttachmentPath();
+ IWorkspaceRoot root = ResourcesPlugin.getWorkspace().getRoot();
+ if (entrySrcPath != null) {
+ ProjectHelper.saveStringProperty(root,
+ getAndroidSourceProperty(target),
+ entrySrcPath.toString());
+ } else {
+ ProjectHelper.saveStringProperty(root,
+ getAndroidSourceProperty(target), null);
+ }
+ IClasspathAttribute[] extraAttributtes = entry.getExtraAttributes();
+ if (extraAttributtes.length == 0) {
+ ProjectHelper.saveStringProperty(root, PROPERTY_ANDROID_API,
+ NULL_API_URL);
+ }
+ for (int j = 0; j < extraAttributtes.length; j++) {
+ IClasspathAttribute extraAttribute = extraAttributtes[j];
+ String value = extraAttribute.getValue();
+ if ((value == null || value.trim().length() == 0)
+ && IClasspathAttribute.JAVADOC_LOCATION_ATTRIBUTE_NAME
+ .equals(extraAttribute.getName())) {
+ value = NULL_API_URL;
+ }
+ if (IClasspathAttribute.JAVADOC_LOCATION_ATTRIBUTE_NAME
+ .equals(extraAttribute.getName())) {
+ ProjectHelper.saveStringProperty(root,
+ PROPERTY_ANDROID_API, value);
+
+ }
+ }
+ }
+ }
+ }
+ }
+ rebindClasspathEntries(project.getJavaModel(), containerPath);
+ }
+ }
+ }
+
+ private static void rebindClasspathEntries(IJavaModel model, IPath containerPath)
+ throws JavaModelException {
+ ArrayList<IJavaProject> affectedProjects = new ArrayList<IJavaProject>();
+
+ IJavaProject[] projects = model.getJavaProjects();
+ for (int i = 0; i < projects.length; i++) {
+ IJavaProject project = projects[i];
+ IClasspathEntry[] entries = project.getRawClasspath();
+ for (int k = 0; k < entries.length; k++) {
+ IClasspathEntry curr = entries[k];
+ if (curr.getEntryKind() == IClasspathEntry.CPE_CONTAINER
+ && containerPath.equals(curr.getPath())) {
+ affectedProjects.add(project);
+ }
+ }
+ }
+ if (!affectedProjects.isEmpty()) {
+ IJavaProject[] affected = affectedProjects
+ .toArray(new IJavaProject[affectedProjects.size()]);
+ updateProjects(affected);
+ }
+ }
+}
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/project/AndroidClasspathContainerPage.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/project/AndroidClasspathContainerPage.java
new file mode 100644
index 000000000..b02765012
--- /dev/null
+++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/project/AndroidClasspathContainerPage.java
@@ -0,0 +1,200 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Eclipse Public License, Version 1.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.eclipse.org/org/documents/epl-v10.php
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ *******************************************************************************/
+
+package com.android.ide.eclipse.adt.internal.project;
+
+import com.android.ide.eclipse.adt.AdtConstants;
+
+import org.eclipse.core.resources.IProject;
+import org.eclipse.core.resources.ResourcesPlugin;
+import org.eclipse.core.runtime.IPath;
+import org.eclipse.core.runtime.IStatus;
+import org.eclipse.core.runtime.Path;
+import org.eclipse.jdt.core.IClasspathEntry;
+import org.eclipse.jdt.core.IJavaProject;
+import org.eclipse.jdt.core.JavaCore;
+import org.eclipse.jdt.internal.ui.dialogs.StatusInfo;
+import org.eclipse.jdt.internal.ui.dialogs.StatusUtil;
+import org.eclipse.jdt.ui.wizards.IClasspathContainerPage;
+import org.eclipse.jdt.ui.wizards.IClasspathContainerPageExtension;
+import org.eclipse.jface.wizard.WizardPage;
+import org.eclipse.swt.SWT;
+import org.eclipse.swt.layout.GridData;
+import org.eclipse.swt.layout.GridLayout;
+import org.eclipse.swt.widgets.Combo;
+import org.eclipse.swt.widgets.Composite;
+import org.eclipse.swt.widgets.Label;
+
+import java.util.Arrays;
+
+public class AndroidClasspathContainerPage extends WizardPage implements IClasspathContainerPage,
+ IClasspathContainerPageExtension {
+
+ private IProject mOwnerProject;
+
+ private String mLibsProjectName;
+
+ private Combo mProjectsCombo;
+
+ private IStatus mCurrStatus;
+
+ private boolean mPageVisible;
+
+ public AndroidClasspathContainerPage() {
+ super("AndroidClasspathContainerPage"); //$NON-NLS-1$
+ mPageVisible = false;
+ mCurrStatus = new StatusInfo();
+ setTitle("Android Libraries");
+ setDescription("This container manages classpath entries for Android container");
+ }
+
+ @Override
+ public IClasspathEntry getSelection() {
+ IPath path = new Path(AdtConstants.CONTAINER_FRAMEWORK);
+
+ final int index = this.mProjectsCombo.getSelectionIndex();
+ if (index != -1) {
+ final String selectedProjectName = this.mProjectsCombo.getItem(index);
+
+ if (this.mOwnerProject == null
+ || !selectedProjectName.equals(this.mOwnerProject.getName())) {
+ path = path.append(selectedProjectName);
+ }
+ }
+
+ return JavaCore.newContainerEntry(path);
+ }
+
+ @Override
+ public void setSelection(final IClasspathEntry cpentry) {
+ final IPath path = cpentry == null ? null : cpentry.getPath();
+
+ if (path == null || path.segmentCount() == 1) {
+ if (this.mOwnerProject != null) {
+ this.mLibsProjectName = this.mOwnerProject.getName();
+ }
+ } else {
+ this.mLibsProjectName = path.segment(1);
+ }
+ }
+
+ @Override
+ public void createControl(final Composite parent) {
+ final Composite composite = new Composite(parent, SWT.NONE);
+ composite.setLayout(new GridLayout(2, false));
+
+ final Label label = new Label(composite, SWT.NONE);
+ label.setText("Project:");
+
+ final String[] androidProjects = getAndroidProjects();
+
+ this.mProjectsCombo = new Combo(composite, SWT.READ_ONLY);
+ this.mProjectsCombo.setItems(androidProjects);
+
+ final int index;
+
+ if (this.mOwnerProject != null) {
+ index = indexOf(androidProjects, this.mLibsProjectName);
+ } else {
+ if (this.mProjectsCombo.getItemCount() > 0) {
+ index = 0;
+ } else {
+ index = -1;
+ }
+ }
+
+ if (index != -1) {
+ this.mProjectsCombo.select(index);
+ }
+
+ final GridData gd = new GridData();
+ gd.grabExcessHorizontalSpace = true;
+ gd.minimumWidth = 100;
+
+ this.mProjectsCombo.setLayoutData(gd);
+
+ setControl(composite);
+ }
+
+ @Override
+ public boolean finish() {
+ return true;
+ }
+
+ @Override
+ public void setVisible(boolean visible) {
+ super.setVisible(visible);
+ mPageVisible = visible;
+ // policy: wizards are not allowed to come up with an error message
+ if (visible && mCurrStatus.matches(IStatus.ERROR)) {
+ StatusInfo status = new StatusInfo();
+ status.setError(""); //$NON-NLS-1$
+ mCurrStatus = status;
+ }
+ updateStatus(mCurrStatus);
+ }
+
+ /**
+ * Updates the status line and the OK button according to the given status
+ *
+ * @param status status to apply
+ */
+ protected void updateStatus(IStatus status) {
+ mCurrStatus = status;
+ setPageComplete(!status.matches(IStatus.ERROR));
+ if (mPageVisible) {
+ StatusUtil.applyToStatusLine(this, status);
+ }
+ }
+
+ /**
+ * Updates the status line and the OK button according to the status
+ * evaluate from an array of status. The most severe error is taken. In case
+ * that two status with the same severity exists, the status with lower
+ * index is taken.
+ *
+ * @param status the array of status
+ */
+ protected void updateStatus(IStatus[] status) {
+ updateStatus(StatusUtil.getMostSevere(status));
+ }
+
+ @Override
+ public void initialize(final IJavaProject project, final IClasspathEntry[] currentEntries) {
+ this.mOwnerProject = (project == null ? null : project.getProject());
+ }
+
+ private static String[] getAndroidProjects() {
+ IProject[] projects = ResourcesPlugin.getWorkspace().getRoot().getProjects();
+ final String[] names = new String[projects.length];
+ for (int i = 0; i < projects.length; i++) {
+ names[i] = projects[i].getName();
+ }
+ Arrays.sort(names);
+ return names;
+ }
+
+ private static int indexOf(final String[] array, final String str) {
+ for (int i = 0; i < array.length; i++) {
+ if (array[i].equals(str)) {
+ return i;
+ }
+ }
+ return -1;
+ }
+
+}
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/project/AndroidExportNature.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/project/AndroidExportNature.java
new file mode 100644
index 000000000..218cffe5e
--- /dev/null
+++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/project/AndroidExportNature.java
@@ -0,0 +1,96 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Eclipse Public License, Version 1.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.eclipse.org/org/documents/epl-v10.php
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.ide.eclipse.adt.internal.project;
+
+import org.eclipse.core.resources.IProject;
+import org.eclipse.core.resources.IProjectNature;
+import org.eclipse.core.runtime.CoreException;
+
+/**
+ * Project nature for the Android Export Projects.
+ */
+public class AndroidExportNature implements IProjectNature {
+
+ /** the project this nature object is associated with */
+ private IProject mProject;
+
+ /**
+ * Configures this nature for its project. This is called by the workspace
+ * when natures are added to the project using
+ * <code>IProject.setDescription</code> and should not be called directly
+ * by clients. The nature extension id is added to the list of natures
+ * before this method is called, and need not be added here.
+ *
+ * Exceptions thrown by this method will be propagated back to the caller of
+ * <code>IProject.setDescription</code>, but the nature will remain in
+ * the project description.
+ *
+ * @see org.eclipse.core.resources.IProjectNature#configure()
+ * @throws CoreException if configuration fails.
+ */
+ @Override
+ public void configure() throws CoreException {
+ // nothing to do.
+ }
+
+ /**
+ * De-configures this nature for its project. This is called by the
+ * workspace when natures are removed from the project using
+ * <code>IProject.setDescription</code> and should not be called directly
+ * by clients. The nature extension id is removed from the list of natures
+ * before this method is called, and need not be removed here.
+ *
+ * Exceptions thrown by this method will be propagated back to the caller of
+ * <code>IProject.setDescription</code>, but the nature will still be
+ * removed from the project description.
+ *
+ * The Android nature removes the custom pre builder and APK builder.
+ *
+ * @see org.eclipse.core.resources.IProjectNature#deconfigure()
+ * @throws CoreException if configuration fails.
+ */
+ @Override
+ public void deconfigure() throws CoreException {
+ // nothing to do
+ }
+
+ /**
+ * Returns the project to which this project nature applies.
+ *
+ * @return the project handle
+ * @see org.eclipse.core.resources.IProjectNature#getProject()
+ */
+ @Override
+ public IProject getProject() {
+ return mProject;
+ }
+
+ /**
+ * Sets the project to which this nature applies. Used when instantiating
+ * this project nature runtime. This is called by
+ * <code>IProject.create()</code> or
+ * <code>IProject.setDescription()</code> and should not be called
+ * directly by clients.
+ *
+ * @param project the project to which this nature applies
+ * @see org.eclipse.core.resources.IProjectNature#setProject(org.eclipse.core.resources.IProject)
+ */
+ @Override
+ public void setProject(IProject project) {
+ mProject = project;
+ }
+}
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/project/AndroidManifestHelper.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/project/AndroidManifestHelper.java
new file mode 100644
index 000000000..eaa309668
--- /dev/null
+++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/project/AndroidManifestHelper.java
@@ -0,0 +1,203 @@
+/*
+ * Copyright (C) 2007 The Android Open Source Project
+ *
+ * Licensed under the Eclipse Public License, Version 1.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.eclipse.org/org/documents/epl-v10.php
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.ide.eclipse.adt.internal.project;
+
+import com.android.ide.common.xml.AndroidManifestParser;
+import com.android.ide.common.xml.ManifestData;
+import com.android.ide.eclipse.adt.AdtPlugin;
+import com.android.ide.eclipse.adt.internal.project.XmlErrorHandler.XmlErrorListener;
+import com.android.ide.eclipse.adt.io.IFileWrapper;
+import com.android.io.FileWrapper;
+import com.android.io.IAbstractFile;
+import com.android.io.StreamException;
+
+import org.eclipse.core.resources.IFile;
+import org.eclipse.core.resources.IProject;
+import org.eclipse.jdt.core.IJavaProject;
+import org.xml.sax.SAXException;
+
+import java.io.FileNotFoundException;
+import java.io.IOException;
+
+import javax.xml.parsers.ParserConfigurationException;
+
+public class AndroidManifestHelper {
+
+ /**
+ * Parses the Android Manifest, and returns an object containing the result of the parsing.
+ * <p/>
+ * This method can also gather XML error during the parsing. This is done by using an
+ * {@link XmlErrorHandler} to mark the files in case of error, as well as a given
+ * {@link XmlErrorListener}. To use a different error handler, consider using
+ * {@link AndroidManifestParser#parse(IAbstractFile, boolean, com.android.sdklib.xml.AndroidManifestParser.ManifestErrorHandler)}
+ * directly.
+ *
+ * @param manifestFile the {@link IFile} representing the manifest file.
+ * @param gatherData indicates whether the parsing will extract data from the manifest. If null,
+ * the method will always return null.
+ * @param errorListener an optional error listener. If non null, then the parser will also
+ * look for XML errors.
+ * @return an {@link ManifestData} or null if the parsing failed.
+ * @throws ParserConfigurationException
+ * @throws StreamException
+ * @throws IOException
+ * @throws SAXException
+ */
+ public static ManifestData parseUnchecked(
+ IAbstractFile manifestFile,
+ boolean gatherData,
+ XmlErrorListener errorListener) throws SAXException, IOException,
+ StreamException, ParserConfigurationException {
+ if (manifestFile != null) {
+ IFile eclipseFile = null;
+ if (manifestFile instanceof IFileWrapper) {
+ eclipseFile = ((IFileWrapper)manifestFile).getIFile();
+ }
+ XmlErrorHandler errorHandler = null;
+ if (errorListener != null) {
+ errorHandler = new XmlErrorHandler(eclipseFile, errorListener);
+ }
+
+ return AndroidManifestParser.parse(manifestFile, gatherData, errorHandler);
+ }
+
+ return null;
+ }
+
+ /**
+ * Parses the Android Manifest, and returns an object containing the result of the parsing.
+ * <p/>
+ * This method can also gather XML error during the parsing. This is done by using an
+ * {@link XmlErrorHandler} to mark the files in case of error, as well as a given
+ * {@link XmlErrorListener}. To use a different error handler, consider using
+ * {@link AndroidManifestParser#parse(IAbstractFile, boolean, com.android.sdklib.xml.AndroidManifestParser.ManifestErrorHandler)}
+ * directly.
+ *
+ * @param manifestFile the {@link IFile} representing the manifest file.
+ * @param gatherData indicates whether the parsing will extract data from the manifest. If null,
+ * the method will always return null.
+ * @param errorListener an optional error listener. If non null, then the parser will also
+ * look for XML errors.
+ * @return an {@link ManifestData} or null if the parsing failed.
+ */
+ public static ManifestData parse(
+ IAbstractFile manifestFile,
+ boolean gatherData,
+ XmlErrorListener errorListener) {
+ try {
+ return parseUnchecked(manifestFile, gatherData, errorListener);
+ } catch (ParserConfigurationException e) {
+ AdtPlugin.logAndPrintError(e, AndroidManifestHelper.class.getCanonicalName(),
+ "Bad parser configuration for %s: %s",
+ manifestFile.getOsLocation(),
+ e.getMessage());
+ } catch (SAXException e) {
+ AdtPlugin.logAndPrintError(e, AndroidManifestHelper.class.getCanonicalName(),
+ "Parser exception for %s: %s",
+ manifestFile.getOsLocation(),
+ e.getMessage());
+ } catch (IOException e) {
+ // Don't log a console error when failing to read a non-existing file
+ if (!(e instanceof FileNotFoundException)) {
+ AdtPlugin.logAndPrintError(e, AndroidManifestHelper.class.getCanonicalName(),
+ "I/O error for %s: %s",
+ manifestFile.getOsLocation(),
+ e.getMessage());
+ }
+ } catch (StreamException e) {
+ AdtPlugin.logAndPrintError(e, AndroidManifestHelper.class.getCanonicalName(),
+ "Unable to read %s: %s",
+ manifestFile.getOsLocation(),
+ e.getMessage());
+ }
+
+ return null;
+ }
+
+ /**
+ * Parses the Android Manifest for a given project, and returns an object containing
+ * the result of the parsing.
+ * <p/>
+ * This method can also gather XML error during the parsing. This is done by using an
+ * {@link XmlErrorHandler} to mark the files in case of error, as well as a given
+ * {@link XmlErrorListener}. To use a different error handler, consider using
+ * {@link AndroidManifestParser#parse(IAbstractFile, boolean, com.android.sdklib.xml.AndroidManifestParser.ManifestErrorHandler)}
+ * directly.
+ *
+ * @param javaProject the project containing the manifest to parse.
+ * @param gatherData indicates whether the parsing will extract data from the manifest. If null,
+ * the method will always return null.
+ * @param errorListener an optional error listener. If non null, then the parser will also
+ * look for XML errors.
+ * @return an {@link ManifestData} or null if the parsing failed.
+ */
+ public static ManifestData parse(
+ IJavaProject javaProject,
+ boolean gatherData,
+ XmlErrorListener errorListener) {
+
+ IFile manifestFile = ProjectHelper.getManifest(javaProject.getProject());
+ if (manifestFile != null) {
+ return parse(new IFileWrapper(manifestFile), gatherData, errorListener);
+ }
+
+ return null;
+ }
+
+ /**
+ * Parses the manifest file only for error check.
+ * @param manifestFile The manifest file to parse.
+ * @param errorListener the {@link XmlErrorListener} object being notified of the presence
+ * of errors.
+ */
+ public static void parseForError(IFile manifestFile, XmlErrorListener errorListener) {
+ parse(new IFileWrapper(manifestFile), false, errorListener);
+ }
+
+ /**
+ * Parses the manifest file, and collects data.
+ * @param manifestFile The manifest file to parse.
+ * @return an {@link ManifestData} or null if the parsing failed.
+ */
+ public static ManifestData parseForData(IFile manifestFile) {
+ return parse(new IFileWrapper(manifestFile), true, null);
+ }
+
+ /**
+ * Parses the manifest file, and collects data.
+ * @param project the project containing the manifest.
+ * @return an {@link AndroidManifestHelper} or null if the parsing failed.
+ */
+ public static ManifestData parseForData(IProject project) {
+ IFile manifestFile = ProjectHelper.getManifest(project);
+ if (manifestFile != null) {
+ return parse(new IFileWrapper(manifestFile), true, null);
+ }
+
+ return null;
+ }
+
+ /**
+ * Parses the manifest file, and collects data.
+ *
+ * @param osManifestFilePath The OS path of the manifest file to parse.
+ * @return an {@link AndroidManifestHelper} or null if the parsing failed.
+ */
+ public static ManifestData parseForData(String osManifestFilePath) {
+ return parse(new FileWrapper(osManifestFilePath), true, null);
+ }
+}
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/project/AndroidNature.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/project/AndroidNature.java
new file mode 100644
index 000000000..3b1c29fe9
--- /dev/null
+++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/project/AndroidNature.java
@@ -0,0 +1,299 @@
+/*
+ * Copyright (C) 2007 The Android Open Source Project
+ *
+ * Licensed under the Eclipse Public License, Version 1.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.eclipse.org/org/documents/epl-v10.php
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.ide.eclipse.adt.internal.project;
+
+import com.android.ide.eclipse.adt.AdtConstants;
+import com.android.ide.eclipse.adt.internal.build.builders.PostCompilerBuilder;
+import com.android.ide.eclipse.adt.internal.build.builders.PreCompilerBuilder;
+import com.android.ide.eclipse.adt.internal.build.builders.ResourceManagerBuilder;
+
+import org.eclipse.core.resources.ICommand;
+import org.eclipse.core.resources.IProject;
+import org.eclipse.core.resources.IProjectDescription;
+import org.eclipse.core.resources.IProjectNature;
+import org.eclipse.core.runtime.CoreException;
+import org.eclipse.core.runtime.IProgressMonitor;
+import org.eclipse.core.runtime.NullProgressMonitor;
+import org.eclipse.core.runtime.SubProgressMonitor;
+import org.eclipse.jdt.core.JavaCore;
+
+/**
+ * Project nature for the Android Projects.
+ */
+public class AndroidNature implements IProjectNature {
+
+ /** the project this nature object is associated with */
+ private IProject mProject;
+
+ /**
+ * Configures this nature for its project. This is called by the workspace
+ * when natures are added to the project using
+ * <code>IProject.setDescription</code> and should not be called directly
+ * by clients. The nature extension id is added to the list of natures
+ * before this method is called, and need not be added here.
+ *
+ * Exceptions thrown by this method will be propagated back to the caller of
+ * <code>IProject.setDescription</code>, but the nature will remain in
+ * the project description.
+ *
+ * The Android nature adds the pre-builder and the APK builder if necessary.
+ *
+ * @see org.eclipse.core.resources.IProjectNature#configure()
+ * @throws CoreException if configuration fails.
+ */
+ @Override
+ public void configure() throws CoreException {
+ configureResourceManagerBuilder(mProject);
+ configurePreBuilder(mProject);
+ configureApkBuilder(mProject);
+ }
+
+ /**
+ * De-configures this nature for its project. This is called by the
+ * workspace when natures are removed from the project using
+ * <code>IProject.setDescription</code> and should not be called directly
+ * by clients. The nature extension id is removed from the list of natures
+ * before this method is called, and need not be removed here.
+ *
+ * Exceptions thrown by this method will be propagated back to the caller of
+ * <code>IProject.setDescription</code>, but the nature will still be
+ * removed from the project description.
+ *
+ * The Android nature removes the custom pre builder and APK builder.
+ *
+ * @see org.eclipse.core.resources.IProjectNature#deconfigure()
+ * @throws CoreException if configuration fails.
+ */
+ @Override
+ public void deconfigure() throws CoreException {
+ // remove the android builders
+ removeBuilder(mProject, ResourceManagerBuilder.ID);
+ removeBuilder(mProject, PreCompilerBuilder.ID);
+ removeBuilder(mProject, PostCompilerBuilder.ID);
+ }
+
+ /**
+ * Returns the project to which this project nature applies.
+ *
+ * @return the project handle
+ * @see org.eclipse.core.resources.IProjectNature#getProject()
+ */
+ @Override
+ public IProject getProject() {
+ return mProject;
+ }
+
+ /**
+ * Sets the project to which this nature applies. Used when instantiating
+ * this project nature runtime. This is called by
+ * <code>IProject.create()</code> or
+ * <code>IProject.setDescription()</code> and should not be called
+ * directly by clients.
+ *
+ * @param project the project to which this nature applies
+ * @see org.eclipse.core.resources.IProjectNature#setProject(org.eclipse.core.resources.IProject)
+ */
+ @Override
+ public void setProject(IProject project) {
+ mProject = project;
+ }
+
+ /**
+ * Adds the Android Nature and the Java Nature to the project if it doesn't
+ * already have them.
+ *
+ * @param project An existing or new project to update
+ * @param monitor An optional progress monitor. Can be null.
+ * @param addAndroidNature true if the Android Nature should be added to the project; false to
+ * add only the Java nature.
+ * @throws CoreException if fails to change the nature.
+ */
+ public static synchronized void setupProjectNatures(IProject project,
+ IProgressMonitor monitor, boolean addAndroidNature) throws CoreException {
+ if (project == null || !project.isOpen()) return;
+ if (monitor == null) monitor = new NullProgressMonitor();
+
+ // Add the natures. We need to add the Java nature first, so it adds its builder to the
+ // project first. This way, when the android nature is added, we can control where to put
+ // the android builders in relation to the java builder.
+ // Adding the java nature after the android one, would place the java builder before the
+ // android builders.
+ addNatureToProjectDescription(project, JavaCore.NATURE_ID, monitor);
+ if (addAndroidNature) {
+ addNatureToProjectDescription(project, AdtConstants.NATURE_DEFAULT, monitor);
+ }
+ }
+
+ /**
+ * Add the specified nature to the specified project. The nature is only
+ * added if not already present.
+ * <p/>
+ * Android Natures are always inserted at the beginning of the list of natures in order to
+ * have the jdt views/dialogs display the proper icon.
+ *
+ * @param project The project to modify.
+ * @param natureId The Id of the nature to add.
+ * @param monitor An existing progress monitor.
+ * @throws CoreException if fails to change the nature.
+ */
+ private static void addNatureToProjectDescription(IProject project,
+ String natureId, IProgressMonitor monitor) throws CoreException {
+ if (!project.hasNature(natureId)) {
+
+ IProjectDescription description = project.getDescription();
+ String[] natures = description.getNatureIds();
+ String[] newNatures = new String[natures.length + 1];
+
+ // Android natures always come first.
+ if (natureId.equals(AdtConstants.NATURE_DEFAULT)) {
+ System.arraycopy(natures, 0, newNatures, 1, natures.length);
+ newNatures[0] = natureId;
+ } else {
+ System.arraycopy(natures, 0, newNatures, 0, natures.length);
+ newNatures[natures.length] = natureId;
+ }
+
+ description.setNatureIds(newNatures);
+ project.setDescription(description, new SubProgressMonitor(monitor, 10));
+ }
+ }
+
+ /**
+ * Adds the ResourceManagerBuilder, if its not already there. It'll insert
+ * itself as the first builder.
+ * @throws CoreException
+ *
+ */
+ public static void configureResourceManagerBuilder(IProject project)
+ throws CoreException {
+ // get the builder list
+ IProjectDescription desc = project.getDescription();
+ ICommand[] commands = desc.getBuildSpec();
+
+ // look for the builder in case it's already there.
+ for (int i = 0; i < commands.length; ++i) {
+ if (ResourceManagerBuilder.ID.equals(commands[i].getBuilderName())) {
+ return;
+ }
+ }
+
+ // it's not there, lets add it at the beginning of the builders
+ ICommand[] newCommands = new ICommand[commands.length + 1];
+ System.arraycopy(commands, 0, newCommands, 1, commands.length);
+ ICommand command = desc.newCommand();
+ command.setBuilderName(ResourceManagerBuilder.ID);
+ newCommands[0] = command;
+ desc.setBuildSpec(newCommands);
+ project.setDescription(desc, null);
+ }
+
+ /**
+ * Adds the PreCompilerBuilder if its not already there. It'll check for
+ * presence of the ResourceManager and insert itself right after.
+ * @param project
+ * @throws CoreException
+ */
+ public static void configurePreBuilder(IProject project)
+ throws CoreException {
+ // get the builder list
+ IProjectDescription desc = project.getDescription();
+ ICommand[] commands = desc.getBuildSpec();
+
+ // look for the builder in case it's already there.
+ for (int i = 0; i < commands.length; ++i) {
+ if (PreCompilerBuilder.ID.equals(commands[i].getBuilderName())) {
+ return;
+ }
+ }
+
+ // we need to add it after the resource manager builder.
+ // Let's look for it
+ int index = -1;
+ for (int i = 0; i < commands.length; ++i) {
+ if (ResourceManagerBuilder.ID.equals(commands[i].getBuilderName())) {
+ index = i;
+ break;
+ }
+ }
+
+ // we're inserting after
+ index++;
+
+ // do the insertion
+
+ // copy the builders before.
+ ICommand[] newCommands = new ICommand[commands.length + 1];
+ System.arraycopy(commands, 0, newCommands, 0, index);
+
+ // insert the new builder
+ ICommand command = desc.newCommand();
+ command.setBuilderName(PreCompilerBuilder.ID);
+ newCommands[index] = command;
+
+ // copy the builder after
+ System.arraycopy(commands, index, newCommands, index + 1, commands.length-index);
+
+ // set the new builders in the project
+ desc.setBuildSpec(newCommands);
+ project.setDescription(desc, null);
+ }
+
+ public static void configureApkBuilder(IProject project)
+ throws CoreException {
+ // Add the .apk builder at the end if it's not already there
+ IProjectDescription desc = project.getDescription();
+ ICommand[] commands = desc.getBuildSpec();
+
+ for (int i = 0; i < commands.length; ++i) {
+ if (PostCompilerBuilder.ID.equals(commands[i].getBuilderName())) {
+ return;
+ }
+ }
+
+ ICommand[] newCommands = new ICommand[commands.length + 1];
+ System.arraycopy(commands, 0, newCommands, 0, commands.length);
+ ICommand command = desc.newCommand();
+ command.setBuilderName(PostCompilerBuilder.ID);
+ newCommands[commands.length] = command;
+ desc.setBuildSpec(newCommands);
+ project.setDescription(desc, null);
+ }
+
+ /**
+ * Removes a builder from the project.
+ * @param project The project to remove the builder from.
+ * @param id The String ID of the builder to remove.
+ * @return true if the builder was found and removed.
+ * @throws CoreException
+ */
+ public static boolean removeBuilder(IProject project, String id) throws CoreException {
+ IProjectDescription description = project.getDescription();
+ ICommand[] commands = description.getBuildSpec();
+ for (int i = 0; i < commands.length; ++i) {
+ if (id.equals(commands[i].getBuilderName())) {
+ ICommand[] newCommands = new ICommand[commands.length - 1];
+ System.arraycopy(commands, 0, newCommands, 0, i);
+ System.arraycopy(commands, i + 1, newCommands, i, commands.length - i - 1);
+ description.setBuildSpec(newCommands);
+ project.setDescription(description, null);
+ return true;
+ }
+ }
+
+ return false;
+ }
+}
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/project/ApkInstallManager.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/project/ApkInstallManager.java
new file mode 100644
index 000000000..903914684
--- /dev/null
+++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/project/ApkInstallManager.java
@@ -0,0 +1,278 @@
+/*
+ * Copyright (C) 2009 The Android Open Source Project
+ *
+ * Licensed under the Eclipse Public License, Version 1.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.eclipse.org/org/documents/epl-v10.php
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.ide.eclipse.adt.internal.project;
+
+import com.android.ddmlib.AndroidDebugBridge;
+import com.android.ddmlib.AndroidDebugBridge.IDebugBridgeChangeListener;
+import com.android.ddmlib.AndroidDebugBridge.IDeviceChangeListener;
+import com.android.ddmlib.IDevice;
+import com.android.ddmlib.MultiLineReceiver;
+import com.android.ide.eclipse.adt.internal.resources.manager.GlobalProjectMonitor;
+import com.android.ide.eclipse.adt.internal.resources.manager.GlobalProjectMonitor.IProjectListener;
+
+import org.eclipse.core.resources.IProject;
+import org.eclipse.core.runtime.IPath;
+
+import java.util.HashSet;
+import java.util.Iterator;
+
+/**
+ * Registers which apk was installed on which device.
+ * <p/>
+ * The goal of this class is to remember the installation of APKs on devices, and provide
+ * information about whether a new APK should be installed on a device prior to running the
+ * application from a launch configuration.
+ * <p/>
+ * The manager uses {@link IProject} and {@link IDevice} to identify the target device and the
+ * (project generating the) APK. This ensures that disconnected and reconnected devices will
+ * always receive new APKs (since the version may not match).
+ * <p/>
+ * This is a singleton. To get the instance, use {@link #getInstance()}
+ */
+public final class ApkInstallManager {
+
+ private final static ApkInstallManager sThis = new ApkInstallManager();
+
+ /**
+ * Internal struct to associate a project and a device.
+ */
+ private final static class ApkInstall {
+ public ApkInstall(IProject project, String packageName, IDevice device) {
+ this.project = project;
+ this.packageName = packageName;
+ this.device = device;
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (obj instanceof ApkInstall) {
+ ApkInstall apkObj = (ApkInstall)obj;
+
+ return (device == apkObj.device && project.equals(apkObj.project) &&
+ packageName.equals(apkObj.packageName));
+ }
+
+ return false;
+ }
+
+ @Override
+ public int hashCode() {
+ return (device.getSerialNumber() + project.getName() + packageName).hashCode();
+ }
+
+ final IProject project;
+ final String packageName;
+ final IDevice device;
+ }
+
+ /**
+ * Receiver and parser for the "pm path package" command.
+ */
+ private final static class PmReceiver extends MultiLineReceiver {
+ boolean foundPackage = false;
+ @Override
+ public void processNewLines(String[] lines) {
+ // if the package if found, then pm will show a line starting with "package:/"
+ if (foundPackage == false) { // just in case this is called several times for multilines
+ for (String line : lines) {
+ if (line.startsWith("package:/")) {
+ foundPackage = true;
+ break;
+ }
+ }
+ }
+ }
+
+ @Override
+ public boolean isCancelled() {
+ return false;
+ }
+ }
+
+ /**
+ * Hashset of the list of installed package. Hashset used to ensure we don't re-add new
+ * objects for the same app.
+ */
+ private final HashSet<ApkInstall> mInstallList = new HashSet<ApkInstall>();
+
+ public static ApkInstallManager getInstance() {
+ return sThis;
+ }
+
+ /**
+ * Registers an installation of <var>project</var> onto <var>device</var>
+ * @param project The project that was installed.
+ * @param packageName the package name of the apk
+ * @param device The device that received the installation.
+ */
+ public void registerInstallation(IProject project, String packageName, IDevice device) {
+ synchronized (mInstallList) {
+ mInstallList.add(new ApkInstall(project, packageName, device));
+ }
+ }
+
+ /**
+ * Returns whether a <var>project</var> was installed on the <var>device</var>.
+ * @param project the project that may have been installed.
+ * @param device the device that may have received the installation.
+ * @return
+ */
+ public boolean isApplicationInstalled(IProject project, String packageName, IDevice device) {
+ synchronized (mInstallList) {
+ ApkInstall found = null;
+ for (ApkInstall install : mInstallList) {
+ if (project.equals(install.project) && packageName.equals(install.packageName) &&
+ device == install.device) {
+ found = install;
+ break;
+ }
+ }
+
+ // check the app is still installed.
+ if (found != null) {
+ try {
+ PmReceiver receiver = new PmReceiver();
+ found.device.executeShellCommand("pm path " + packageName, receiver);
+ if (receiver.foundPackage == false) {
+ mInstallList.remove(found);
+ }
+
+ return receiver.foundPackage;
+ } catch (Exception e) {
+ // failed to query pm? force reinstall.
+ return false;
+ }
+ }
+ }
+ return false;
+ }
+
+ /**
+ * Resets registered installations for a specific {@link IProject}.
+ * <p/>This ensures that {@link #isApplicationInstalled(IProject, IDevice)} will always return
+ * <code>null</code> for this specified project, for any device.
+ * @param project the project for which to reset all installations.
+ */
+ public void resetInstallationFor(IProject project) {
+ synchronized (mInstallList) {
+ Iterator<ApkInstall> iterator = mInstallList.iterator();
+ while (iterator.hasNext()) {
+ ApkInstall install = iterator.next();
+ if (install.project.equals(project)) {
+ iterator.remove();
+ }
+ }
+ }
+ }
+
+ private ApkInstallManager() {
+ AndroidDebugBridge.addDeviceChangeListener(mDeviceChangeListener);
+ AndroidDebugBridge.addDebugBridgeChangeListener(mDebugBridgeListener);
+ GlobalProjectMonitor.getMonitor().addProjectListener(mProjectListener);
+ }
+
+ private IDebugBridgeChangeListener mDebugBridgeListener = new IDebugBridgeChangeListener() {
+ /**
+ * Responds to a bridge change by clearing the full installation list.
+ *
+ * @see IDebugBridgeChangeListener#bridgeChanged(AndroidDebugBridge)
+ */
+ @Override
+ public void bridgeChanged(AndroidDebugBridge bridge) {
+ // the bridge changed, there is no way to know which IDevice will be which.
+ // We reset everything
+ synchronized (mInstallList) {
+ mInstallList.clear();
+ }
+ }
+ };
+
+ private IDeviceChangeListener mDeviceChangeListener = new IDeviceChangeListener() {
+ /**
+ * Responds to a device being disconnected by removing all installations related
+ * to this device.
+ *
+ * @see IDeviceChangeListener#deviceDisconnected(IDevice)
+ */
+ @Override
+ public void deviceDisconnected(IDevice device) {
+ synchronized (mInstallList) {
+ Iterator<ApkInstall> iterator = mInstallList.iterator();
+ while (iterator.hasNext()) {
+ ApkInstall install = iterator.next();
+ if (install.device == device) {
+ iterator.remove();
+ }
+ }
+ }
+ }
+
+ @Override
+ public void deviceChanged(IDevice device, int changeMask) {
+ // nothing to do.
+ }
+
+ @Override
+ public void deviceConnected(IDevice device) {
+ // nothing to do.
+ }
+ };
+
+ private IProjectListener mProjectListener = new IProjectListener() {
+ /**
+ * Responds to a closed project by resetting all its installation.
+ *
+ * @see IProjectListener#projectClosed(IProject)
+ */
+ @Override
+ public void projectClosed(IProject project) {
+ resetInstallationFor(project);
+ }
+
+ /**
+ * Responds to a deleted project by resetting all its installation.
+ *
+ * @see IProjectListener#projectDeleted(IProject)
+ */
+ @Override
+ public void projectDeleted(IProject project) {
+ resetInstallationFor(project);
+ }
+
+ @Override
+ public void projectOpened(IProject project) {
+ // nothing to do.
+ }
+
+ @Override
+ public void projectOpenedWithWorkspace(IProject project) {
+ // nothing to do.
+ }
+
+ @Override
+ public void allProjectsOpenedWithWorkspace() {
+ // nothing to do.
+ }
+
+ @Override
+ public void projectRenamed(IProject project, IPath from) {
+ // project renaming also triggers delete/open events so
+ // there's nothing to do here (since delete will remove
+ // whatever's linked to the project from the list).
+ }
+ };
+}
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/project/BaseClasspathContainerInitializer.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/project/BaseClasspathContainerInitializer.java
new file mode 100644
index 000000000..a58f27d61
--- /dev/null
+++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/project/BaseClasspathContainerInitializer.java
@@ -0,0 +1,103 @@
+/*
+ * Copyright (C) 2012 The Android Open Source Project
+ *
+ * Licensed under the Eclipse Public License, Version 1.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.eclipse.org/org/documents/epl-v10.php
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.ide.eclipse.adt.internal.project;
+
+import com.android.ide.eclipse.adt.AdtPlugin;
+
+import org.eclipse.core.resources.IMarker;
+import org.eclipse.core.resources.IProject;
+import org.eclipse.core.resources.IResource;
+import org.eclipse.core.resources.ResourcesPlugin;
+import org.eclipse.core.runtime.CoreException;
+import org.eclipse.core.runtime.IProgressMonitor;
+import org.eclipse.core.runtime.IStatus;
+import org.eclipse.core.runtime.Status;
+import org.eclipse.core.runtime.jobs.Job;
+import org.eclipse.jdt.core.ClasspathContainerInitializer;
+
+/**
+ * Base CPC initializer providing support to all our initializer.
+ *
+ */
+abstract class BaseClasspathContainerInitializer extends ClasspathContainerInitializer {
+
+
+ /**
+ * Adds an error to a project, or remove all markers if error message is null
+ * @param project the project to modify
+ * @param errorMessage the errorMessage or null to remove errors.
+ * @param markerType the marker type to be used.
+ * @param outputToConsole whether to output to the console.
+ */
+ protected static void processError(final IProject project, final String errorMessage,
+ final String markerType, boolean outputToConsole) {
+ if (errorMessage != null) {
+ // log the error and put the marker on the project if we can.
+ if (outputToConsole) {
+ AdtPlugin.printErrorToConsole(project, errorMessage);
+ }
+
+ // Use a job to prevent requiring a workspace lock in this thread.
+ final String fmessage = errorMessage;
+ Job markerJob = new Job("Android SDK: Resolving error markers") {
+ @Override
+ protected IStatus run(IProgressMonitor monitor) {
+ try {
+ BaseProjectHelper.markProject(project,
+ markerType,
+ fmessage, IMarker.SEVERITY_ERROR,
+ IMarker.PRIORITY_HIGH);
+ } catch (CoreException e2) {
+ AdtPlugin.log(e2, null);
+ // Don't return e2.getStatus(); the job control will then produce
+ // a popup with this error, which isn't very interesting for the
+ // user.
+ }
+
+ return Status.OK_STATUS;
+ }
+ };
+
+ // build jobs are run after other interactive jobs
+ markerJob.setPriority(Job.BUILD);
+ markerJob.setRule(ResourcesPlugin.getWorkspace().getRoot());
+ markerJob.schedule();
+ } else {
+ // Use a job to prevent requiring a workspace lock in this thread.
+ Job markerJob = new Job("Android SDK: Resolving error markers") {
+ @Override
+ protected IStatus run(IProgressMonitor monitor) {
+ try {
+ if (project.isAccessible()) {
+ project.deleteMarkers(markerType, true,
+ IResource.DEPTH_INFINITE);
+ }
+ } catch (CoreException e2) {
+ AdtPlugin.log(e2, null);
+ }
+
+ return Status.OK_STATUS;
+ }
+ };
+
+ // build jobs are run after other interactive jobs
+ markerJob.setPriority(Job.BUILD);
+ markerJob.setRule(ResourcesPlugin.getWorkspace().getRoot());
+ markerJob.schedule();
+ }
+ }
+}
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/project/BaseProjectHelper.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/project/BaseProjectHelper.java
new file mode 100644
index 000000000..57632ea87
--- /dev/null
+++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/project/BaseProjectHelper.java
@@ -0,0 +1,527 @@
+/*
+ * Copyright (C) 2007 The Android Open Source Project
+ *
+ * Licensed under the Eclipse Public License, Version 1.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.eclipse.org/org/documents/epl-v10.php
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.ide.eclipse.adt.internal.project;
+
+import com.android.SdkConstants;
+import com.android.annotations.NonNull;
+import com.android.annotations.Nullable;
+import com.android.ide.eclipse.adt.AdtConstants;
+import com.android.ide.eclipse.adt.AdtPlugin;
+import com.google.common.collect.Lists;
+
+import org.eclipse.core.resources.IFolder;
+import org.eclipse.core.resources.IMarker;
+import org.eclipse.core.resources.IProject;
+import org.eclipse.core.resources.IResource;
+import org.eclipse.core.resources.IWorkspaceRoot;
+import org.eclipse.core.resources.ResourcesPlugin;
+import org.eclipse.core.runtime.CoreException;
+import org.eclipse.core.runtime.IPath;
+import org.eclipse.core.runtime.NullProgressMonitor;
+import org.eclipse.jdt.core.Flags;
+import org.eclipse.jdt.core.IClasspathEntry;
+import org.eclipse.jdt.core.IJavaModel;
+import org.eclipse.jdt.core.IJavaProject;
+import org.eclipse.jdt.core.IMethod;
+import org.eclipse.jdt.core.IType;
+import org.eclipse.jdt.core.ITypeHierarchy;
+import org.eclipse.jdt.core.JavaCore;
+import org.eclipse.jdt.core.JavaModelException;
+import org.eclipse.jdt.ui.JavaUI;
+import org.eclipse.jdt.ui.actions.OpenJavaPerspectiveAction;
+import org.eclipse.jface.text.BadLocationException;
+import org.eclipse.jface.text.IDocument;
+import org.eclipse.jface.text.IRegion;
+import org.eclipse.ui.IEditorInput;
+import org.eclipse.ui.IEditorPart;
+import org.eclipse.ui.IWorkbench;
+import org.eclipse.ui.IWorkbenchPage;
+import org.eclipse.ui.IWorkbenchWindow;
+import org.eclipse.ui.PartInitException;
+import org.eclipse.ui.PlatformUI;
+import org.eclipse.ui.texteditor.IDocumentProvider;
+import org.eclipse.ui.texteditor.ITextEditor;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Utility methods to manipulate projects.
+ */
+public final class BaseProjectHelper {
+
+ public static final String TEST_CLASS_OK = null;
+
+ /**
+ * Project filter to be used with {@link BaseProjectHelper#getAndroidProjects(IProjectFilter)}.
+ */
+ public static interface IProjectFilter {
+ boolean accept(IProject project);
+ }
+
+ /**
+ * returns a list of source classpath for a specified project
+ * @param javaProject
+ * @return a list of path relative to the workspace root.
+ */
+ @NonNull
+ public static List<IPath> getSourceClasspaths(IJavaProject javaProject) {
+ List<IPath> sourceList = Lists.newArrayList();
+ IClasspathEntry[] classpaths = javaProject.readRawClasspath();
+ if (classpaths != null) {
+ for (IClasspathEntry e : classpaths) {
+ if (e.getEntryKind() == IClasspathEntry.CPE_SOURCE) {
+ sourceList.add(e.getPath());
+ }
+ }
+ }
+
+ return sourceList;
+ }
+
+ /**
+ * returns a list of source classpath for a specified project
+ * @param project
+ * @return a list of path relative to the workspace root.
+ */
+ public static List<IPath> getSourceClasspaths(IProject project) {
+ IJavaProject javaProject = JavaCore.create(project);
+ return getSourceClasspaths(javaProject);
+ }
+
+ /**
+ * Adds a marker to a file on a specific line. This methods catches thrown
+ * {@link CoreException}, and returns null instead.
+ * @param resource the resource to be marked
+ * @param markerId The id of the marker to add.
+ * @param message the message associated with the mark
+ * @param lineNumber the line number where to put the mark. If line is < 1, it puts the marker
+ * on line 1,
+ * @param severity the severity of the marker.
+ * @return the IMarker that was added or null if it failed to add one.
+ */
+ public final static IMarker markResource(IResource resource, String markerId,
+ String message, int lineNumber, int severity) {
+ return markResource(resource, markerId, message, lineNumber, -1, -1, severity);
+ }
+
+ /**
+ * Adds a marker to a file on a specific line, for a specific range of text. This
+ * methods catches thrown {@link CoreException}, and returns null instead.
+ *
+ * @param resource the resource to be marked
+ * @param markerId The id of the marker to add.
+ * @param message the message associated with the mark
+ * @param lineNumber the line number where to put the mark. If line is < 1, it puts
+ * the marker on line 1,
+ * @param startOffset the beginning offset of the marker (relative to the beginning of
+ * the document, not the line), or -1 for no range
+ * @param endOffset the ending offset of the marker
+ * @param severity the severity of the marker.
+ * @return the IMarker that was added or null if it failed to add one.
+ */
+ @Nullable
+ public final static IMarker markResource(IResource resource, String markerId,
+ String message, int lineNumber, int startOffset, int endOffset, int severity) {
+ if (!resource.isAccessible()) {
+ return null;
+ }
+
+ try {
+ IMarker marker = resource.createMarker(markerId);
+ marker.setAttribute(IMarker.MESSAGE, message);
+ marker.setAttribute(IMarker.SEVERITY, severity);
+
+ // if marker is text type, enforce a line number so that it shows in the editor
+ // somewhere (line 1)
+ if (lineNumber < 1 && marker.isSubtypeOf(IMarker.TEXT)) {
+ lineNumber = 1;
+ }
+
+ if (lineNumber >= 1) {
+ marker.setAttribute(IMarker.LINE_NUMBER, lineNumber);
+ }
+
+ if (startOffset != -1) {
+ marker.setAttribute(IMarker.CHAR_START, startOffset);
+ marker.setAttribute(IMarker.CHAR_END, endOffset);
+ }
+
+ // on Windows, when adding a marker to a project, it takes a refresh for the marker
+ // to show. In order to fix this we're forcing a refresh of elements receiving
+ // markers (and only the element, not its children), to force the marker display.
+ resource.refreshLocal(IResource.DEPTH_ZERO, new NullProgressMonitor());
+
+ return marker;
+ } catch (CoreException e) {
+ AdtPlugin.log(e, "Failed to add marker '%1$s' to '%2$s'", //$NON-NLS-1$
+ markerId, resource.getFullPath());
+ }
+
+ return null;
+ }
+
+ /**
+ * Adds a marker to a resource. This methods catches thrown {@link CoreException},
+ * and returns null instead.
+ * @param resource the file to be marked
+ * @param markerId The id of the marker to add.
+ * @param message the message associated with the mark
+ * @param severity the severity of the marker.
+ * @return the IMarker that was added or null if it failed to add one.
+ */
+ @Nullable
+ public final static IMarker markResource(IResource resource, String markerId,
+ String message, int severity) {
+ return markResource(resource, markerId, message, -1, severity);
+ }
+
+ /**
+ * Adds a marker to an {@link IProject}. This method does not catch {@link CoreException}, like
+ * {@link #markResource(IResource, String, String, int)}.
+ *
+ * @param project the project to be marked
+ * @param markerId The id of the marker to add.
+ * @param message the message associated with the mark
+ * @param severity the severity of the marker.
+ * @param priority the priority of the marker
+ * @return the IMarker that was added.
+ * @throws CoreException if the marker cannot be added
+ */
+ @Nullable
+ public final static IMarker markProject(IProject project, String markerId,
+ String message, int severity, int priority) throws CoreException {
+ if (!project.isAccessible()) {
+ return null;
+ }
+
+ IMarker marker = project.createMarker(markerId);
+ marker.setAttribute(IMarker.MESSAGE, message);
+ marker.setAttribute(IMarker.SEVERITY, severity);
+ marker.setAttribute(IMarker.PRIORITY, priority);
+
+ // on Windows, when adding a marker to a project, it takes a refresh for the marker
+ // to show. In order to fix this we're forcing a refresh of elements receiving
+ // markers (and only the element, not its children), to force the marker display.
+ project.refreshLocal(IResource.DEPTH_ZERO, new NullProgressMonitor());
+
+ return marker;
+ }
+
+ /**
+ * Tests that a class name is valid for usage in the manifest.
+ * <p/>
+ * This tests the class existence, that it can be instantiated (ie it must not be abstract,
+ * nor non static if enclosed), and that it extends the proper super class (not necessarily
+ * directly)
+ * @param javaProject the {@link IJavaProject} containing the class.
+ * @param className the fully qualified name of the class to test.
+ * @param superClassName the fully qualified name of the expected super class.
+ * @param testVisibility if <code>true</code>, the method will check the visibility of the class
+ * or of its constructors.
+ * @return {@link #TEST_CLASS_OK} or an error message.
+ */
+ public final static String testClassForManifest(IJavaProject javaProject, String className,
+ String superClassName, boolean testVisibility) {
+ try {
+ // replace $ by .
+ String javaClassName = className.replaceAll("\\$", "\\."); //$NON-NLS-1$ //$NON-NLS-2$
+
+ // look for the IType object for this class
+ IType type = javaProject.findType(javaClassName);
+ if (type != null && type.exists()) {
+ // test that the class is not abstract
+ int flags = type.getFlags();
+ if (Flags.isAbstract(flags)) {
+ return String.format("%1$s is abstract", className);
+ }
+
+ // test whether the class is public or not.
+ if (testVisibility && Flags.isPublic(flags) == false) {
+ // if its not public, it may have a public default constructor,
+ // which would then be fine.
+ IMethod basicConstructor = type.getMethod(type.getElementName(), new String[0]);
+ if (basicConstructor != null && basicConstructor.exists()) {
+ int constructFlags = basicConstructor.getFlags();
+ if (Flags.isPublic(constructFlags) == false) {
+ return String.format(
+ "%1$s or its default constructor must be public for the system to be able to instantiate it",
+ className);
+ }
+ } else {
+ return String.format(
+ "%1$s must be public, or the system will not be able to instantiate it.",
+ className);
+ }
+ }
+
+ // If it's enclosed, test that it's static. If its declaring class is enclosed
+ // as well, test that it is also static, and public.
+ IType declaringType = type;
+ do {
+ IType tmpType = declaringType.getDeclaringType();
+ if (tmpType != null) {
+ if (tmpType.exists()) {
+ flags = declaringType.getFlags();
+ if (Flags.isStatic(flags) == false) {
+ return String.format("%1$s is enclosed, but not static",
+ declaringType.getFullyQualifiedName());
+ }
+
+ flags = tmpType.getFlags();
+ if (testVisibility && Flags.isPublic(flags) == false) {
+ return String.format("%1$s is not public",
+ tmpType.getFullyQualifiedName());
+ }
+ } else {
+ // if it doesn't exist, we need to exit so we may as well mark it null.
+ tmpType = null;
+ }
+ }
+ declaringType = tmpType;
+ } while (declaringType != null);
+
+ // test the class inherit from the specified super class.
+ // get the type hierarchy
+ ITypeHierarchy hierarchy = type.newSupertypeHierarchy(new NullProgressMonitor());
+
+ // if the super class is not the reference class, it may inherit from
+ // it so we get its supertype. At some point it will be null and we
+ // will stop
+ IType superType = type;
+ boolean foundProperSuperClass = false;
+ while ((superType = hierarchy.getSuperclass(superType)) != null &&
+ superType.exists()) {
+ if (superClassName.equals(superType.getFullyQualifiedName())) {
+ foundProperSuperClass = true;
+ }
+ }
+
+ // didn't find the proper superclass? return false.
+ if (foundProperSuperClass == false) {
+ return String.format("%1$s does not extend %2$s", className, superClassName);
+ }
+
+ return TEST_CLASS_OK;
+ } else {
+ return String.format("Class %1$s does not exist", className);
+ }
+ } catch (JavaModelException e) {
+ return String.format("%1$s: %2$s", className, e.getMessage());
+ }
+ }
+
+ /**
+ * Returns the {@link IJavaProject} for a {@link IProject} object.
+ * <p/>
+ * This checks if the project has the Java Nature first.
+ * @param project
+ * @return the IJavaProject or null if the project couldn't be created or if the project
+ * does not have the Java Nature.
+ * @throws CoreException if this method fails. Reasons include:
+ * <ul><li>This project does not exist.</li><li>This project is not open.</li></ul>
+ */
+ public static IJavaProject getJavaProject(IProject project) throws CoreException {
+ if (project != null && project.hasNature(JavaCore.NATURE_ID)) {
+ return JavaCore.create(project);
+ }
+ return null;
+ }
+
+ /**
+ * Reveals a specific line in the source file defining a specified class,
+ * for a specific project.
+ * @param project
+ * @param className
+ * @param line
+ * @return true if the source was revealed
+ */
+ public static boolean revealSource(IProject project, String className, int line) {
+ // Inner classes are pointless: All we need is the enclosing type to find the file, and the
+ // line number.
+ // Since the anonymous ones will cause IJavaProject#findType to fail, we remove
+ // all of them.
+ int pos = className.indexOf('$');
+ if (pos != -1) {
+ className = className.substring(0, pos);
+ }
+
+ // get the java project
+ IJavaProject javaProject = JavaCore.create(project);
+
+ try {
+ // look for the IType matching the class name.
+ IType result = javaProject.findType(className);
+ if (result != null && result.exists()) {
+ // before we show the type in an editor window, we make sure the current
+ // workbench page has an editor area (typically the ddms perspective doesn't).
+ IWorkbench workbench = PlatformUI.getWorkbench();
+ IWorkbenchWindow window = workbench.getActiveWorkbenchWindow();
+ IWorkbenchPage page = window.getActivePage();
+ if (page.isEditorAreaVisible() == false) {
+ // no editor area? we open the java perspective.
+ new OpenJavaPerspectiveAction().run();
+ }
+
+ IEditorPart editor = JavaUI.openInEditor(result);
+ if (editor instanceof ITextEditor) {
+ // get the text editor that was just opened.
+ ITextEditor textEditor = (ITextEditor)editor;
+
+ IEditorInput input = textEditor.getEditorInput();
+
+ // get the location of the line to show.
+ IDocumentProvider documentProvider = textEditor.getDocumentProvider();
+ IDocument document = documentProvider.getDocument(input);
+ IRegion lineInfo = document.getLineInformation(line - 1);
+
+ // select and reveal the line.
+ textEditor.selectAndReveal(lineInfo.getOffset(), lineInfo.getLength());
+ }
+
+ return true;
+ }
+ } catch (JavaModelException e) {
+ } catch (PartInitException e) {
+ } catch (BadLocationException e) {
+ }
+
+ return false;
+ }
+
+ /**
+ * Returns the list of android-flagged projects. This list contains projects that are opened
+ * in the workspace and that are flagged as android project (through the android nature)
+ * @param filter an optional filter to control which android project are returned. Can be null.
+ * @return an array of IJavaProject, which can be empty if no projects match.
+ */
+ public static @NonNull IJavaProject[] getAndroidProjects(@Nullable IProjectFilter filter) {
+ IWorkspaceRoot workspaceRoot = ResourcesPlugin.getWorkspace().getRoot();
+ IJavaModel javaModel = JavaCore.create(workspaceRoot);
+
+ return getAndroidProjects(javaModel, filter);
+ }
+
+ /**
+ * Returns the list of android-flagged projects for the specified java Model.
+ * This list contains projects that are opened in the workspace and that are flagged as android
+ * project (through the android nature)
+ * @param javaModel the Java Model object corresponding for the current workspace root.
+ * @param filter an optional filter to control which android project are returned. Can be null.
+ * @return an array of IJavaProject, which can be empty if no projects match.
+ */
+ @NonNull
+ public static IJavaProject[] getAndroidProjects(@NonNull IJavaModel javaModel,
+ @Nullable IProjectFilter filter) {
+ // get the java projects
+ IJavaProject[] javaProjectList = null;
+ try {
+ javaProjectList = javaModel.getJavaProjects();
+ }
+ catch (JavaModelException jme) {
+ return new IJavaProject[0];
+ }
+
+ // temp list to build the android project array
+ ArrayList<IJavaProject> androidProjectList = new ArrayList<IJavaProject>();
+
+ // loop through the projects and add the android flagged projects to the temp list.
+ for (IJavaProject javaProject : javaProjectList) {
+ // get the workspace project object
+ IProject project = javaProject.getProject();
+
+ // check if it's an android project based on its nature
+ if (isAndroidProject(project)) {
+ if (filter == null || filter.accept(project)) {
+ androidProjectList.add(javaProject);
+ }
+ }
+ }
+
+ // return the android projects list.
+ return androidProjectList.toArray(new IJavaProject[androidProjectList.size()]);
+ }
+
+ /**
+ * Returns true if the given project is an Android project (e.g. is a Java project
+ * that also has the Android nature)
+ *
+ * @param project the project to test
+ * @return true if the given project is an Android project
+ */
+ public static boolean isAndroidProject(IProject project) {
+ // check if it's an android project based on its nature
+ try {
+ return project.hasNature(AdtConstants.NATURE_DEFAULT);
+ } catch (CoreException e) {
+ // this exception, thrown by IProject.hasNature(), means the project either doesn't
+ // exist or isn't opened. So, in any case we just skip it (the exception will
+ // bypass the ArrayList.add()
+ }
+
+ return false;
+ }
+
+ /**
+ * Returns the {@link IFolder} representing the output for the project for Android specific
+ * files.
+ * <p>
+ * The project must be a java project and be opened, or the method will return null.
+ * @param project the {@link IProject}
+ * @return an IFolder item or null.
+ */
+ public final static IFolder getJavaOutputFolder(IProject project) {
+ try {
+ if (project.isOpen() && project.hasNature(JavaCore.NATURE_ID)) {
+ // get a java project from the normal project object
+ IJavaProject javaProject = JavaCore.create(project);
+
+ IPath path = javaProject.getOutputLocation();
+ path = path.removeFirstSegments(1);
+ return project.getFolder(path);
+ }
+ } catch (JavaModelException e) {
+ // Let's do nothing and return null
+ } catch (CoreException e) {
+ // Let's do nothing and return null
+ }
+ return null;
+ }
+
+ /**
+ * Returns the {@link IFolder} representing the output for the project for compiled Java
+ * files.
+ * <p>
+ * The project must be a java project and be opened, or the method will return null.
+ * @param project the {@link IProject}
+ * @return an IFolder item or null.
+ */
+ @Nullable
+ public final static IFolder getAndroidOutputFolder(IProject project) {
+ try {
+ if (project.isOpen() && project.hasNature(JavaCore.NATURE_ID)) {
+ return project.getFolder(SdkConstants.FD_OUTPUT);
+ }
+ } catch (JavaModelException e) {
+ // Let's do nothing and return null
+ } catch (CoreException e) {
+ // Let's do nothing and return null
+ }
+ return null;
+ }
+
+}
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/project/ExportHelper.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/project/ExportHelper.java
new file mode 100644
index 000000000..56e0c0938
--- /dev/null
+++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/project/ExportHelper.java
@@ -0,0 +1,448 @@
+/*
+ * Copyright (C) 2008 The Android Open Source Project
+ *
+ * Licensed under the Eclipse Public License, Version 1.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.eclipse.org/org/documents/epl-v10.php
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.ide.eclipse.adt.internal.project;
+
+import static com.android.sdklib.internal.project.ProjectProperties.PROPERTY_SDK;
+
+import com.android.SdkConstants;
+import com.android.ide.eclipse.adt.AdtConstants;
+import com.android.ide.eclipse.adt.AdtPlugin;
+import com.android.ide.eclipse.adt.AndroidPrintStream;
+import com.android.ide.eclipse.adt.internal.build.BuildHelper;
+import com.android.ide.eclipse.adt.internal.build.DexException;
+import com.android.ide.eclipse.adt.internal.build.NativeLibInJarException;
+import com.android.ide.eclipse.adt.internal.build.ProguardExecException;
+import com.android.ide.eclipse.adt.internal.build.ProguardResultException;
+import com.android.ide.eclipse.adt.internal.preferences.AdtPrefs;
+import com.android.ide.eclipse.adt.internal.sdk.ProjectState;
+import com.android.ide.eclipse.adt.internal.sdk.Sdk;
+import com.android.ide.eclipse.adt.io.IFileWrapper;
+import com.android.sdklib.BuildToolInfo;
+import com.android.sdklib.build.ApkCreationException;
+import com.android.sdklib.build.DuplicateFileException;
+import com.android.sdklib.internal.project.ProjectProperties;
+import com.android.tools.lint.detector.api.LintUtils;
+import com.android.xml.AndroidManifest;
+
+import org.eclipse.core.resources.IFile;
+import org.eclipse.core.resources.IFolder;
+import org.eclipse.core.resources.IProject;
+import org.eclipse.core.resources.IResource;
+import org.eclipse.core.resources.IncrementalProjectBuilder;
+import org.eclipse.core.runtime.CoreException;
+import org.eclipse.core.runtime.IProgressMonitor;
+import org.eclipse.core.runtime.IStatus;
+import org.eclipse.core.runtime.Status;
+import org.eclipse.core.runtime.jobs.Job;
+import org.eclipse.jdt.core.IJavaProject;
+import org.eclipse.jdt.core.JavaCore;
+import org.eclipse.swt.SWT;
+import org.eclipse.swt.widgets.Display;
+import org.eclipse.swt.widgets.FileDialog;
+import org.eclipse.swt.widgets.Shell;
+
+import java.io.BufferedInputStream;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.OutputStream;
+import java.security.PrivateKey;
+import java.security.cert.X509Certificate;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.List;
+import java.util.jar.JarEntry;
+import java.util.jar.JarOutputStream;
+
+/**
+ * Export helper to export release version of APKs.
+ */
+public final class ExportHelper {
+ private static final String HOME_PROPERTY = "user.home"; //$NON-NLS-1$
+ private static final String HOME_PROPERTY_REF = "${" + HOME_PROPERTY + '}'; //$NON-NLS-1$
+ private static final String SDK_PROPERTY_REF = "${" + PROPERTY_SDK + '}'; //$NON-NLS-1$
+ private final static String TEMP_PREFIX = "android_"; //$NON-NLS-1$
+
+ /**
+ * Exports a release version of the application created by the given project.
+ * @param project the project to export
+ * @param outputFile the file to write
+ * @param key the key to used for signing. Can be null.
+ * @param certificate the certificate used for signing. Can be null.
+ * @param monitor progress monitor
+ * @throws CoreException if an error occurs
+ */
+ public static void exportReleaseApk(IProject project, File outputFile, PrivateKey key,
+ X509Certificate certificate, IProgressMonitor monitor) throws CoreException {
+
+ // the export, takes the output of the precompiler & Java builders so it's
+ // important to call build in case the auto-build option of the workspace is disabled.
+ // Also enable dependency building to make sure everything is up to date.
+ // However do not package the APK since we're going to do it manually here, using a
+ // different output location.
+ ProjectHelper.compileInReleaseMode(project, monitor);
+
+ // if either key or certificate is null, ensure the other is null.
+ if (key == null) {
+ certificate = null;
+ } else if (certificate == null) {
+ key = null;
+ }
+
+ try {
+ // check if the manifest declares debuggable as true. While this is a release build,
+ // debuggable in the manifest will override this and generate a debug build
+ IResource manifestResource = project.findMember(SdkConstants.FN_ANDROID_MANIFEST_XML);
+ if (manifestResource.getType() != IResource.FILE) {
+ throw new CoreException(new Status(IStatus.ERROR, AdtPlugin.PLUGIN_ID,
+ String.format("%1$s missing.", SdkConstants.FN_ANDROID_MANIFEST_XML)));
+ }
+
+ IFileWrapper manifestFile = new IFileWrapper((IFile) manifestResource);
+ boolean debugMode = AndroidManifest.getDebuggable(manifestFile);
+
+ AndroidPrintStream fakeStream = new AndroidPrintStream(null, null, new OutputStream() {
+ @Override
+ public void write(int b) throws IOException {
+ // do nothing
+ }
+ });
+
+ ProjectState projectState = Sdk.getProjectState(project);
+
+ // get the jumbo mode option
+ String forceJumboStr = projectState.getProperty(AdtConstants.DEX_OPTIONS_FORCEJUMBO);
+ Boolean jumbo = Boolean.valueOf(forceJumboStr);
+
+ String dexMergerStr = projectState.getProperty(AdtConstants.DEX_OPTIONS_DISABLE_MERGER);
+ Boolean dexMerger = Boolean.valueOf(dexMergerStr);
+
+ BuildToolInfo buildToolInfo = getBuildTools(projectState);
+
+ BuildHelper helper = new BuildHelper(
+ projectState,
+ buildToolInfo,
+ fakeStream, fakeStream,
+ jumbo.booleanValue(),
+ dexMerger.booleanValue(),
+ debugMode, false /*verbose*/,
+ null /*resourceMarker*/);
+
+ // get the list of library projects
+ List<IProject> libProjects = projectState.getFullLibraryProjects();
+
+ // Step 1. Package the resources.
+
+ // tmp file for the packaged resource file. To not disturb the incremental builders
+ // output, all intermediary files are created in tmp files.
+ File resourceFile = File.createTempFile(TEMP_PREFIX, SdkConstants.DOT_RES);
+ resourceFile.deleteOnExit();
+
+ // Make sure the PNG crunch cache is up to date
+ helper.updateCrunchCache();
+
+ // get the merged manifest
+ IFolder androidOutputFolder = BaseProjectHelper.getAndroidOutputFolder(project);
+ IFile mergedManifestFile = androidOutputFolder.getFile(
+ SdkConstants.FN_ANDROID_MANIFEST_XML);
+
+
+ // package the resources.
+ helper.packageResources(
+ mergedManifestFile,
+ libProjects,
+ null, // res filter
+ 0, // versionCode
+ resourceFile.getParent(),
+ resourceFile.getName());
+
+ // Step 2. Convert the byte code to Dalvik bytecode
+
+ // tmp file for the packaged resource file.
+ File dexFile = File.createTempFile(TEMP_PREFIX, SdkConstants.DOT_DEX);
+ dexFile.deleteOnExit();
+
+ ProjectState state = Sdk.getProjectState(project);
+ String proguardConfig = state.getProperties().getProperty(
+ ProjectProperties.PROPERTY_PROGUARD_CONFIG);
+
+ boolean runProguard = false;
+ List<File> proguardConfigFiles = null;
+ if (proguardConfig != null && proguardConfig.length() > 0) {
+ // Be tolerant with respect to file and path separators just like
+ // Ant is. Allow "/" in the property file to mean whatever the file
+ // separator character is:
+ if (File.separatorChar != '/' && proguardConfig.indexOf('/') != -1) {
+ proguardConfig = proguardConfig.replace('/', File.separatorChar);
+ }
+
+ Iterable<String> paths = LintUtils.splitPath(proguardConfig);
+ for (String path : paths) {
+ if (path.startsWith(SDK_PROPERTY_REF)) {
+ path = AdtPrefs.getPrefs().getOsSdkFolder() +
+ path.substring(SDK_PROPERTY_REF.length());
+ } else if (path.startsWith(HOME_PROPERTY_REF)) {
+ path = System.getProperty(HOME_PROPERTY) +
+ path.substring(HOME_PROPERTY_REF.length());
+ }
+ File proguardConfigFile = new File(path);
+ if (proguardConfigFile.isAbsolute() == false) {
+ proguardConfigFile = new File(project.getLocation().toFile(), path);
+ }
+ if (proguardConfigFile.isFile()) {
+ if (proguardConfigFiles == null) {
+ proguardConfigFiles = new ArrayList<File>();
+ }
+ proguardConfigFiles.add(proguardConfigFile);
+ runProguard = true;
+ } else {
+ throw new CoreException(new Status(IStatus.ERROR, AdtPlugin.PLUGIN_ID,
+ "Invalid proguard configuration file path " + proguardConfigFile
+ + " does not exist or is not a regular file", null));
+ }
+ }
+
+ // get the proguard file output by aapt
+ if (proguardConfigFiles != null) {
+ IFile proguardFile = androidOutputFolder.getFile(AdtConstants.FN_AAPT_PROGUARD);
+ proguardConfigFiles.add(proguardFile.getLocation().toFile());
+ }
+ }
+
+ Collection<String> dxInput;
+
+ if (runProguard) {
+ // get all the compiled code paths. This will contain both project output
+ // folder and jar files.
+ Collection<String> paths = helper.getCompiledCodePaths();
+
+ // create a jar file containing all the project output (as proguard cannot
+ // process folders of .class files).
+ File inputJar = File.createTempFile(TEMP_PREFIX, SdkConstants.DOT_JAR);
+ inputJar.deleteOnExit();
+ JarOutputStream jos = new JarOutputStream(new FileOutputStream(inputJar));
+
+ // a list of the other paths (jar files.)
+ List<String> jars = new ArrayList<String>();
+
+ for (String path : paths) {
+ File root = new File(path);
+ if (root.isDirectory()) {
+ addFileToJar(jos, root, root);
+ } else if (root.isFile()) {
+ jars.add(path);
+ }
+ }
+ jos.close();
+
+ // destination file for proguard
+ File obfuscatedJar = File.createTempFile(TEMP_PREFIX, SdkConstants.DOT_JAR);
+ obfuscatedJar.deleteOnExit();
+
+ // run proguard
+ helper.runProguard(proguardConfigFiles, inputJar, jars, obfuscatedJar,
+ new File(project.getLocation().toFile(), SdkConstants.FD_PROGUARD));
+
+ helper.setProguardOutput(obfuscatedJar.getAbsolutePath());
+
+ // dx input is proguard's output
+ dxInput = Collections.singletonList(obfuscatedJar.getAbsolutePath());
+ } else {
+ // no proguard, simply get all the compiled code path: project output(s) +
+ // jar file(s)
+ dxInput = helper.getCompiledCodePaths();
+ }
+
+ IJavaProject javaProject = JavaCore.create(project);
+
+ helper.executeDx(javaProject, dxInput, dexFile.getAbsolutePath());
+
+ // Step 3. Final package
+
+ helper.finalPackage(
+ resourceFile.getAbsolutePath(),
+ dexFile.getAbsolutePath(),
+ outputFile.getAbsolutePath(),
+ libProjects,
+ key,
+ certificate,
+ null); //resourceMarker
+
+ // success!
+ } catch (CoreException e) {
+ throw e;
+ } catch (ProguardResultException e) {
+ String msg = String.format("Proguard returned with error code %d. See console",
+ e.getErrorCode());
+ AdtPlugin.printErrorToConsole(project, msg);
+ AdtPlugin.printErrorToConsole(project, (Object[]) e.getOutput());
+ throw new CoreException(new Status(IStatus.ERROR, AdtPlugin.PLUGIN_ID,
+ msg, e));
+ } catch (ProguardExecException e) {
+ String msg = String.format("Failed to run proguard: %s", e.getMessage());
+ AdtPlugin.printErrorToConsole(project, msg);
+ throw new CoreException(new Status(IStatus.ERROR, AdtPlugin.PLUGIN_ID,
+ msg, e));
+ } catch (DuplicateFileException e) {
+ String msg = String.format(
+ "Found duplicate file for APK: %1$s\nOrigin 1: %2$s\nOrigin 2: %3$s",
+ e.getArchivePath(), e.getFile1(), e.getFile2());
+ AdtPlugin.printErrorToConsole(project, msg);
+ throw new CoreException(new Status(IStatus.ERROR, AdtPlugin.PLUGIN_ID,
+ e.getMessage(), e));
+ } catch (NativeLibInJarException e) {
+ String msg = e.getMessage();
+
+ AdtPlugin.printErrorToConsole(project, msg);
+ AdtPlugin.printErrorToConsole(project, (Object[]) e.getAdditionalInfo());
+ throw new CoreException(new Status(IStatus.ERROR, AdtPlugin.PLUGIN_ID,
+ e.getMessage(), e));
+ } catch (DexException e) {
+ throw new CoreException(new Status(IStatus.ERROR, AdtPlugin.PLUGIN_ID,
+ e.getMessage(), e));
+ } catch (ApkCreationException e) {
+ throw new CoreException(new Status(IStatus.ERROR, AdtPlugin.PLUGIN_ID,
+ e.getMessage(), e));
+ } catch (Exception e) {
+ throw new CoreException(new Status(IStatus.ERROR, AdtPlugin.PLUGIN_ID,
+ "Failed to export application", e));
+ } finally {
+ // move back to a debug build.
+ // By using a normal build, we'll simply rebuild the debug version, and let the
+ // builder decide whether to build the full package or not.
+ ProjectHelper.buildWithDeps(project, IncrementalProjectBuilder.FULL_BUILD, monitor);
+ project.refreshLocal(IResource.DEPTH_INFINITE, monitor);
+ }
+ }
+
+ public static BuildToolInfo getBuildTools(ProjectState projectState)
+ throws CoreException {
+ BuildToolInfo buildToolInfo = projectState.getBuildToolInfo();
+ if (buildToolInfo == null) {
+ buildToolInfo = Sdk.getCurrent().getLatestBuildTool();
+ }
+
+ if (buildToolInfo == null) {
+ throw new CoreException(new Status(IStatus.ERROR, AdtPlugin.PLUGIN_ID,
+ "No Build Tools installed in the SDK."));
+ }
+ return buildToolInfo;
+ }
+
+ /**
+ * Exports an unsigned release APK after prompting the user for a location.
+ *
+ * <strong>Must be called from the UI thread.</strong>
+ *
+ * @param project the project to export
+ */
+ public static void exportUnsignedReleaseApk(final IProject project) {
+ Shell shell = Display.getCurrent().getActiveShell();
+
+ // create a default file name for the apk.
+ String fileName = project.getName() + SdkConstants.DOT_ANDROID_PACKAGE;
+
+ // Pop up the file save window to get the file location
+ FileDialog fileDialog = new FileDialog(shell, SWT.SAVE);
+
+ fileDialog.setText("Export Project");
+ fileDialog.setFileName(fileName);
+
+ final String saveLocation = fileDialog.open();
+ if (saveLocation != null) {
+ new Job("Android Release Export") {
+ @Override
+ protected IStatus run(IProgressMonitor monitor) {
+ try {
+ exportReleaseApk(project,
+ new File(saveLocation),
+ null, //key
+ null, //certificate
+ monitor);
+
+ // this is unsigned export. Let's tell the developers to run zip align
+ AdtPlugin.displayWarning("Android IDE Plug-in", String.format(
+ "An unsigned package of the application was saved at\n%1$s\n\n" +
+ "Before publishing the application you will need to:\n" +
+ "- Sign the application with your release key,\n" +
+ "- run zipalign on the signed package. ZipAlign is located in <SDK>/tools/\n\n" +
+ "Aligning applications allows Android to use application resources\n" +
+ "more efficiently.", saveLocation));
+
+ return Status.OK_STATUS;
+ } catch (CoreException e) {
+ AdtPlugin.displayError("Android IDE Plug-in", String.format(
+ "Error exporting application:\n\n%1$s", e.getMessage()));
+ return e.getStatus();
+ }
+ }
+ }.schedule();
+ }
+ }
+
+ /**
+ * Adds a file to a jar file.
+ * The <var>rootDirectory</var> dictates the path of the file inside the jar file. It must be
+ * a parent of <var>file</var>.
+ * @param jar the jar to add the file to
+ * @param file the file to add
+ * @param rootDirectory the rootDirectory.
+ * @throws IOException
+ */
+ private static void addFileToJar(JarOutputStream jar, File file, File rootDirectory)
+ throws IOException {
+ if (file.isDirectory()) {
+ if (file.getName().equals("META-INF") == false) {
+ for (File child: file.listFiles()) {
+ addFileToJar(jar, child, rootDirectory);
+ }
+ }
+ } else if (file.isFile()) {
+ String rootPath = rootDirectory.getAbsolutePath();
+ String path = file.getAbsolutePath();
+ path = path.substring(rootPath.length()).replace("\\", "/"); //$NON-NLS-1$ //$NON-NLS-2$
+ if (path.charAt(0) == '/') {
+ path = path.substring(1);
+ }
+
+ JarEntry entry = new JarEntry(path);
+ entry.setTime(file.lastModified());
+ jar.putNextEntry(entry);
+
+ // put the content of the file.
+ byte[] buffer = new byte[1024];
+ int count;
+ BufferedInputStream bis = null;
+ try {
+ bis = new BufferedInputStream(new FileInputStream(file));
+ while ((count = bis.read(buffer)) != -1) {
+ jar.write(buffer, 0, count);
+ }
+ } finally {
+ if (bis != null) {
+ try {
+ bis.close();
+ } catch (IOException ignore) {
+ }
+ }
+ }
+ jar.closeEntry();
+ }
+ }
+}
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/project/FixLaunchConfig.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/project/FixLaunchConfig.java
new file mode 100644
index 000000000..e311bfb0b
--- /dev/null
+++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/project/FixLaunchConfig.java
@@ -0,0 +1,156 @@
+/*
+ * Copyright (C) 2007 The Android Open Source Project
+ *
+ * Licensed under the Eclipse Public License, Version 1.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.eclipse.org/org/documents/epl-v10.php
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.ide.eclipse.adt.internal.project;
+
+import com.android.ide.eclipse.adt.AdtPlugin;
+import com.android.ide.eclipse.adt.internal.launch.LaunchConfigDelegate;
+
+import org.eclipse.core.resources.IProject;
+import org.eclipse.core.runtime.CoreException;
+import org.eclipse.debug.core.DebugPlugin;
+import org.eclipse.debug.core.ILaunchConfiguration;
+import org.eclipse.debug.core.ILaunchConfigurationType;
+import org.eclipse.debug.core.ILaunchConfigurationWorkingCopy;
+import org.eclipse.debug.core.ILaunchManager;
+import org.eclipse.jdt.launching.IJavaLaunchConfigurationConstants;
+
+import java.util.ArrayList;
+
+/**
+ * Class to fix the launch configuration of a project if the java package
+ * defined in the manifest has been changed.<br>
+ * This fix can be done synchronously, or asynchronously.<br>
+ * <code>start()</code> will start a thread that will do the fix.<br>
+ * <code>run()</code> will do the fix in the current thread.<br><br>
+ * By default, the fix first display a dialog to the user asking if he/she wants to
+ * do the fix. This can be overriden by calling <code>setDisplayPrompt(false)</code>.
+ *
+ */
+public class FixLaunchConfig extends Thread {
+
+ private IProject mProject;
+ private String mOldPackage;
+ private String mNewPackage;
+
+ private boolean mDisplayPrompt = true;
+
+ public FixLaunchConfig(IProject project, String oldPackage, String newPackage) {
+ super();
+
+ mProject = project;
+ mOldPackage = oldPackage;
+ mNewPackage = newPackage;
+ }
+
+ /**
+ * Set the display prompt. If true run()/start() first ask the user if he/she wants
+ * to fix the Launch Config
+ * @param displayPrompt
+ */
+ public void setDisplayPrompt(boolean displayPrompt) {
+ mDisplayPrompt = displayPrompt;
+ }
+
+ /**
+ * Fix the Launch configurations.
+ */
+ @Override
+ public void run() {
+
+ if (mDisplayPrompt) {
+ // ask the user if he really wants to fix the launch config
+ boolean res = AdtPlugin.displayPrompt(
+ "Launch Configuration Update",
+ "The package definition in the manifest changed.\nDo you want to update your Launch Configuration(s)?");
+
+ if (res == false) {
+ return;
+ }
+ }
+
+ // get the list of config for the project
+ String projectName = mProject.getName();
+ ILaunchConfiguration[] configs = findConfigs(mProject.getName());
+
+ // loop through all the config and update the package
+ for (ILaunchConfiguration config : configs) {
+ try {
+ // get the working copy so that we can make changes.
+ ILaunchConfigurationWorkingCopy copy = config.getWorkingCopy();
+
+ // get the attributes for the activity
+ String activity = config.getAttribute(LaunchConfigDelegate.ATTR_ACTIVITY,
+ ""); //$NON-NLS-1$
+
+ // manifests can define activities that are not in the defined package,
+ // so we need to make sure the activity is inside the old package.
+ if (activity.startsWith(mOldPackage)) {
+ // create the new activity
+ activity = mNewPackage + activity.substring(mOldPackage.length());
+
+ // put it in the copy
+ copy.setAttribute(LaunchConfigDelegate.ATTR_ACTIVITY, activity);
+
+ // save the config
+ copy.doSave();
+ }
+ } catch (CoreException e) {
+ // couldn't get the working copy. we output the error in the console
+ String msg = String.format("Failed to modify %1$s: %2$s", projectName,
+ e.getMessage());
+ AdtPlugin.printErrorToConsole(mProject, msg);
+ }
+ }
+
+ }
+
+ /**
+ * Looks for and returns all existing Launch Configuration object for a
+ * specified project.
+ * @param projectName The name of the project
+ * @return all the ILaunchConfiguration object. If none are present, an empty array is
+ * returned.
+ */
+ private static ILaunchConfiguration[] findConfigs(String projectName) {
+ // get the launch manager
+ ILaunchManager manager = DebugPlugin.getDefault().getLaunchManager();
+
+ // now get the config type for our particular android type.
+ ILaunchConfigurationType configType = manager.
+ getLaunchConfigurationType(LaunchConfigDelegate.ANDROID_LAUNCH_TYPE_ID);
+
+ // create a temp list to hold all the valid configs
+ ArrayList<ILaunchConfiguration> list = new ArrayList<ILaunchConfiguration>();
+
+ try {
+ ILaunchConfiguration[] configs = manager.getLaunchConfigurations(configType);
+
+ for (ILaunchConfiguration config : configs) {
+ if (config.getAttribute(
+ IJavaLaunchConfigurationConstants.ATTR_PROJECT_NAME,
+ "").equals(projectName)) { //$NON-NLS-1$
+ list.add(config);
+ }
+ }
+ } catch (CoreException e) {
+ }
+
+ return list.toArray(new ILaunchConfiguration[list.size()]);
+
+ }
+
+}
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/project/FolderDecorator.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/project/FolderDecorator.java
new file mode 100644
index 000000000..054890f86
--- /dev/null
+++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/project/FolderDecorator.java
@@ -0,0 +1,109 @@
+/*
+ * Copyright (C) 2008 The Android Open Source Project
+ *
+ * Licensed under the Eclipse Public License, Version 1.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.eclipse.org/org/documents/epl-v10.php
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.ide.eclipse.adt.internal.project;
+
+import com.android.SdkConstants;
+import com.android.ide.eclipse.adt.AdtConstants;
+import com.android.ide.eclipse.adt.AdtPlugin;
+
+import org.eclipse.core.resources.IFolder;
+import org.eclipse.core.resources.IProject;
+import org.eclipse.core.resources.IResource;
+import org.eclipse.core.runtime.CoreException;
+import org.eclipse.jface.resource.ImageDescriptor;
+import org.eclipse.jface.viewers.IDecoration;
+import org.eclipse.jface.viewers.ILabelDecorator;
+import org.eclipse.jface.viewers.ILabelProviderListener;
+import org.eclipse.jface.viewers.ILightweightLabelDecorator;
+
+/**
+ * A {@link ILabelDecorator} associated with an org.eclipse.ui.decorators extension.
+ * This is used to add android icons in some special folders in the package explorer.
+ */
+public class FolderDecorator implements ILightweightLabelDecorator {
+
+ private ImageDescriptor mDescriptor;
+
+ public FolderDecorator() {
+ mDescriptor = AdtPlugin.getImageDescriptor("/icons/android_project.png"); //$NON-NLS-1$
+ }
+
+ @Override
+ public void decorate(Object element, IDecoration decoration) {
+ if (element instanceof IFolder) {
+ IFolder folder = (IFolder)element;
+
+ // get the project and make sure this is an android project
+ IProject project = folder.getProject();
+ if (project == null || !project.exists() || !folder.exists()) {
+ return;
+ }
+
+ try {
+ if (project.hasNature(AdtConstants.NATURE_DEFAULT)) {
+ // check the folder is directly under the project.
+ if (folder.getParent().getType() == IResource.PROJECT) {
+ String name = folder.getName();
+ if (name.equals(SdkConstants.FD_ASSETS)) {
+ doDecoration(decoration, null);
+ } else if (name.equals(SdkConstants.FD_RESOURCES)) {
+ doDecoration(decoration, null);
+ } else if (name.equals(SdkConstants.FD_GEN_SOURCES)) {
+ doDecoration(decoration, " [Generated Java Files]");
+ } else if (name.equals(SdkConstants.FD_NATIVE_LIBS)) {
+ doDecoration(decoration, null);
+ } else if (name.equals(SdkConstants.FD_OUTPUT)) {
+ doDecoration(decoration, null);
+ }
+ }
+ }
+ } catch (CoreException e) {
+ // log the error
+ AdtPlugin.log(e, "Unable to get nature of project '%s'.", project.getName());
+ }
+ }
+ }
+
+ public void doDecoration(IDecoration decoration, String suffix) {
+ decoration.addOverlay(mDescriptor, IDecoration.TOP_LEFT);
+
+ if (suffix != null) {
+ decoration.addSuffix(suffix);
+ }
+ }
+
+ @Override
+ public boolean isLabelProperty(Object element, String property) {
+ // Property change do not affect the label
+ return false;
+ }
+
+ @Override
+ public void addListener(ILabelProviderListener listener) {
+ // No state change will affect the rendering.
+ }
+
+ @Override
+ public void removeListener(ILabelProviderListener listener) {
+ // No state change will affect the rendering.
+ }
+
+ @Override
+ public void dispose() {
+ // nothing to dispose
+ }
+}
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/project/LibraryClasspathContainerInitializer.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/project/LibraryClasspathContainerInitializer.java
new file mode 100644
index 000000000..8fbee4089
--- /dev/null
+++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/project/LibraryClasspathContainerInitializer.java
@@ -0,0 +1,641 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Eclipse Public License, Version 1.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.eclipse.org/org/documents/epl-v10.php
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.ide.eclipse.adt.internal.project;
+
+import static com.android.ide.eclipse.adt.AdtConstants.CONTAINER_DEPENDENCIES;
+
+import com.android.SdkConstants;
+import com.android.ide.common.sdk.LoadStatus;
+import com.android.ide.eclipse.adt.AdtConstants;
+import com.android.ide.eclipse.adt.AdtPlugin;
+import com.android.ide.eclipse.adt.AndroidPrintStream;
+import com.android.ide.eclipse.adt.internal.sdk.ProjectState;
+import com.android.ide.eclipse.adt.internal.sdk.Sdk;
+import com.android.sdklib.BuildToolInfo;
+import com.android.sdklib.build.JarListSanitizer;
+import com.android.sdklib.build.JarListSanitizer.DifferentLibException;
+import com.android.sdklib.build.JarListSanitizer.Sha1Exception;
+import com.android.sdklib.build.RenderScriptProcessor;
+
+import org.eclipse.core.resources.IFile;
+import org.eclipse.core.resources.IFolder;
+import org.eclipse.core.resources.IProject;
+import org.eclipse.core.resources.IResource;
+import org.eclipse.core.resources.IWorkspaceRoot;
+import org.eclipse.core.resources.ResourcesPlugin;
+import org.eclipse.core.runtime.CoreException;
+import org.eclipse.core.runtime.IPath;
+import org.eclipse.core.runtime.NullProgressMonitor;
+import org.eclipse.core.runtime.Path;
+import org.eclipse.jdt.core.IAccessRule;
+import org.eclipse.jdt.core.IClasspathAttribute;
+import org.eclipse.jdt.core.IClasspathContainer;
+import org.eclipse.jdt.core.IClasspathEntry;
+import org.eclipse.jdt.core.IJavaProject;
+import org.eclipse.jdt.core.JavaCore;
+import org.eclipse.jdt.core.JavaModelException;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileNotFoundException;
+import java.io.IOException;
+import java.io.InputStream;
+import java.net.MalformedURLException;
+import java.util.ArrayList;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Properties;
+import java.util.Set;
+
+public class LibraryClasspathContainerInitializer extends BaseClasspathContainerInitializer {
+
+ private final static String ATTR_SRC = "src"; //$NON-NLS-1$
+ private final static String ATTR_DOC = "doc"; //$NON-NLS-1$
+ private final static String DOT_PROPERTIES = ".properties"; //$NON-NLS-1$
+
+ public LibraryClasspathContainerInitializer() {
+ }
+
+ /**
+ * Updates the {@link IJavaProject} objects with new library.
+ * @param androidProjects the projects to update.
+ * @return <code>true</code> if success, <code>false</code> otherwise.
+ */
+ public static boolean updateProjects(IJavaProject[] androidProjects) {
+ try {
+ // Allocate a new AndroidClasspathContainer, and associate it to the library
+ // container id for each projects.
+ int projectCount = androidProjects.length;
+
+ IClasspathContainer[] libraryContainers = new IClasspathContainer[projectCount];
+ IClasspathContainer[] dependencyContainers = new IClasspathContainer[projectCount];
+ for (int i = 0 ; i < projectCount; i++) {
+ libraryContainers[i] = allocateLibraryContainer(androidProjects[i]);
+ dependencyContainers[i] = allocateDependencyContainer(androidProjects[i]);
+ }
+
+ // give each project their new container in one call.
+ JavaCore.setClasspathContainer(
+ new Path(AdtConstants.CONTAINER_PRIVATE_LIBRARIES),
+ androidProjects, libraryContainers, new NullProgressMonitor());
+
+ JavaCore.setClasspathContainer(
+ new Path(AdtConstants.CONTAINER_DEPENDENCIES),
+ androidProjects, dependencyContainers, new NullProgressMonitor());
+ return true;
+ } catch (JavaModelException e) {
+ return false;
+ }
+ }
+
+ /**
+ * Updates the {@link IJavaProject} objects with new library.
+ * @param androidProjects the projects to update.
+ * @return <code>true</code> if success, <code>false</code> otherwise.
+ */
+ public static boolean updateProject(List<ProjectState> projects) {
+ List<IJavaProject> javaProjectList = new ArrayList<IJavaProject>(projects.size());
+ for (ProjectState p : projects) {
+ IJavaProject javaProject = JavaCore.create(p.getProject());
+ if (javaProject != null) {
+ javaProjectList.add(javaProject);
+ }
+ }
+
+ IJavaProject[] javaProjects = javaProjectList.toArray(
+ new IJavaProject[javaProjectList.size()]);
+
+ return updateProjects(javaProjects);
+ }
+
+ @Override
+ public void initialize(IPath containerPath, IJavaProject project) throws CoreException {
+ if (AdtConstants.CONTAINER_PRIVATE_LIBRARIES.equals(containerPath.toString())) {
+ IClasspathContainer libraries = allocateLibraryContainer(project);
+ if (libraries != null) {
+ JavaCore.setClasspathContainer(new Path(AdtConstants.CONTAINER_PRIVATE_LIBRARIES),
+ new IJavaProject[] { project },
+ new IClasspathContainer[] { libraries },
+ new NullProgressMonitor());
+ }
+
+ } else if(AdtConstants.CONTAINER_DEPENDENCIES.equals(containerPath.toString())) {
+ IClasspathContainer dependencies = allocateDependencyContainer(project);
+ if (dependencies != null) {
+ JavaCore.setClasspathContainer(new Path(AdtConstants.CONTAINER_DEPENDENCIES),
+ new IJavaProject[] { project },
+ new IClasspathContainer[] { dependencies },
+ new NullProgressMonitor());
+ }
+ }
+ }
+
+ private static IClasspathContainer allocateLibraryContainer(IJavaProject javaProject) {
+ final IProject iProject = javaProject.getProject();
+
+ // check if the project has a valid target.
+ ProjectState state = Sdk.getProjectState(iProject);
+ if (state == null) {
+ // getProjectState should already have logged an error. Just bail out.
+ return null;
+ }
+
+ /*
+ * At this point we're going to gather a list of all that need to go in the
+ * dependency container.
+ * - Library project outputs (direct and indirect)
+ * - Java project output (those can be indirectly referenced through library projects
+ * or other other Java projects)
+ * - Jar files:
+ * + inside this project's libs/
+ * + inside the library projects' libs/
+ * + inside the referenced Java projects' classpath
+ */
+ List<IClasspathEntry> entries = new ArrayList<IClasspathEntry>();
+
+ // list of java project dependencies and jar files that will be built while
+ // going through the library projects.
+ Set<File> jarFiles = new HashSet<File>();
+ Set<IProject> refProjects = new HashSet<IProject>();
+
+ // process all the libraries
+
+ List<IProject> libProjects = state.getFullLibraryProjects();
+ for (IProject libProject : libProjects) {
+ // process all of the library project's dependencies
+ getDependencyListFromClasspath(libProject, refProjects, jarFiles, true);
+ }
+
+ // now process this projects' referenced projects only.
+ processReferencedProjects(iProject, refProjects, jarFiles);
+
+ // and the content of its libs folder
+ getJarListFromLibsFolder(iProject, jarFiles);
+
+ // now add a classpath entry for each Java project (this is a set so dups are already
+ // removed)
+ for (IProject p : refProjects) {
+ entries.add(JavaCore.newProjectEntry(p.getFullPath(), true /*isExported*/));
+ }
+
+ entries.addAll(convertJarsToClasspathEntries(iProject, jarFiles));
+
+ return allocateContainer(javaProject, entries, new Path(AdtConstants.CONTAINER_PRIVATE_LIBRARIES),
+ "Android Private Libraries");
+ }
+
+ private static List<IClasspathEntry> convertJarsToClasspathEntries(final IProject iProject,
+ Set<File> jarFiles) {
+ List<IClasspathEntry> entries = new ArrayList<IClasspathEntry>(jarFiles.size());
+
+ // and process the jar files list, but first sanitize it to remove dups.
+ JarListSanitizer sanitizer = new JarListSanitizer(
+ iProject.getFolder(SdkConstants.FD_OUTPUT).getLocation().toFile(),
+ new AndroidPrintStream(iProject, null /*prefix*/,
+ AdtPlugin.getOutStream()));
+
+ String errorMessage = null;
+
+ try {
+ List<File> sanitizedList = sanitizer.sanitize(jarFiles);
+
+ for (File jarFile : sanitizedList) {
+ if (jarFile instanceof CPEFile) {
+ CPEFile cpeFile = (CPEFile) jarFile;
+ IClasspathEntry e = cpeFile.getClasspathEntry();
+
+ entries.add(JavaCore.newLibraryEntry(
+ e.getPath(),
+ e.getSourceAttachmentPath(),
+ e.getSourceAttachmentRootPath(),
+ e.getAccessRules(),
+ e.getExtraAttributes(),
+ true /*isExported*/));
+ } else {
+ String jarPath = jarFile.getAbsolutePath();
+
+ IPath sourceAttachmentPath = null;
+ IClasspathAttribute javaDocAttribute = null;
+
+ File jarProperties = new File(jarPath + DOT_PROPERTIES);
+ if (jarProperties.isFile()) {
+ Properties p = new Properties();
+ InputStream is = null;
+ try {
+ p.load(is = new FileInputStream(jarProperties));
+
+ String value = p.getProperty(ATTR_SRC);
+ if (value != null) {
+ File srcPath = getFile(jarFile, value);
+
+ if (srcPath.exists()) {
+ sourceAttachmentPath = new Path(srcPath.getAbsolutePath());
+ }
+ }
+
+ value = p.getProperty(ATTR_DOC);
+ if (value != null) {
+ File docPath = getFile(jarFile, value);
+ if (docPath.exists()) {
+ try {
+ javaDocAttribute = JavaCore.newClasspathAttribute(
+ IClasspathAttribute.JAVADOC_LOCATION_ATTRIBUTE_NAME,
+ docPath.toURI().toURL().toString());
+ } catch (MalformedURLException e) {
+ AdtPlugin.log(e, "Failed to process 'doc' attribute for %s",
+ jarProperties.getAbsolutePath());
+ }
+ }
+ }
+
+ } catch (FileNotFoundException e) {
+ // shouldn't happen since we check upfront
+ } catch (IOException e) {
+ AdtPlugin.log(e, "Failed to read %s", jarProperties.getAbsolutePath());
+ } finally {
+ if (is != null) {
+ try {
+ is.close();
+ } catch (IOException e) {
+ // ignore
+ }
+ }
+ }
+ }
+
+ if (javaDocAttribute != null) {
+ entries.add(JavaCore.newLibraryEntry(new Path(jarPath),
+ sourceAttachmentPath, null /*sourceAttachmentRootPath*/,
+ new IAccessRule[0],
+ new IClasspathAttribute[] { javaDocAttribute },
+ true /*isExported*/));
+ } else {
+ entries.add(JavaCore.newLibraryEntry(new Path(jarPath),
+ sourceAttachmentPath, null /*sourceAttachmentRootPath*/,
+ true /*isExported*/));
+ }
+ }
+ }
+ } catch (DifferentLibException e) {
+ errorMessage = e.getMessage();
+ AdtPlugin.printErrorToConsole(iProject, (Object[]) e.getDetails());
+ } catch (Sha1Exception e) {
+ errorMessage = e.getMessage();
+ }
+
+ processError(iProject, errorMessage, AdtConstants.MARKER_DEPENDENCY,
+ true /*outputToConsole*/);
+
+ return entries;
+ }
+
+ private static IClasspathContainer allocateDependencyContainer(IJavaProject javaProject) {
+ final IProject iProject = javaProject.getProject();
+ final List<IClasspathEntry> entries = new ArrayList<IClasspathEntry>();
+ final Set<File> jarFiles = new HashSet<File>();
+ final IWorkspaceRoot workspaceRoot = ResourcesPlugin.getWorkspace().getRoot();
+
+ AdtPlugin plugin = AdtPlugin.getDefault();
+ if (plugin == null) { // This is totally weird, but I've seen it happen!
+ return null;
+ }
+
+ synchronized (Sdk.getLock()) {
+ boolean sdkIsLoaded = plugin.getSdkLoadStatus() == LoadStatus.LOADED;
+
+ // check if the project has a valid target.
+ final ProjectState state = Sdk.getProjectState(iProject);
+ if (state == null) {
+ // getProjectState should already have logged an error. Just bail out.
+ return null;
+ }
+
+ // annotations support for older version of android
+ if (state.getTarget() != null && state.getTarget().getVersion().getApiLevel() <= 15) {
+ File annotationsJar = new File(Sdk.getCurrent().getSdkOsLocation(),
+ SdkConstants.FD_TOOLS + File.separator + SdkConstants.FD_SUPPORT +
+ File.separator + SdkConstants.FN_ANNOTATIONS_JAR);
+
+ jarFiles.add(annotationsJar);
+ }
+
+ if (state.getRenderScriptSupportMode()) {
+ if (!sdkIsLoaded) {
+ return null;
+ }
+ BuildToolInfo buildToolInfo = state.getBuildToolInfo();
+ if (buildToolInfo == null) {
+ buildToolInfo = Sdk.getCurrent().getLatestBuildTool();
+
+ if (buildToolInfo == null) {
+ return null;
+ }
+ }
+
+ File renderScriptSupportJar = RenderScriptProcessor.getSupportJar(
+ buildToolInfo.getLocation().getAbsolutePath());
+
+ jarFiles.add(renderScriptSupportJar);
+ }
+
+ // process all the libraries
+
+ List<IProject> libProjects = state.getFullLibraryProjects();
+ for (IProject libProject : libProjects) {
+ // get the project output
+ IFolder outputFolder = BaseProjectHelper.getAndroidOutputFolder(libProject);
+
+ if (outputFolder != null) { // can happen when closing/deleting a library)
+ IFile jarIFile = outputFolder.getFile(libProject.getName().toLowerCase() +
+ SdkConstants.DOT_JAR);
+
+ // get the source folder for the library project
+ List<IPath> srcs = BaseProjectHelper.getSourceClasspaths(libProject);
+ // find the first non-derived source folder.
+ IPath sourceFolder = null;
+ for (IPath src : srcs) {
+ IFolder srcFolder = workspaceRoot.getFolder(src);
+ if (srcFolder.isDerived() == false) {
+ sourceFolder = src;
+ break;
+ }
+ }
+
+ // we can directly add a CPE for this jar as there's no risk of a duplicate.
+ IClasspathEntry entry = JavaCore.newLibraryEntry(
+ jarIFile.getLocation(),
+ sourceFolder, // source attachment path
+ null, // default source attachment root path.
+ true /*isExported*/);
+
+ entries.add(entry);
+ }
+ }
+
+ entries.addAll(convertJarsToClasspathEntries(iProject, jarFiles));
+
+ return allocateContainer(javaProject, entries, new Path(CONTAINER_DEPENDENCIES),
+ "Android Dependencies");
+ }
+ }
+
+ private static IClasspathContainer allocateContainer(IJavaProject javaProject,
+ List<IClasspathEntry> entries, IPath id, String description) {
+
+ if (AdtPlugin.getDefault() == null) { // This is totally weird, but I've seen it happen!
+ return null;
+ }
+
+ // First check that the project has a library-type container.
+ try {
+ IClasspathEntry[] rawClasspath = javaProject.getRawClasspath();
+ final IClasspathEntry[] oldRawClasspath = rawClasspath;
+
+ boolean foundContainer = false;
+ for (IClasspathEntry entry : rawClasspath) {
+ // get the entry and kind
+ final int kind = entry.getEntryKind();
+
+ if (kind == IClasspathEntry.CPE_CONTAINER) {
+ String path = entry.getPath().toString();
+ String idString = id.toString();
+ if (idString.equals(path)) {
+ foundContainer = true;
+ break;
+ }
+ }
+ }
+
+ // if there isn't any, add it.
+ if (foundContainer == false) {
+ // add the android container to the array
+ rawClasspath = ProjectHelper.addEntryToClasspath(rawClasspath,
+ JavaCore.newContainerEntry(id, true /*isExported*/));
+ }
+
+ // set the new list of entries to the project
+ if (rawClasspath != oldRawClasspath) {
+ javaProject.setRawClasspath(rawClasspath, new NullProgressMonitor());
+ }
+ } catch (JavaModelException e) {
+ // This really shouldn't happen, but if it does, simply return null (the calling
+ // method will fails as well)
+ return null;
+ }
+
+ return new AndroidClasspathContainer(
+ entries.toArray(new IClasspathEntry[entries.size()]),
+ id,
+ description,
+ IClasspathContainer.K_APPLICATION);
+ }
+
+ private static File getFile(File root, String value) {
+ File file = new File(value);
+ if (file.isAbsolute() == false) {
+ file = new File(root.getParentFile(), value);
+ }
+
+ return file;
+ }
+
+ /**
+ * Finds all the jar files inside a project's libs folder.
+ * @param project
+ * @param jarFiles
+ */
+ private static void getJarListFromLibsFolder(IProject project, Set<File> jarFiles) {
+ IFolder libsFolder = project.getFolder(SdkConstants.FD_NATIVE_LIBS);
+ if (libsFolder.exists()) {
+ try {
+ IResource[] members = libsFolder.members();
+ for (IResource member : members) {
+ if (member.getType() == IResource.FILE &&
+ SdkConstants.EXT_JAR.equalsIgnoreCase(member.getFileExtension())) {
+ IPath location = member.getLocation();
+ if (location != null) {
+ jarFiles.add(location.toFile());
+ }
+ }
+ }
+ } catch (CoreException e) {
+ // can't get the list? ignore this folder.
+ }
+ }
+ }
+
+ /**
+ * Process reference projects from the main projects to add indirect dependencies coming
+ * from Java project.
+ * @param project the main project
+ * @param projects the project list to add to
+ * @param jarFiles the jar list to add to.
+ */
+ private static void processReferencedProjects(IProject project,
+ Set<IProject> projects, Set<File> jarFiles) {
+ try {
+ IProject[] refs = project.getReferencedProjects();
+ for (IProject p : refs) {
+ // ignore if it's an Android project, or if it's not a Java
+ // Project
+ if (p.hasNature(JavaCore.NATURE_ID)
+ && p.hasNature(AdtConstants.NATURE_DEFAULT) == false) {
+
+ // process this project's dependencies
+ getDependencyListFromClasspath(p, projects, jarFiles, true /*includeJarFiles*/);
+ }
+ }
+ } catch (CoreException e) {
+ // can't get the referenced projects? ignore
+ }
+ }
+
+ /**
+ * Finds all the dependencies of a given project and add them to a project list and
+ * a jar list.
+ * Only classpath entries that are exported are added, and only Java project (not Android
+ * project) are added.
+ *
+ * @param project the project to query
+ * @param projects the referenced project list to add to
+ * @param jarFiles the jar list to add to
+ * @param includeJarFiles whether to include jar files or just projects. This is useful when
+ * calling on an Android project (value should be <code>false</code>)
+ */
+ private static void getDependencyListFromClasspath(IProject project, Set<IProject> projects,
+ Set<File> jarFiles, boolean includeJarFiles) {
+ IJavaProject javaProject = JavaCore.create(project);
+ IWorkspaceRoot wsRoot = ResourcesPlugin.getWorkspace().getRoot();
+
+ // we could use IJavaProject.getResolvedClasspath directly, but we actually
+ // want to see the containers themselves.
+ IClasspathEntry[] classpaths = javaProject.readRawClasspath();
+ if (classpaths != null) {
+ for (IClasspathEntry e : classpaths) {
+ // ignore entries that are not exported
+ if (!e.getPath().toString().equals(CONTAINER_DEPENDENCIES) && e.isExported()) {
+ processCPE(e, javaProject, wsRoot, projects, jarFiles, includeJarFiles);
+ }
+ }
+ }
+ }
+
+ /**
+ * Processes a {@link IClasspathEntry} and add it to one of the list if applicable.
+ * @param entry the entry to process
+ * @param javaProject the {@link IJavaProject} from which this entry came.
+ * @param wsRoot the {@link IWorkspaceRoot}
+ * @param projects the project list to add to
+ * @param jarFiles the jar list to add to
+ * @param includeJarFiles whether to include jar files or just projects. This is useful when
+ * calling on an Android project (value should be <code>false</code>)
+ */
+ private static void processCPE(IClasspathEntry entry, IJavaProject javaProject,
+ IWorkspaceRoot wsRoot,
+ Set<IProject> projects, Set<File> jarFiles, boolean includeJarFiles) {
+
+ // if this is a classpath variable reference, we resolve it.
+ if (entry.getEntryKind() == IClasspathEntry.CPE_VARIABLE) {
+ entry = JavaCore.getResolvedClasspathEntry(entry);
+ }
+
+ if (entry.getEntryKind() == IClasspathEntry.CPE_PROJECT) {
+ IProject refProject = wsRoot.getProject(entry.getPath().lastSegment());
+ try {
+ // ignore if it's an Android project, or if it's not a Java Project
+ if (refProject.hasNature(JavaCore.NATURE_ID) &&
+ refProject.hasNature(AdtConstants.NATURE_DEFAULT) == false) {
+ // add this project to the list
+ projects.add(refProject);
+
+ // also get the dependency from this project.
+ getDependencyListFromClasspath(refProject, projects, jarFiles,
+ true /*includeJarFiles*/);
+ }
+ } catch (CoreException exception) {
+ // can't query the project nature? ignore
+ }
+ } else if (entry.getEntryKind() == IClasspathEntry.CPE_LIBRARY) {
+ if (includeJarFiles) {
+ handleClasspathLibrary(entry, wsRoot, jarFiles);
+ }
+ } else if (entry.getEntryKind() == IClasspathEntry.CPE_CONTAINER) {
+ // get the container and its content
+ try {
+ IClasspathContainer container = JavaCore.getClasspathContainer(
+ entry.getPath(), javaProject);
+ // ignore the system and default_system types as they represent
+ // libraries that are part of the runtime.
+ if (container != null &&
+ container.getKind() == IClasspathContainer.K_APPLICATION) {
+ IClasspathEntry[] entries = container.getClasspathEntries();
+ for (IClasspathEntry cpe : entries) {
+ processCPE(cpe, javaProject, wsRoot, projects, jarFiles, includeJarFiles);
+ }
+ }
+ } catch (JavaModelException jme) {
+ // can't resolve the container? ignore it.
+ AdtPlugin.log(jme, "Failed to resolve ClasspathContainer: %s", entry.getPath());
+ }
+ }
+ }
+
+ private static final class CPEFile extends File {
+ private static final long serialVersionUID = 1L;
+
+ private final IClasspathEntry mClasspathEntry;
+
+ public CPEFile(String pathname, IClasspathEntry classpathEntry) {
+ super(pathname);
+ mClasspathEntry = classpathEntry;
+ }
+
+ public CPEFile(File file, IClasspathEntry classpathEntry) {
+ super(file.getAbsolutePath());
+ mClasspathEntry = classpathEntry;
+ }
+
+ public IClasspathEntry getClasspathEntry() {
+ return mClasspathEntry;
+ }
+ }
+
+ private static void handleClasspathLibrary(IClasspathEntry e, IWorkspaceRoot wsRoot,
+ Set<File> jarFiles) {
+ // get the IPath
+ IPath path = e.getPath();
+
+ IResource resource = wsRoot.findMember(path);
+
+ if (SdkConstants.EXT_JAR.equalsIgnoreCase(path.getFileExtension())) {
+ // case of a jar file (which could be relative to the workspace or a full path)
+ if (resource != null && resource.exists() &&
+ resource.getType() == IResource.FILE) {
+ jarFiles.add(new CPEFile(resource.getLocation().toFile(), e));
+ } else {
+ // if the jar path doesn't match a workspace resource,
+ // then we get an OSString and check if this links to a valid file.
+ String osFullPath = path.toOSString();
+
+ File f = new CPEFile(osFullPath, e);
+ if (f.isFile()) {
+ jarFiles.add(f);
+ }
+ }
+ }
+ }
+}
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/project/ProjectChooserHelper.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/project/ProjectChooserHelper.java
new file mode 100644
index 000000000..9de8ad06e
--- /dev/null
+++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/project/ProjectChooserHelper.java
@@ -0,0 +1,304 @@
+/*
+ * Copyright (C) 2008 The Android Open Source Project
+ *
+ * Licensed under the Eclipse Public License, Version 1.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.eclipse.org/org/documents/epl-v10.php
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.ide.eclipse.adt.internal.project;
+
+import com.android.ide.eclipse.adt.internal.project.BaseProjectHelper.IProjectFilter;
+import com.android.ide.eclipse.adt.internal.sdk.ProjectState;
+import com.android.ide.eclipse.adt.internal.sdk.Sdk;
+
+import org.eclipse.core.resources.IProject;
+import org.eclipse.core.resources.IWorkspaceRoot;
+import org.eclipse.core.resources.ResourcesPlugin;
+import org.eclipse.jdt.core.IJavaModel;
+import org.eclipse.jdt.core.IJavaProject;
+import org.eclipse.jdt.core.JavaCore;
+import org.eclipse.jdt.ui.JavaElementLabelProvider;
+import org.eclipse.jface.viewers.ILabelProvider;
+import org.eclipse.jface.window.Window;
+import org.eclipse.swt.SWT;
+import org.eclipse.swt.events.SelectionEvent;
+import org.eclipse.swt.events.SelectionListener;
+import org.eclipse.swt.widgets.Combo;
+import org.eclipse.swt.widgets.Composite;
+import org.eclipse.swt.widgets.Shell;
+import org.eclipse.ui.dialogs.ElementListSelectionDialog;
+
+/**
+ * Helper class to deal with displaying a project choosing dialog that lists only the
+ * projects with the Android nature.
+ */
+public class ProjectChooserHelper {
+
+ private final Shell mParentShell;
+ private final IProjectChooserFilter mFilter;
+
+ /**
+ * List of current android projects. Since the dialog is modal, we'll just get
+ * the list once on-demand.
+ */
+ private IJavaProject[] mAndroidProjects;
+
+ /**
+ * Interface to filter out some project displayed by {@link ProjectChooserHelper}.
+ *
+ * @see IProjectFilter
+ */
+ public interface IProjectChooserFilter extends IProjectFilter {
+ /**
+ * Whether the Project Chooser can compute the project list once and cache the result.
+ * </p>If false the project list is recomputed every time the dialog is opened.
+ */
+ boolean useCache();
+ }
+
+ /**
+ * An implementation of {@link IProjectChooserFilter} that only displays non-library projects.
+ */
+ public final static class NonLibraryProjectOnlyFilter implements IProjectChooserFilter {
+ @Override
+ public boolean accept(IProject project) {
+ ProjectState state = Sdk.getProjectState(project);
+ if (state != null) {
+ return state.isLibrary() == false;
+ }
+
+ return false;
+ }
+
+ @Override
+ public boolean useCache() {
+ return true;
+ }
+ }
+
+ /**
+ * An implementation of {@link IProjectChooserFilter} that only displays library projects.
+ */
+ public final static class LibraryProjectOnlyFilter implements IProjectChooserFilter {
+ @Override
+ public boolean accept(IProject project) {
+ ProjectState state = Sdk.getProjectState(project);
+ if (state != null ) {
+ return state.isLibrary();
+ }
+
+ return false;
+ }
+
+ @Override
+ public boolean useCache() {
+ return true;
+ }
+ }
+
+ /**
+ * Creates a new project chooser.
+ * @param parentShell the parent {@link Shell} for the dialog.
+ * @param filter a filter to only accept certain projects. Can be null.
+ */
+ public ProjectChooserHelper(Shell parentShell, IProjectChooserFilter filter) {
+ mParentShell = parentShell;
+ mFilter = filter;
+ }
+
+ /**
+ * Displays a project chooser dialog which lists all available projects with the Android nature.
+ * <p/>
+ * The list of project is built from Android flagged projects currently opened in the workspace.
+ *
+ * @param projectName If non null and not empty, represents the name of an Android project
+ * that will be selected by default.
+ * @param message Message for the dialog box. Can be null in which case a default message
+ * is displayed.
+ * @return the project chosen by the user in the dialog, or null if the dialog was canceled.
+ */
+ public IJavaProject chooseJavaProject(String projectName, String message) {
+ ILabelProvider labelProvider = new JavaElementLabelProvider(
+ JavaElementLabelProvider.SHOW_DEFAULT);
+ ElementListSelectionDialog dialog = new ElementListSelectionDialog(
+ mParentShell, labelProvider);
+ dialog.setTitle("Project Selection");
+ if (message == null) {
+ message = "Please select a project";
+ }
+ dialog.setMessage(message);
+
+ IWorkspaceRoot workspaceRoot = ResourcesPlugin.getWorkspace().getRoot();
+ IJavaModel javaModel = JavaCore.create(workspaceRoot);
+
+ // set the elements in the dialog. These are opened android projects.
+ dialog.setElements(getAndroidProjects(javaModel));
+
+ // look for the project matching the given project name
+ IJavaProject javaProject = null;
+ if (projectName != null && projectName.length() > 0) {
+ javaProject = javaModel.getJavaProject(projectName);
+ }
+
+ // if we found it, we set the initial selection in the dialog to this one.
+ if (javaProject != null) {
+ dialog.setInitialSelections(new Object[] { javaProject });
+ }
+
+ // open the dialog and return the object selected if OK was clicked, or null otherwise
+ if (dialog.open() == Window.OK) {
+ return (IJavaProject) dialog.getFirstResult();
+ }
+ return null;
+ }
+
+ /**
+ * Returns the list of Android projects.
+ * <p/>
+ * Because this list can be time consuming, this class caches the list of project.
+ * It is recommended to call this method instead of
+ * {@link BaseProjectHelper#getAndroidProjects()}.
+ *
+ * @param javaModel the java model. Can be null.
+ */
+ public IJavaProject[] getAndroidProjects(IJavaModel javaModel) {
+ // recompute only if we don't have the projects already or the filter is dynamic
+ // and prevent usage of a cache.
+ if (mAndroidProjects == null || (mFilter != null && mFilter.useCache() == false)) {
+ if (javaModel == null) {
+ mAndroidProjects = BaseProjectHelper.getAndroidProjects(mFilter);
+ } else {
+ mAndroidProjects = BaseProjectHelper.getAndroidProjects(javaModel, mFilter);
+ }
+ }
+
+ return mAndroidProjects;
+ }
+
+ /**
+ * Helper method to get the Android project with the given name
+ *
+ * @param projectName the name of the project to find
+ * @return the {@link IProject} for the Android project. <code>null</code> if not found.
+ */
+ public IProject getAndroidProject(String projectName) {
+ IProject iproject = null;
+ IJavaProject[] javaProjects = getAndroidProjects(null);
+ if (javaProjects != null) {
+ for (IJavaProject javaProject : javaProjects) {
+ if (javaProject.getElementName().equals(projectName)) {
+ iproject = javaProject.getProject();
+ break;
+ }
+ }
+ }
+ return iproject;
+ }
+
+ /**
+ * A selector combo for showing the currently selected project and for
+ * changing the selection
+ */
+ public static class ProjectCombo extends Combo implements SelectionListener {
+ /** Currently chosen project, or null when no project has been initialized or selected */
+ private IProject mProject;
+ private IJavaProject[] mAvailableProjects;
+
+ /**
+ * Creates a new project selector combo
+ *
+ * @param helper associated {@link ProjectChooserHelper} for looking up
+ * projects
+ * @param parent parent composite to add the combo to
+ * @param initialProject the initial project to select, or null (which
+ * will show a "Please Choose Project..." label instead.)
+ */
+ public ProjectCombo(ProjectChooserHelper helper, Composite parent,
+ IProject initialProject) {
+ super(parent, SWT.BORDER | SWT.FLAT | SWT.READ_ONLY);
+ mProject = initialProject;
+
+ mAvailableProjects = helper.getAndroidProjects(null);
+ String[] items = new String[mAvailableProjects.length + 1];
+ items[0] = "--- Choose Project ---";
+
+ ILabelProvider labelProvider = new JavaElementLabelProvider(
+ JavaElementLabelProvider.SHOW_DEFAULT);
+ int selectionIndex = 0;
+ for (int i = 0, n = mAvailableProjects.length; i < n; i++) {
+ IProject project = mAvailableProjects[i].getProject();
+ items[i + 1] = labelProvider.getText(project);
+ if (project == initialProject) {
+ selectionIndex = i + 1;
+ }
+ }
+ setItems(items);
+ select(selectionIndex);
+
+ addSelectionListener(this);
+ }
+
+ /**
+ * Returns the project selected by this chooser (or the initial project
+ * passed to the constructor if the user did not change it)
+ *
+ * @return the selected project
+ */
+ public IProject getSelectedProject() {
+ return mProject;
+ }
+
+ /**
+ * Sets the project selected by this chooser
+ *
+ * @param project the selected project
+ */
+ public void setSelectedProject(IProject project) {
+ mProject = project;
+
+ int selectionIndex = 0;
+ for (int i = 0, n = mAvailableProjects.length; i < n; i++) {
+ if (project == mAvailableProjects[i].getProject()) {
+ selectionIndex = i + 1; // +1: Slot 0 is reserved for "Choose Project"
+ select(selectionIndex);
+ break;
+ }
+ }
+ }
+
+ /**
+ * Click handler for the button: Open the {@link ProjectChooserHelper}
+ * dialog for selecting a new project.
+ */
+ @Override
+ public void widgetSelected(SelectionEvent e) {
+ int selectionIndex = getSelectionIndex();
+ if (selectionIndex > 0 && mAvailableProjects != null
+ && selectionIndex <= mAvailableProjects.length) {
+ // selection index 0 is "Choose Project", all other projects are offset
+ // by 1 from the selection index
+ mProject = mAvailableProjects[selectionIndex - 1].getProject();
+ } else {
+ mProject = null;
+ }
+ }
+
+ @Override
+ public void widgetDefaultSelected(SelectionEvent e) {
+ }
+
+ @Override
+ protected void checkSubclass() {
+ // Disable the check that prevents subclassing of SWT components
+ }
+ }
+}
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/project/ProjectHelper.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/project/ProjectHelper.java
new file mode 100644
index 000000000..a32b4ca8b
--- /dev/null
+++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/project/ProjectHelper.java
@@ -0,0 +1,1153 @@
+/*
+ * Copyright (C) 2007 The Android Open Source Project
+ *
+ * Licensed under the Eclipse Public License, Version 1.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.eclipse.org/org/documents/epl-v10.php
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.ide.eclipse.adt.internal.project;
+
+import static com.android.ide.eclipse.adt.AdtConstants.COMPILER_COMPLIANCE_PREFERRED;
+
+import com.android.SdkConstants;
+import com.android.annotations.NonNull;
+import com.android.ide.common.xml.ManifestData;
+import com.android.ide.eclipse.adt.AdtConstants;
+import com.android.ide.eclipse.adt.AdtPlugin;
+import com.android.ide.eclipse.adt.internal.build.builders.PostCompilerBuilder;
+import com.android.ide.eclipse.adt.internal.build.builders.PreCompilerBuilder;
+import com.android.ide.eclipse.adt.internal.preferences.AdtPrefs;
+import com.android.ide.eclipse.adt.internal.sdk.ProjectState;
+import com.android.ide.eclipse.adt.internal.sdk.Sdk;
+import com.android.sdklib.BuildToolInfo;
+import com.android.sdklib.IAndroidTarget;
+import com.android.utils.Pair;
+
+import org.eclipse.core.resources.ICommand;
+import org.eclipse.core.resources.IFile;
+import org.eclipse.core.resources.IFolder;
+import org.eclipse.core.resources.IMarker;
+import org.eclipse.core.resources.IProject;
+import org.eclipse.core.resources.IProjectDescription;
+import org.eclipse.core.resources.IResource;
+import org.eclipse.core.resources.IWorkspace;
+import org.eclipse.core.resources.IncrementalProjectBuilder;
+import org.eclipse.core.resources.ResourcesPlugin;
+import org.eclipse.core.runtime.CoreException;
+import org.eclipse.core.runtime.IPath;
+import org.eclipse.core.runtime.IProgressMonitor;
+import org.eclipse.core.runtime.NullProgressMonitor;
+import org.eclipse.core.runtime.Path;
+import org.eclipse.core.runtime.QualifiedName;
+import org.eclipse.jdt.core.IClasspathEntry;
+import org.eclipse.jdt.core.IJavaModel;
+import org.eclipse.jdt.core.IJavaProject;
+import org.eclipse.jdt.core.JavaCore;
+import org.eclipse.jdt.core.JavaModelException;
+import org.eclipse.jdt.internal.corext.util.JavaModelUtil;
+import org.eclipse.jdt.launching.IVMInstall;
+import org.eclipse.jdt.launching.IVMInstall2;
+import org.eclipse.jdt.launching.IVMInstallType;
+import org.eclipse.jdt.launching.JavaRuntime;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.TreeMap;
+
+/**
+ * Utility class to manipulate Project parameters/properties.
+ */
+public final class ProjectHelper {
+ public final static int COMPILER_COMPLIANCE_OK = 0;
+ public final static int COMPILER_COMPLIANCE_LEVEL = 1;
+ public final static int COMPILER_COMPLIANCE_SOURCE = 2;
+ public final static int COMPILER_COMPLIANCE_CODEGEN_TARGET = 3;
+
+ /**
+ * Adds the given ClasspathEntry object to the class path entries.
+ * This method does not check whether the entry is already defined in the project.
+ *
+ * @param entries The class path entries to read. A copy will be returned.
+ * @param newEntry The new class path entry to add.
+ * @return A new class path entries array.
+ */
+ public static IClasspathEntry[] addEntryToClasspath(
+ IClasspathEntry[] entries, IClasspathEntry newEntry) {
+ int n = entries.length;
+ IClasspathEntry[] newEntries = new IClasspathEntry[n + 1];
+ System.arraycopy(entries, 0, newEntries, 0, n);
+ newEntries[n] = newEntry;
+ return newEntries;
+ }
+
+ /**
+ * Replaces the given ClasspathEntry in the classpath entries.
+ *
+ * If the classpath does not yet exists (Check is based on entry path), then it is added.
+ *
+ * @param entries The class path entries to read. The same array (replace) or a copy (add)
+ * will be returned.
+ * @param newEntry The new class path entry to add.
+ * @return The same array (replace) or a copy (add) will be returned.
+ *
+ * @see IClasspathEntry#getPath()
+ */
+ public static IClasspathEntry[] replaceEntryInClasspath(
+ IClasspathEntry[] entries, IClasspathEntry newEntry) {
+
+ IPath path = newEntry.getPath();
+ for (int i = 0, count = entries.length; i < count ; i++) {
+ if (path.equals(entries[i].getPath())) {
+ entries[i] = newEntry;
+ return entries;
+ }
+ }
+
+ return addEntryToClasspath(entries, newEntry);
+ }
+
+ /**
+ * Adds the corresponding source folder to the project's class path entries.
+ * This method does not check whether the entry is already defined in the project.
+ *
+ * @param javaProject The java project of which path entries to update.
+ * @param newEntry The new class path entry to add.
+ * @throws JavaModelException
+ */
+ public static void addEntryToClasspath(IJavaProject javaProject, IClasspathEntry newEntry)
+ throws JavaModelException {
+
+ IClasspathEntry[] entries = javaProject.getRawClasspath();
+ entries = addEntryToClasspath(entries, newEntry);
+ javaProject.setRawClasspath(entries, new NullProgressMonitor());
+ }
+
+ /**
+ * Checks whether the given class path entry is already defined in the project.
+ *
+ * @param javaProject The java project of which path entries to check.
+ * @param newEntry The parent source folder to remove.
+ * @return True if the class path entry is already defined.
+ * @throws JavaModelException
+ */
+ public static boolean isEntryInClasspath(IJavaProject javaProject, IClasspathEntry newEntry)
+ throws JavaModelException {
+
+ IClasspathEntry[] entries = javaProject.getRawClasspath();
+ for (IClasspathEntry entry : entries) {
+ if (entry.equals(newEntry)) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ /**
+ * Remove a classpath entry from the array.
+ * @param entries The class path entries to read. A copy will be returned
+ * @param index The index to remove.
+ * @return A new class path entries array.
+ */
+ public static IClasspathEntry[] removeEntryFromClasspath(
+ IClasspathEntry[] entries, int index) {
+ int n = entries.length;
+ IClasspathEntry[] newEntries = new IClasspathEntry[n-1];
+
+ // copy the entries before index
+ System.arraycopy(entries, 0, newEntries, 0, index);
+
+ // copy the entries after index
+ System.arraycopy(entries, index + 1, newEntries, index,
+ entries.length - index - 1);
+
+ return newEntries;
+ }
+
+ /**
+ * Converts a OS specific path into a path valid for the java doc location
+ * attributes of a project.
+ * @param javaDocOSLocation The OS specific path.
+ * @return a valid path for the java doc location.
+ */
+ public static String getJavaDocPath(String javaDocOSLocation) {
+ // first thing we do is convert the \ into /
+ String javaDoc = javaDocOSLocation.replaceAll("\\\\", //$NON-NLS-1$
+ AdtConstants.WS_SEP);
+
+ // then we add file: at the beginning for unix path, and file:/ for non
+ // unix path
+ if (javaDoc.startsWith(AdtConstants.WS_SEP)) {
+ return "file:" + javaDoc; //$NON-NLS-1$
+ }
+
+ return "file:/" + javaDoc; //$NON-NLS-1$
+ }
+
+ /**
+ * Look for a specific classpath entry by full path and return its index.
+ * @param entries The entry array to search in.
+ * @param entryPath The OS specific path of the entry.
+ * @param entryKind The kind of the entry. Accepted values are 0
+ * (no filter), IClasspathEntry.CPE_LIBRARY, IClasspathEntry.CPE_PROJECT,
+ * IClasspathEntry.CPE_SOURCE, IClasspathEntry.CPE_VARIABLE,
+ * and IClasspathEntry.CPE_CONTAINER
+ * @return the index of the found classpath entry or -1.
+ */
+ public static int findClasspathEntryByPath(IClasspathEntry[] entries,
+ String entryPath, int entryKind) {
+ for (int i = 0 ; i < entries.length ; i++) {
+ IClasspathEntry entry = entries[i];
+
+ int kind = entry.getEntryKind();
+
+ if (kind == entryKind || entryKind == 0) {
+ // get the path
+ IPath path = entry.getPath();
+
+ String osPathString = path.toOSString();
+ if (osPathString.equals(entryPath)) {
+ return i;
+ }
+ }
+ }
+
+ // not found, return bad index.
+ return -1;
+ }
+
+ /**
+ * Look for a specific classpath entry for file name only and return its
+ * index.
+ * @param entries The entry array to search in.
+ * @param entryName The filename of the entry.
+ * @param entryKind The kind of the entry. Accepted values are 0
+ * (no filter), IClasspathEntry.CPE_LIBRARY, IClasspathEntry.CPE_PROJECT,
+ * IClasspathEntry.CPE_SOURCE, IClasspathEntry.CPE_VARIABLE,
+ * and IClasspathEntry.CPE_CONTAINER
+ * @param startIndex Index where to start the search
+ * @return the index of the found classpath entry or -1.
+ */
+ public static int findClasspathEntryByName(IClasspathEntry[] entries,
+ String entryName, int entryKind, int startIndex) {
+ if (startIndex < 0) {
+ startIndex = 0;
+ }
+ for (int i = startIndex ; i < entries.length ; i++) {
+ IClasspathEntry entry = entries[i];
+
+ int kind = entry.getEntryKind();
+
+ if (kind == entryKind || entryKind == 0) {
+ // get the path
+ IPath path = entry.getPath();
+ String name = path.segment(path.segmentCount()-1);
+
+ if (name.equals(entryName)) {
+ return i;
+ }
+ }
+ }
+
+ // not found, return bad index.
+ return -1;
+ }
+
+ public static boolean updateProject(IJavaProject project) {
+ return updateProjects(new IJavaProject[] { project});
+ }
+
+ /**
+ * Update the android-specific projects's classpath containers.
+ * @param projects the projects to update
+ * @return
+ */
+ public static boolean updateProjects(IJavaProject[] projects) {
+ boolean r = AndroidClasspathContainerInitializer.updateProjects(projects);
+ if (r) {
+ return LibraryClasspathContainerInitializer.updateProjects(projects);
+ }
+ return false;
+ }
+
+ /**
+ * Fix the project. This checks the SDK location.
+ * @param project The project to fix.
+ * @throws JavaModelException
+ */
+ public static void fixProject(IProject project) throws JavaModelException {
+ if (AdtPlugin.getOsSdkFolder().length() == 0) {
+ AdtPlugin.printToConsole(project, "Unknown SDK Location, project not fixed.");
+ return;
+ }
+
+ // get a java project
+ IJavaProject javaProject = JavaCore.create(project);
+ fixProjectClasspathEntries(javaProject);
+ }
+
+ /**
+ * Fix the project classpath entries. The method ensures that:
+ * <ul>
+ * <li>The project does not reference any old android.zip/android.jar archive.</li>
+ * <li>The project does not use its output folder as a sourc folder.</li>
+ * <li>The project does not reference a desktop JRE</li>
+ * <li>The project references the AndroidClasspathContainer.
+ * </ul>
+ * @param javaProject The project to fix.
+ * @throws JavaModelException
+ */
+ public static void fixProjectClasspathEntries(IJavaProject javaProject)
+ throws JavaModelException {
+
+ // get the project classpath
+ IClasspathEntry[] entries = javaProject.getRawClasspath();
+ IClasspathEntry[] oldEntries = entries;
+ boolean forceRewriteOfCPE = false;
+
+ // check if the JRE is set as library
+ int jreIndex = ProjectHelper.findClasspathEntryByPath(entries, JavaRuntime.JRE_CONTAINER,
+ IClasspathEntry.CPE_CONTAINER);
+ if (jreIndex != -1) {
+ // the project has a JRE included, we remove it
+ entries = ProjectHelper.removeEntryFromClasspath(entries, jreIndex);
+ }
+
+ // get the output folder
+ IPath outputFolder = javaProject.getOutputLocation();
+
+ boolean foundFrameworkContainer = false;
+ IClasspathEntry foundLibrariesContainer = null;
+ IClasspathEntry foundDependenciesContainer = null;
+
+ for (int i = 0 ; i < entries.length ;) {
+ // get the entry and kind
+ IClasspathEntry entry = entries[i];
+ int kind = entry.getEntryKind();
+
+ if (kind == IClasspathEntry.CPE_SOURCE) {
+ IPath path = entry.getPath();
+
+ if (path.equals(outputFolder)) {
+ entries = ProjectHelper.removeEntryFromClasspath(entries, i);
+
+ // continue, to skip the i++;
+ continue;
+ }
+ } else if (kind == IClasspathEntry.CPE_CONTAINER) {
+ String path = entry.getPath().toString();
+ if (AdtConstants.CONTAINER_FRAMEWORK.equals(path)) {
+ foundFrameworkContainer = true;
+ } else if (AdtConstants.CONTAINER_PRIVATE_LIBRARIES.equals(path)) {
+ foundLibrariesContainer = entry;
+ } else if (AdtConstants.CONTAINER_DEPENDENCIES.equals(path)) {
+ foundDependenciesContainer = entry;
+ }
+ }
+
+ i++;
+ }
+
+ // look to see if we have the m2eclipse nature
+ boolean m2eNature = false;
+ try {
+ m2eNature = javaProject.getProject().hasNature("org.eclipse.m2e.core.maven2Nature");
+ } catch (CoreException e) {
+ AdtPlugin.log(e, "Failed to query project %s for m2e nature",
+ javaProject.getProject().getName());
+ }
+
+
+ // if the framework container is not there, we add it
+ if (!foundFrameworkContainer) {
+ // add the android container to the array
+ entries = ProjectHelper.addEntryToClasspath(entries,
+ JavaCore.newContainerEntry(new Path(AdtConstants.CONTAINER_FRAMEWORK)));
+ }
+
+ // same thing for the library container
+ if (foundLibrariesContainer == null) {
+ // add the exported libraries android container to the array
+ entries = ProjectHelper.addEntryToClasspath(entries,
+ JavaCore.newContainerEntry(
+ new Path(AdtConstants.CONTAINER_PRIVATE_LIBRARIES), true));
+ } else if (!m2eNature && !foundLibrariesContainer.isExported()) {
+ // the container is present but it's not exported and since there's no m2e nature
+ // we do want it to be exported.
+ // keep all the other parameters the same.
+ entries = ProjectHelper.replaceEntryInClasspath(entries,
+ JavaCore.newContainerEntry(
+ new Path(AdtConstants.CONTAINER_PRIVATE_LIBRARIES),
+ foundLibrariesContainer.getAccessRules(),
+ foundLibrariesContainer.getExtraAttributes(),
+ true));
+ forceRewriteOfCPE = true;
+ }
+
+ // same thing for the dependencies container
+ if (foundDependenciesContainer == null) {
+ // add the android dependencies container to the array
+ entries = ProjectHelper.addEntryToClasspath(entries,
+ JavaCore.newContainerEntry(
+ new Path(AdtConstants.CONTAINER_DEPENDENCIES), true));
+ } else if (!m2eNature && !foundDependenciesContainer.isExported()) {
+ // the container is present but it's not exported and since there's no m2e nature
+ // we do want it to be exported.
+ // keep all the other parameters the same.
+ entries = ProjectHelper.replaceEntryInClasspath(entries,
+ JavaCore.newContainerEntry(
+ new Path(AdtConstants.CONTAINER_DEPENDENCIES),
+ foundDependenciesContainer.getAccessRules(),
+ foundDependenciesContainer.getExtraAttributes(),
+ true));
+ forceRewriteOfCPE = true;
+ }
+
+ // set the new list of entries to the project
+ if (entries != oldEntries || forceRewriteOfCPE) {
+ javaProject.setRawClasspath(entries, new NullProgressMonitor());
+ }
+
+ // If needed, check and fix compiler compliance and source compatibility
+ ProjectHelper.checkAndFixCompilerCompliance(javaProject);
+ }
+
+
+ /**
+ * Checks the project compiler compliance level is supported.
+ * @param javaProject The project to check
+ * @return A pair with the first integer being an error code, and the second value
+ * being the invalid value found or null. The error code can be: <ul>
+ * <li><code>COMPILER_COMPLIANCE_OK</code> if the project is properly configured</li>
+ * <li><code>COMPILER_COMPLIANCE_LEVEL</code> for unsupported compiler level</li>
+ * <li><code>COMPILER_COMPLIANCE_SOURCE</code> for unsupported source compatibility</li>
+ * <li><code>COMPILER_COMPLIANCE_CODEGEN_TARGET</code> for unsupported .class format</li>
+ * </ul>
+ */
+ public static final Pair<Integer, String> checkCompilerCompliance(IJavaProject javaProject) {
+ // get the project compliance level option
+ String compliance = javaProject.getOption(JavaCore.COMPILER_COMPLIANCE, true);
+
+ // check it against a list of valid compliance level strings.
+ if (!checkCompliance(javaProject, compliance)) {
+ // if we didn't find the proper compliance level, we return an error
+ return Pair.of(COMPILER_COMPLIANCE_LEVEL, compliance);
+ }
+
+ // otherwise we check source compatibility
+ String source = javaProject.getOption(JavaCore.COMPILER_SOURCE, true);
+
+ // check it against a list of valid compliance level strings.
+ if (!checkCompliance(javaProject, source)) {
+ // if we didn't find the proper compliance level, we return an error
+ return Pair.of(COMPILER_COMPLIANCE_SOURCE, source);
+ }
+
+ // otherwise check codegen level
+ String codeGen = javaProject.getOption(JavaCore.COMPILER_CODEGEN_TARGET_PLATFORM, true);
+
+ // check it against a list of valid compliance level strings.
+ if (!checkCompliance(javaProject, codeGen)) {
+ // if we didn't find the proper compliance level, we return an error
+ return Pair.of(COMPILER_COMPLIANCE_CODEGEN_TARGET, codeGen);
+ }
+
+ return Pair.of(COMPILER_COMPLIANCE_OK, null);
+ }
+
+ /**
+ * Checks the project compiler compliance level is supported.
+ * @param project The project to check
+ * @return A pair with the first integer being an error code, and the second value
+ * being the invalid value found or null. The error code can be: <ul>
+ * <li><code>COMPILER_COMPLIANCE_OK</code> if the project is properly configured</li>
+ * <li><code>COMPILER_COMPLIANCE_LEVEL</code> for unsupported compiler level</li>
+ * <li><code>COMPILER_COMPLIANCE_SOURCE</code> for unsupported source compatibility</li>
+ * <li><code>COMPILER_COMPLIANCE_CODEGEN_TARGET</code> for unsupported .class format</li>
+ * </ul>
+ */
+ public static final Pair<Integer, String> checkCompilerCompliance(IProject project) {
+ // get the java project from the IProject resource object
+ IJavaProject javaProject = JavaCore.create(project);
+
+ // check and return the result.
+ return checkCompilerCompliance(javaProject);
+ }
+
+
+ /**
+ * Checks, and fixes if needed, the compiler compliance level, and the source compatibility
+ * level
+ * @param project The project to check and fix.
+ */
+ public static final void checkAndFixCompilerCompliance(IProject project) {
+ // FIXME This method is never used. Shall we just removed it?
+ // {@link #checkAndFixCompilerCompliance(IJavaProject)} is used instead.
+
+ // get the java project from the IProject resource object
+ IJavaProject javaProject = JavaCore.create(project);
+
+ // Now we check the compiler compliance level and make sure it is valid
+ checkAndFixCompilerCompliance(javaProject);
+ }
+
+ /**
+ * Checks, and fixes if needed, the compiler compliance level, and the source compatibility
+ * level
+ * @param javaProject The Java project to check and fix.
+ */
+ public static final void checkAndFixCompilerCompliance(IJavaProject javaProject) {
+ Pair<Integer, String> result = checkCompilerCompliance(javaProject);
+ if (result.getFirst().intValue() != COMPILER_COMPLIANCE_OK) {
+ // setup the preferred compiler compliance level.
+ javaProject.setOption(JavaCore.COMPILER_COMPLIANCE,
+ AdtConstants.COMPILER_COMPLIANCE_PREFERRED);
+ javaProject.setOption(JavaCore.COMPILER_SOURCE,
+ AdtConstants.COMPILER_COMPLIANCE_PREFERRED);
+ javaProject.setOption(JavaCore.COMPILER_CODEGEN_TARGET_PLATFORM,
+ AdtConstants.COMPILER_COMPLIANCE_PREFERRED);
+
+ // clean the project to make sure we recompile
+ try {
+ javaProject.getProject().build(IncrementalProjectBuilder.CLEAN_BUILD,
+ new NullProgressMonitor());
+ } catch (CoreException e) {
+ AdtPlugin.printErrorToConsole(javaProject.getProject(),
+ "Project compiler settings changed. Clean your project.");
+ }
+ }
+ }
+
+ /**
+ * Makes the given project use JDK 6 (or more specifically,
+ * {@link AdtConstants#COMPILER_COMPLIANCE_PREFERRED} as the compilation
+ * target, regardless of what the default IDE JDK level is, provided a JRE
+ * of the given level is installed.
+ *
+ * @param javaProject the Java project
+ * @throws CoreException if the IDE throws an exception setting the compiler
+ * level
+ */
+ @SuppressWarnings("restriction") // JDT API for setting compliance options
+ public static void enforcePreferredCompilerCompliance(@NonNull IJavaProject javaProject)
+ throws CoreException {
+ String compliance = javaProject.getOption(JavaCore.COMPILER_COMPLIANCE, true);
+ if (compliance == null ||
+ JavaModelUtil.isVersionLessThan(compliance, COMPILER_COMPLIANCE_PREFERRED)) {
+ IVMInstallType[] types = JavaRuntime.getVMInstallTypes();
+ for (int i = 0; i < types.length; i++) {
+ IVMInstallType type = types[i];
+ IVMInstall[] installs = type.getVMInstalls();
+ for (int j = 0; j < installs.length; j++) {
+ IVMInstall install = installs[j];
+ if (install instanceof IVMInstall2) {
+ IVMInstall2 install2 = (IVMInstall2) install;
+ // Java version can be 1.6.0, and preferred is 1.6
+ if (install2.getJavaVersion().startsWith(COMPILER_COMPLIANCE_PREFERRED)) {
+ Map<String, String> options = javaProject.getOptions(false);
+ JavaCore.setComplianceOptions(COMPILER_COMPLIANCE_PREFERRED, options);
+ JavaModelUtil.setDefaultClassfileOptions(options,
+ COMPILER_COMPLIANCE_PREFERRED);
+ javaProject.setOptions(options);
+ return;
+ }
+ }
+ }
+ }
+ }
+ }
+
+ /**
+ * Returns a {@link IProject} by its running application name, as it returned by the AVD.
+ * <p/>
+ * <var>applicationName</var> will in most case be the package declared in the manifest, but
+ * can, in some cases, be a custom process name declared in the manifest, in the
+ * <code>application</code>, <code>activity</code>, <code>receiver</code>, or
+ * <code>service</code> nodes.
+ * @param applicationName The application name.
+ * @return a project or <code>null</code> if no matching project were found.
+ */
+ public static IProject findAndroidProjectByAppName(String applicationName) {
+ // Get the list of project for the current workspace
+ IWorkspace workspace = ResourcesPlugin.getWorkspace();
+ IProject[] projects = workspace.getRoot().getProjects();
+
+ // look for a project that matches the packageName of the app
+ // we're trying to debug
+ for (IProject p : projects) {
+ if (p.isOpen()) {
+ try {
+ if (p.hasNature(AdtConstants.NATURE_DEFAULT) == false) {
+ // ignore non android projects
+ continue;
+ }
+ } catch (CoreException e) {
+ // failed to get the nature? skip project.
+ continue;
+ }
+
+ // check that there is indeed a manifest file.
+ IFile manifestFile = getManifest(p);
+ if (manifestFile == null) {
+ // no file? skip this project.
+ continue;
+ }
+
+ ManifestData data = AndroidManifestHelper.parseForData(manifestFile);
+ if (data == null) {
+ // skip this project.
+ continue;
+ }
+
+ String manifestPackage = data.getPackage();
+
+ if (manifestPackage != null && manifestPackage.equals(applicationName)) {
+ // this is the project we were looking for!
+ return p;
+ } else {
+ // if the package and application name don't match,
+ // we look for other possible process names declared in the manifest.
+ String[] processes = data.getProcesses();
+ for (String process : processes) {
+ if (process.equals(applicationName)) {
+ return p;
+ }
+ }
+ }
+ }
+ }
+
+ return null;
+
+ }
+
+ public static void fixProjectNatureOrder(IProject project) throws CoreException {
+ IProjectDescription description = project.getDescription();
+ String[] natures = description.getNatureIds();
+
+ // if the android nature is not the first one, we reorder them
+ if (AdtConstants.NATURE_DEFAULT.equals(natures[0]) == false) {
+ // look for the index
+ for (int i = 0 ; i < natures.length ; i++) {
+ if (AdtConstants.NATURE_DEFAULT.equals(natures[i])) {
+ // if we try to just reorder the array in one pass, this doesn't do
+ // anything. I guess JDT check that we are actually adding/removing nature.
+ // So, first we'll remove the android nature, and then add it back.
+
+ // remove the android nature
+ removeNature(project, AdtConstants.NATURE_DEFAULT);
+
+ // now add it back at the first index.
+ description = project.getDescription();
+ natures = description.getNatureIds();
+
+ String[] newNatures = new String[natures.length + 1];
+
+ // first one is android
+ newNatures[0] = AdtConstants.NATURE_DEFAULT;
+
+ // next the rest that was before the android nature
+ System.arraycopy(natures, 0, newNatures, 1, natures.length);
+
+ // set the new natures
+ description.setNatureIds(newNatures);
+ project.setDescription(description, null);
+
+ // and stop
+ break;
+ }
+ }
+ }
+ }
+
+
+ /**
+ * Removes a specific nature from a project.
+ * @param project The project to remove the nature from.
+ * @param nature The nature id to remove.
+ * @throws CoreException
+ */
+ public static void removeNature(IProject project, String nature) throws CoreException {
+ IProjectDescription description = project.getDescription();
+ String[] natures = description.getNatureIds();
+
+ // check if the project already has the android nature.
+ for (int i = 0; i < natures.length; ++i) {
+ if (nature.equals(natures[i])) {
+ String[] newNatures = new String[natures.length - 1];
+ if (i > 0) {
+ System.arraycopy(natures, 0, newNatures, 0, i);
+ }
+ System.arraycopy(natures, i + 1, newNatures, i, natures.length - i - 1);
+ description.setNatureIds(newNatures);
+ project.setDescription(description, null);
+
+ return;
+ }
+ }
+
+ }
+
+ /**
+ * Returns if the project has error level markers.
+ * @param includeReferencedProjects flag to also test the referenced projects.
+ * @throws CoreException
+ */
+ public static boolean hasError(IProject project, boolean includeReferencedProjects)
+ throws CoreException {
+ IMarker[] markers = project.findMarkers(IMarker.PROBLEM, true, IResource.DEPTH_INFINITE);
+ if (markers != null && markers.length > 0) {
+ // the project has marker(s). even though they are "problem" we
+ // don't know their severity. so we loop on them and figure if they
+ // are warnings or errors
+ for (IMarker m : markers) {
+ int s = m.getAttribute(IMarker.SEVERITY, -1);
+ if (s == IMarker.SEVERITY_ERROR) {
+ return true;
+ }
+ }
+ }
+
+ // test the referenced projects if needed.
+ if (includeReferencedProjects) {
+ List<IProject> projects = getReferencedProjects(project);
+
+ for (IProject p : projects) {
+ if (hasError(p, false)) {
+ return true;
+ }
+ }
+ }
+
+ return false;
+ }
+
+ /**
+ * Saves a String property into the persistent storage of a resource.
+ * @param resource The resource into which the string value is saved.
+ * @param propertyName the name of the property. The id of the plug-in is added to this string.
+ * @param value the value to save
+ * @return true if the save succeeded.
+ */
+ public static boolean saveStringProperty(IResource resource, String propertyName,
+ String value) {
+ QualifiedName qname = new QualifiedName(AdtPlugin.PLUGIN_ID, propertyName);
+
+ try {
+ resource.setPersistentProperty(qname, value);
+ } catch (CoreException e) {
+ return false;
+ }
+
+ return true;
+ }
+
+ /**
+ * Loads a String property from the persistent storage of a resource.
+ * @param resource The resource from which the string value is loaded.
+ * @param propertyName the name of the property. The id of the plug-in is added to this string.
+ * @return the property value or null if it was not found.
+ */
+ public static String loadStringProperty(IResource resource, String propertyName) {
+ QualifiedName qname = new QualifiedName(AdtPlugin.PLUGIN_ID, propertyName);
+
+ try {
+ String value = resource.getPersistentProperty(qname);
+ return value;
+ } catch (CoreException e) {
+ return null;
+ }
+ }
+
+ /**
+ * Saves a property into the persistent storage of a resource.
+ * @param resource The resource into which the boolean value is saved.
+ * @param propertyName the name of the property. The id of the plug-in is added to this string.
+ * @param value the value to save
+ * @return true if the save succeeded.
+ */
+ public static boolean saveBooleanProperty(IResource resource, String propertyName,
+ boolean value) {
+ return saveStringProperty(resource, propertyName, Boolean.toString(value));
+ }
+
+ /**
+ * Loads a boolean property from the persistent storage of a resource.
+ * @param resource The resource from which the boolean value is loaded.
+ * @param propertyName the name of the property. The id of the plug-in is added to this string.
+ * @param defaultValue The default value to return if the property was not found.
+ * @return the property value or the default value if the property was not found.
+ */
+ public static boolean loadBooleanProperty(IResource resource, String propertyName,
+ boolean defaultValue) {
+ String value = loadStringProperty(resource, propertyName);
+ if (value != null) {
+ return Boolean.parseBoolean(value);
+ }
+
+ return defaultValue;
+ }
+
+ public static Boolean loadBooleanProperty(IResource resource, String propertyName) {
+ String value = loadStringProperty(resource, propertyName);
+ if (value != null) {
+ return Boolean.valueOf(value);
+ }
+
+ return null;
+ }
+
+ /**
+ * Saves the path of a resource into the persistent storage of a resource.
+ * @param resource The resource into which the resource path is saved.
+ * @param propertyName the name of the property. The id of the plug-in is added to this string.
+ * @param value The resource to save. It's its path that is actually stored. If null, an
+ * empty string is stored.
+ * @return true if the save succeeded
+ */
+ public static boolean saveResourceProperty(IResource resource, String propertyName,
+ IResource value) {
+ if (value != null) {
+ IPath iPath = value.getFullPath();
+ return saveStringProperty(resource, propertyName, iPath.toString());
+ }
+
+ return saveStringProperty(resource, propertyName, ""); //$NON-NLS-1$
+ }
+
+ /**
+ * Loads the path of a resource from the persistent storage of a resource, and returns the
+ * corresponding IResource object.
+ * @param resource The resource from which the resource path is loaded.
+ * @param propertyName the name of the property. The id of the plug-in is added to this string.
+ * @return The corresponding IResource object (or children interface) or null
+ */
+ public static IResource loadResourceProperty(IResource resource, String propertyName) {
+ String value = loadStringProperty(resource, propertyName);
+
+ if (value != null && value.length() > 0) {
+ return ResourcesPlugin.getWorkspace().getRoot().findMember(new Path(value));
+ }
+
+ return null;
+ }
+
+ /**
+ * Returns the list of referenced project that are opened and Java projects.
+ * @param project
+ * @return a new list object containing the opened referenced java project.
+ * @throws CoreException
+ */
+ public static List<IProject> getReferencedProjects(IProject project) throws CoreException {
+ IProject[] projects = project.getReferencedProjects();
+
+ ArrayList<IProject> list = new ArrayList<IProject>();
+
+ for (IProject p : projects) {
+ if (p.isOpen() && p.hasNature(JavaCore.NATURE_ID)) {
+ list.add(p);
+ }
+ }
+
+ return list;
+ }
+
+
+ /**
+ * Checks a Java project compiler level option against a list of supported versions.
+ * @param optionValue the Compiler level option.
+ * @return true if the option value is supported.
+ */
+ private static boolean checkCompliance(@NonNull IJavaProject project, String optionValue) {
+ for (String s : AdtConstants.COMPILER_COMPLIANCE) {
+ if (s != null && s.equals(optionValue)) {
+ return true;
+ }
+ }
+
+ if (JavaCore.VERSION_1_7.equals(optionValue)) {
+ // Requires API 19 and buildTools 19
+ Sdk currentSdk = Sdk.getCurrent();
+ if (currentSdk != null) {
+ IProject p = project.getProject();
+ IAndroidTarget target = currentSdk.getTarget(p);
+ if (target == null || target.getVersion().getApiLevel() < 19) {
+ return false;
+ }
+
+ ProjectState projectState = Sdk.getProjectState(p);
+ if (projectState != null) {
+ BuildToolInfo buildToolInfo = projectState.getBuildToolInfo();
+ if (buildToolInfo == null) {
+ buildToolInfo = currentSdk.getLatestBuildTool();
+ }
+ if (buildToolInfo == null || buildToolInfo.getRevision().getMajor() < 19) {
+ return false;
+ }
+ }
+
+ return true;
+ }
+ }
+
+ return false;
+ }
+
+ /**
+ * Returns the apk filename for the given project
+ * @param project The project.
+ * @param config An optional config name. Can be null.
+ */
+ public static String getApkFilename(IProject project, String config) {
+ if (config != null) {
+ return project.getName() + "-" + config + SdkConstants.DOT_ANDROID_PACKAGE; //$NON-NLS-1$
+ }
+
+ return project.getName() + SdkConstants.DOT_ANDROID_PACKAGE;
+ }
+
+ /**
+ * Find the list of projects on which this JavaProject is dependent on at the compilation level.
+ *
+ * @param javaProject Java project that we are looking for the dependencies.
+ * @return A list of Java projects for which javaProject depend on.
+ * @throws JavaModelException
+ */
+ public static List<IJavaProject> getAndroidProjectDependencies(IJavaProject javaProject)
+ throws JavaModelException {
+ String[] requiredProjectNames = javaProject.getRequiredProjectNames();
+
+ // Go from java project name to JavaProject name
+ IJavaModel javaModel = javaProject.getJavaModel();
+
+ // loop through all dependent projects and keep only those that are Android projects
+ List<IJavaProject> projectList = new ArrayList<IJavaProject>(requiredProjectNames.length);
+ for (String javaProjectName : requiredProjectNames) {
+ IJavaProject androidJavaProject = javaModel.getJavaProject(javaProjectName);
+
+ //Verify that the project has also the Android Nature
+ try {
+ if (!androidJavaProject.getProject().hasNature(AdtConstants.NATURE_DEFAULT)) {
+ continue;
+ }
+ } catch (CoreException e) {
+ continue;
+ }
+
+ projectList.add(androidJavaProject);
+ }
+
+ return projectList;
+ }
+
+ /**
+ * Returns the android package file as an IFile object for the specified
+ * project.
+ * @param project The project
+ * @return The android package as an IFile object or null if not found.
+ */
+ public static IFile getApplicationPackage(IProject project) {
+ // get the output folder
+ IFolder outputLocation = BaseProjectHelper.getAndroidOutputFolder(project);
+
+ if (outputLocation == null) {
+ AdtPlugin.printErrorToConsole(project,
+ "Failed to get the output location of the project. Check build path properties"
+ );
+ return null;
+ }
+
+
+ // get the package path
+ String packageName = project.getName() + SdkConstants.DOT_ANDROID_PACKAGE;
+ IResource r = outputLocation.findMember(packageName);
+
+ // check the package is present
+ if (r instanceof IFile && r.exists()) {
+ return (IFile)r;
+ }
+
+ String msg = String.format("Could not find %1$s!", packageName);
+ AdtPlugin.printErrorToConsole(project, msg);
+
+ return null;
+ }
+
+ /**
+ * Returns an {@link IFile} object representing the manifest for the given project.
+ *
+ * @param project The project containing the manifest file.
+ * @return An IFile object pointing to the manifest or null if the manifest
+ * is missing.
+ */
+ public static IFile getManifest(IProject project) {
+ IResource r = project.findMember(AdtConstants.WS_SEP
+ + SdkConstants.FN_ANDROID_MANIFEST_XML);
+
+ if (r == null || r.exists() == false || (r instanceof IFile) == false) {
+ return null;
+ }
+ return (IFile) r;
+ }
+
+ /**
+ * Does a full release build of the application, including the libraries. Do not build the
+ * package.
+ *
+ * @param project The project to be built.
+ * @param monitor A eclipse runtime progress monitor to be updated by the builders.
+ * @throws CoreException
+ */
+ @SuppressWarnings("unchecked")
+ public static void compileInReleaseMode(IProject project, IProgressMonitor monitor)
+ throws CoreException {
+ compileInReleaseMode(project, true /*includeDependencies*/, monitor);
+ }
+
+ /**
+ * Does a full release build of the application, including the libraries. Do not build the
+ * package.
+ *
+ * @param project The project to be built.
+ * @param monitor A eclipse runtime progress monitor to be updated by the builders.
+ * @throws CoreException
+ */
+ @SuppressWarnings("unchecked")
+ private static void compileInReleaseMode(IProject project, boolean includeDependencies,
+ IProgressMonitor monitor)
+ throws CoreException {
+
+ if (includeDependencies) {
+ ProjectState projectState = Sdk.getProjectState(project);
+
+ // this gives us all the library projects, direct and indirect dependencies,
+ // so no need to run this method recursively.
+ List<IProject> libraries = projectState.getFullLibraryProjects();
+
+ // build dependencies in reverse order to prevent libraries being rebuilt
+ // due to refresh of other libraries (they would be compiled in the wrong mode).
+ for (int i = libraries.size() - 1 ; i >= 0 ; i--) {
+ IProject lib = libraries.get(i);
+ compileInReleaseMode(lib, false /*includeDependencies*/, monitor);
+
+ // force refresh of the dependency.
+ lib.refreshLocal(IResource.DEPTH_INFINITE, monitor);
+ }
+ }
+
+ // do a full build on all the builders to guarantee that the builders are called.
+ // (Eclipse does an optimization where builders are not called if there aren't any
+ // deltas).
+
+ ICommand[] commands = project.getDescription().getBuildSpec();
+ for (ICommand command : commands) {
+ String name = command.getBuilderName();
+ if (PreCompilerBuilder.ID.equals(name)) {
+ Map newArgs = new HashMap();
+ newArgs.put(PreCompilerBuilder.RELEASE_REQUESTED, "");
+ if (command.getArguments() != null) {
+ newArgs.putAll(command.getArguments());
+ }
+
+ project.build(IncrementalProjectBuilder.FULL_BUILD,
+ PreCompilerBuilder.ID, newArgs, monitor);
+ } else if (PostCompilerBuilder.ID.equals(name)) {
+ if (includeDependencies == false) {
+ // this is a library, we need to build it!
+ project.build(IncrementalProjectBuilder.FULL_BUILD, name,
+ command.getArguments(), monitor);
+ }
+ } else {
+
+ project.build(IncrementalProjectBuilder.FULL_BUILD, name,
+ command.getArguments(), monitor);
+ }
+ }
+ }
+
+ /**
+ * Force building the project and all its dependencies.
+ *
+ * @param project the project to build
+ * @param kind the build kind
+ * @param monitor
+ * @throws CoreException
+ */
+ public static void buildWithDeps(IProject project, int kind, IProgressMonitor monitor)
+ throws CoreException {
+ // Get list of projects that we depend on
+ ProjectState projectState = Sdk.getProjectState(project);
+
+ // this gives us all the library projects, direct and indirect dependencies,
+ // so no need to run this method recursively.
+ List<IProject> libraries = projectState.getFullLibraryProjects();
+
+ // build dependencies in reverse order to prevent libraries being rebuilt
+ // due to refresh of other libraries (they would be compiled in the wrong mode).
+ for (int i = libraries.size() - 1 ; i >= 0 ; i--) {
+ IProject lib = libraries.get(i);
+ lib.build(kind, monitor);
+ lib.refreshLocal(IResource.DEPTH_INFINITE, monitor);
+ }
+
+ project.build(kind, monitor);
+ }
+
+
+ /**
+ * Build project incrementally, including making the final packaging even if it is disabled
+ * by default.
+ *
+ * @param project The project to be built.
+ * @param monitor A eclipse runtime progress monitor to be updated by the builders.
+ * @throws CoreException
+ */
+ public static void doFullIncrementalDebugBuild(IProject project, IProgressMonitor monitor)
+ throws CoreException {
+ // Get list of projects that we depend on
+ List<IJavaProject> androidProjectList = new ArrayList<IJavaProject>();
+ try {
+ androidProjectList = getAndroidProjectDependencies(
+ BaseProjectHelper.getJavaProject(project));
+ } catch (JavaModelException e) {
+ AdtPlugin.printErrorToConsole(project, e);
+ }
+ // Recursively build dependencies
+ for (IJavaProject dependency : androidProjectList) {
+ doFullIncrementalDebugBuild(dependency.getProject(), monitor);
+ }
+
+ // Do an incremental build to pick up all the deltas
+ project.build(IncrementalProjectBuilder.INCREMENTAL_BUILD, monitor);
+
+ // If the preferences indicate not to use post compiler optimization
+ // then the incremental build will have done everything necessary, otherwise,
+ // we have to run the final builder manually (if requested).
+ if (AdtPrefs.getPrefs().getBuildSkipPostCompileOnFileSave()) {
+ // Create the map to pass to the PostC builder
+ Map<String, String> args = new TreeMap<String, String>();
+ args.put(PostCompilerBuilder.POST_C_REQUESTED, ""); //$NON-NLS-1$
+
+ // call the post compiler manually, forcing FULL_BUILD otherwise Eclipse won't
+ // call the builder since the delta is empty.
+ project.build(IncrementalProjectBuilder.FULL_BUILD,
+ PostCompilerBuilder.ID, args, monitor);
+ }
+
+ // because the post compiler builder does a delayed refresh due to
+ // library not picking the refresh up if it's done during the build,
+ // we want to force a refresh here as this call is generally asking for
+ // a build to use the apk right after the call.
+ project.refreshLocal(IResource.DEPTH_INFINITE, monitor);
+ }
+}
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/project/SupportLibraryHelper.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/project/SupportLibraryHelper.java
new file mode 100644
index 000000000..e1819b283
--- /dev/null
+++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/project/SupportLibraryHelper.java
@@ -0,0 +1,176 @@
+/*
+ * Copyright (C) 2012 The Android Open Source Project
+ *
+ * Licensed under the Eclipse Public License, Version 1.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.eclipse.org/org/documents/epl-v10.php
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.ide.eclipse.adt.internal.project;
+
+import static com.android.SdkConstants.FQCN_GRID_LAYOUT;
+import static com.android.SdkConstants.FQCN_GRID_LAYOUT_V7;
+import static com.android.SdkConstants.FQCN_SPACE;
+import static com.android.SdkConstants.FQCN_SPACE_V7;
+
+import com.android.annotations.NonNull;
+import com.android.annotations.Nullable;
+import com.android.ide.eclipse.adt.AdtUtils;
+import com.android.ide.eclipse.adt.internal.actions.AddSupportJarAction;
+import com.android.ide.eclipse.adt.internal.editors.manifest.ManifestInfo;
+import com.android.ide.eclipse.adt.internal.sdk.ProjectState;
+import com.android.ide.eclipse.adt.internal.sdk.ProjectState.LibraryState;
+import com.android.ide.eclipse.adt.internal.sdk.Sdk;
+
+import org.eclipse.core.resources.IProject;
+import org.eclipse.jdt.core.IJavaProject;
+import org.eclipse.jface.dialogs.MessageDialog;
+import org.eclipse.swt.widgets.Display;
+
+/**
+ * Helper class for the Android Support Library. The support library provides
+ * (for example) a backport of GridLayout, which must be used as a library
+ * project rather than a jar library since it has resources. This class provides
+ * support for finding the library project, or downloading and installing it on
+ * demand if it does not, as well as translating tags such as
+ * {@code <GridLayout>} into {@code <com.android.support.v7.GridLayout>} if it
+ * does not.
+ */
+public class SupportLibraryHelper {
+ /**
+ * Returns the correct tag to use for the given view tag. This is normally
+ * the same as the tag itself. However, for some views which are not available
+ * on all platforms, this will:
+ * <ul>
+ * <li> Check if the view is available in the compatibility library,
+ * and if so, if the support library is not installed, will offer to
+ * install it via the SDK manager.
+ * <li> (The tool may also offer to adjust the minimum SDK of the project
+ * up to a level such that the given tag is supported directly, and then
+ * this method will return the original tag.)
+ * <li> Check whether the compatibility library is included in the project, and
+ * if not, offer to copy it into the workspace and add a library dependency.
+ * <li> Return the alternative tag. For example, for "GridLayout", it will
+ * (if the minimum SDK is less than 14) return "com.android.support.v7.GridLayout"
+ * instead.
+ * </ul>
+ *
+ * @param project the project to add the dependency into
+ * @param tag the tag to look up, such as "GridLayout"
+ * @return the tag to use in the layout, normally the same as the input tag but possibly
+ * an equivalent compatibility library tag instead.
+ */
+ @NonNull
+ public static String getTagFor(@NonNull IProject project, @NonNull String tag) {
+ boolean isGridLayout = tag.equals(FQCN_GRID_LAYOUT);
+ boolean isSpace = tag.equals(FQCN_SPACE);
+ if (isGridLayout || isSpace) {
+ int minSdk = ManifestInfo.get(project).getMinSdkVersion();
+ if (minSdk < 14) {
+ // See if the support library is installed in the SDK area
+ // See if there is a local project in the workspace providing the
+ // project
+ IProject supportProject = getSupportProjectV7();
+ if (supportProject != null) {
+ // Make sure I have a dependency on it
+ ProjectState state = Sdk.getProjectState(project);
+ if (state != null) {
+ for (LibraryState library : state.getLibraries()) {
+ if (supportProject.equals(library.getProjectState().getProject())) {
+ // Found it: you have the compatibility library and have linked
+ // to it: use the alternative tag
+ return isGridLayout ? FQCN_GRID_LAYOUT_V7 : FQCN_SPACE_V7;
+ }
+ }
+ }
+ }
+
+ // Ask user to install it
+ String message = String.format(
+ "%1$s requires API level 14 or higher, or a compatibility "
+ + "library for older versions.\n\n"
+ + " Do you want to install the compatibility library?", tag);
+ MessageDialog dialog =
+ new MessageDialog(
+ Display.getCurrent().getActiveShell(),
+ "Warning",
+ null,
+ message,
+ MessageDialog.QUESTION,
+ new String[] {
+ "Install", "Cancel"
+ },
+ 1 /* default button: Cancel */);
+ int answer = dialog.open();
+ if (answer == 0) {
+ if (supportProject != null) {
+ // Just add library dependency
+ if (!AddSupportJarAction.addLibraryDependency(
+ supportProject,
+ project,
+ true /* waitForFinish */)) {
+ return tag;
+ }
+ } else {
+ // Install library AND add dependency
+ if (!AddSupportJarAction.installGridLayoutLibrary(
+ project,
+ true /* waitForFinish */)) {
+ return tag;
+ }
+ }
+
+ return isGridLayout ? FQCN_GRID_LAYOUT_V7 : FQCN_SPACE_V7;
+ }
+ }
+ }
+
+ return tag;
+ }
+
+ /** Cache for {@link #getSupportProjectV7()} */
+ private static IProject sCachedProject;
+
+ /**
+ * Finds and returns the support project in the workspace, if any.
+ *
+ * @return the android support library project, or null if not found
+ */
+ @Nullable
+ public static IProject getSupportProjectV7() {
+ if (sCachedProject != null) {
+ if (sCachedProject.isAccessible()) {
+ return sCachedProject;
+ } else {
+ sCachedProject = null;
+ }
+ }
+
+ sCachedProject = findSupportProjectV7();
+ return sCachedProject;
+ }
+
+ @Nullable
+ private static IProject findSupportProjectV7() {
+ for (IJavaProject javaProject : AdtUtils.getOpenAndroidProjects()) {
+ IProject project = javaProject.getProject();
+ ProjectState state = Sdk.getProjectState(project);
+ if (state != null && state.isLibrary()) {
+ ManifestInfo manifestInfo = ManifestInfo.get(project);
+ if (manifestInfo.getPackage().equals("android.support.v7.gridlayout")) { //$NON-NLS-1$
+ return project;
+ }
+ }
+ }
+
+ return null;
+ }
+}
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/project/XmlErrorHandler.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/project/XmlErrorHandler.java
new file mode 100644
index 000000000..c496c7e57
--- /dev/null
+++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/project/XmlErrorHandler.java
@@ -0,0 +1,175 @@
+/*
+ * Copyright (C) 2007 The Android Open Source Project
+ *
+ * Licensed under the Eclipse Public License, Version 1.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.eclipse.org/org/documents/epl-v10.php
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.ide.eclipse.adt.internal.project;
+
+import com.android.ide.common.xml.AndroidManifestParser.ManifestErrorHandler;
+import com.android.ide.eclipse.adt.AdtConstants;
+
+import org.eclipse.core.resources.IFile;
+import org.eclipse.core.resources.IMarker;
+import org.eclipse.core.runtime.CoreException;
+import org.eclipse.jdt.core.IJavaProject;
+import org.xml.sax.Locator;
+import org.xml.sax.SAXException;
+import org.xml.sax.SAXParseException;
+import org.xml.sax.helpers.DefaultHandler;
+
+/**
+ * XML error handler used by the parser to report errors/warnings.
+ */
+public class XmlErrorHandler extends DefaultHandler implements ManifestErrorHandler {
+
+ private final IJavaProject mJavaProject;
+ /** file being parsed */
+ private final IFile mFile;
+ /** link to the delta visitor, to set the xml error flag */
+ private final XmlErrorListener mErrorListener;
+
+ /**
+ * Classes which implement this interface provide a method that deals
+ * with XML errors.
+ */
+ public interface XmlErrorListener {
+ /**
+ * Sent when an XML error is detected.
+ */
+ public void errorFound();
+ }
+
+ public static class BasicXmlErrorListener implements XmlErrorListener {
+ public boolean mHasXmlError = false;
+
+ @Override
+ public void errorFound() {
+ mHasXmlError = true;
+ }
+ }
+
+ public XmlErrorHandler(IJavaProject javaProject, IFile file, XmlErrorListener errorListener) {
+ mJavaProject = javaProject;
+ mFile = file;
+ mErrorListener = errorListener;
+ }
+
+ public XmlErrorHandler(IFile file, XmlErrorListener errorListener) {
+ this(null, file, errorListener);
+ }
+
+ /**
+ * Xml Error call back
+ * @param exception the parsing exception
+ * @throws SAXException
+ */
+ @Override
+ public void error(SAXParseException exception) throws SAXException {
+ handleError(exception, exception.getLineNumber());
+ }
+
+ /**
+ * Xml Fatal Error call back
+ * @param exception the parsing exception
+ * @throws SAXException
+ */
+ @Override
+ public void fatalError(SAXParseException exception) throws SAXException {
+ handleError(exception, exception.getLineNumber());
+ }
+
+ /**
+ * Xml Warning call back
+ * @param exception the parsing exception
+ * @throws SAXException
+ */
+ @Override
+ public void warning(SAXParseException exception) throws SAXException {
+ if (mFile != null) {
+ BaseProjectHelper.markResource(mFile,
+ AdtConstants.MARKER_XML,
+ exception.getMessage(),
+ exception.getLineNumber(),
+ IMarker.SEVERITY_WARNING);
+ }
+ }
+
+ protected final IFile getFile() {
+ return mFile;
+ }
+
+ /**
+ * Handles a parsing error and an optional line number.
+ * @param exception
+ * @param lineNumber
+ */
+ @Override
+ public void handleError(Exception exception, int lineNumber) {
+ if (mErrorListener != null) {
+ mErrorListener.errorFound();
+ }
+
+ String message = exception.getMessage();
+ if (message == null) {
+ message = "Unknown error " + exception.getClass().getCanonicalName();
+ }
+
+ if (mFile != null) {
+ BaseProjectHelper.markResource(mFile,
+ AdtConstants.MARKER_XML,
+ message,
+ lineNumber,
+ IMarker.SEVERITY_ERROR);
+ }
+ }
+
+ /**
+ * Checks that a class is valid and can be used in the Android Manifest.
+ * <p/>
+ * Errors are put as {@link IMarker} on the manifest file.
+ * @param locator
+ * @param className the fully qualified name of the class to test.
+ * @param superClassName the fully qualified name of the class it is supposed to extend.
+ * @param testVisibility if <code>true</code>, the method will check the visibility of
+ * the class or of its constructors.
+ */
+ @Override
+ public void checkClass(Locator locator, String className, String superClassName,
+ boolean testVisibility) {
+ if (mJavaProject == null) {
+ return;
+ }
+ // we need to check the validity of the activity.
+ String result = BaseProjectHelper.testClassForManifest(mJavaProject,
+ className, superClassName, testVisibility);
+ if (result != BaseProjectHelper.TEST_CLASS_OK) {
+ // get the line number
+ int line = locator.getLineNumber();
+
+ // mark the file
+ IMarker marker = BaseProjectHelper.markResource(getFile(),
+ AdtConstants.MARKER_ANDROID, result, line, IMarker.SEVERITY_ERROR);
+
+ // add custom attributes to be used by the manifest editor.
+ if (marker != null) {
+ try {
+ marker.setAttribute(AdtConstants.MARKER_ATTR_TYPE,
+ AdtConstants.MARKER_ATTR_TYPE_ACTIVITY);
+ marker.setAttribute(AdtConstants.MARKER_ATTR_CLASS, className);
+ } catch (CoreException e) {
+ }
+ }
+ }
+ }
+}