blob: 6d2d1c1f21b4e50a0a1bc1a613f10fa29edfa50d [file] [log] [blame]
/*
* 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.editors.manifest;
import static com.android.SdkConstants.ANDROID_STYLE_RESOURCE_PREFIX;
import static com.android.SdkConstants.CLASS_ACTIVITY;
import static com.android.SdkConstants.NS_RESOURCES;
import static com.android.xml.AndroidManifest.ATTRIBUTE_ICON;
import static com.android.xml.AndroidManifest.ATTRIBUTE_LABEL;
import static com.android.xml.AndroidManifest.ATTRIBUTE_MIN_SDK_VERSION;
import static com.android.xml.AndroidManifest.ATTRIBUTE_NAME;
import static com.android.xml.AndroidManifest.ATTRIBUTE_PACKAGE;
import static com.android.xml.AndroidManifest.ATTRIBUTE_PARENT_ACTIVITY_NAME;
import static com.android.xml.AndroidManifest.ATTRIBUTE_SUPPORTS_RTL;
import static com.android.xml.AndroidManifest.ATTRIBUTE_TARGET_SDK_VERSION;
import static com.android.xml.AndroidManifest.ATTRIBUTE_THEME;
import static com.android.xml.AndroidManifest.ATTRIBUTE_UI_OPTIONS;
import static com.android.xml.AndroidManifest.ATTRIBUTE_VALUE;
import static com.android.xml.AndroidManifest.NODE_ACTIVITY;
import static com.android.xml.AndroidManifest.NODE_METADATA;
import static com.android.xml.AndroidManifest.NODE_USES_SDK;
import static com.android.xml.AndroidManifest.VALUE_PARENT_ACTIVITY;
import static org.eclipse.jdt.core.search.IJavaSearchConstants.REFERENCES;
import com.android.SdkConstants;
import com.android.annotations.NonNull;
import com.android.annotations.Nullable;
import com.android.ide.eclipse.adt.AdtPlugin;
import com.android.ide.eclipse.adt.internal.project.BaseProjectHelper;
import com.android.ide.eclipse.adt.internal.sdk.Sdk;
import com.android.ide.eclipse.adt.io.IFolderWrapper;
import com.android.io.IAbstractFile;
import com.android.io.StreamException;
import com.android.resources.ScreenSize;
import com.android.sdklib.IAndroidTarget;
import com.android.utils.Pair;
import com.android.xml.AndroidManifest;
import org.eclipse.core.resources.IFile;
import org.eclipse.core.resources.IProject;
import org.eclipse.core.resources.IResource;
import org.eclipse.core.resources.IWorkspace;
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.OperationCanceledException;
import org.eclipse.core.runtime.QualifiedName;
import org.eclipse.jdt.core.IField;
import org.eclipse.jdt.core.IJavaElement;
import org.eclipse.jdt.core.IJavaProject;
import org.eclipse.jdt.core.IMethod;
import org.eclipse.jdt.core.IPackageFragment;
import org.eclipse.jdt.core.IPackageFragmentRoot;
import org.eclipse.jdt.core.IType;
import org.eclipse.jdt.core.ITypeHierarchy;
import org.eclipse.jdt.core.search.IJavaSearchScope;
import org.eclipse.jdt.core.search.SearchEngine;
import org.eclipse.jdt.core.search.SearchMatch;
import org.eclipse.jdt.core.search.SearchParticipant;
import org.eclipse.jdt.core.search.SearchPattern;
import org.eclipse.jdt.core.search.SearchRequestor;
import org.eclipse.jdt.internal.core.BinaryType;
import org.eclipse.jface.text.IDocument;
import org.eclipse.ui.editors.text.TextFileDocumentProvider;
import org.eclipse.ui.texteditor.IDocumentProvider;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.NodeList;
import org.xml.sax.InputSource;
import org.xml.sax.SAXException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.xpath.XPathExpressionException;
/**
* Retrieves and caches manifest information such as the themes to be used for
* a given activity.
*
* @see AndroidManifest
*/
public class ManifestInfo {
public static class ActivityAttributes {
@Nullable
private final String mIcon;
@Nullable
private final String mLabel;
@NonNull
private final String mName;
@Nullable
private final String mParentActivity;
@Nullable
private final String mTheme;
@Nullable
private final String mUiOptions;
public ActivityAttributes(Element activity, String packageName) {
// Get activity name.
String name = activity.getAttributeNS(NS_RESOURCES, ATTRIBUTE_NAME);
if (name == null || name.length() == 0) {
throw new RuntimeException("Activity name cannot be empty");
}
int index = name.indexOf('.');
if (index <= 0 && packageName != null && !packageName.isEmpty()) {
name = packageName + (index == -1 ? "." : "") + name;
}
mName = name;
// Get activity icon.
String value = activity.getAttributeNS(NS_RESOURCES, ATTRIBUTE_ICON);
if (value != null && value.length() > 0) {
mIcon = value;
} else {
mIcon = null;
}
// Get activity label.
value = activity.getAttributeNS(NS_RESOURCES, ATTRIBUTE_LABEL);
if (value != null && value.length() > 0) {
mLabel = value;
} else {
mLabel = null;
}
// Get activity parent. Also search the meta-data for parent info.
value = activity.getAttributeNS(NS_RESOURCES, ATTRIBUTE_PARENT_ACTIVITY_NAME);
if (value == null || value.length() == 0) {
// TODO: Not sure if meta data can be used for API Level > 16
NodeList metaData = activity.getElementsByTagName(NODE_METADATA);
for (int j = 0, m = metaData.getLength(); j < m; j++) {
Element data = (Element) metaData.item(j);
String metadataName = data.getAttributeNS(NS_RESOURCES, ATTRIBUTE_NAME);
if (VALUE_PARENT_ACTIVITY.equals(metadataName)) {
value = data.getAttributeNS(NS_RESOURCES, ATTRIBUTE_VALUE);
if (value != null) {
index = value.indexOf('.');
if (index <= 0 && packageName != null && !packageName.isEmpty()) {
value = packageName + (index == -1 ? "." : "") + value;
break;
}
}
}
}
}
if (value != null && value.length() > 0) {
mParentActivity = value;
} else {
mParentActivity = null;
}
// Get activity theme.
value = activity.getAttributeNS(NS_RESOURCES, ATTRIBUTE_THEME);
if (value != null && value.length() > 0) {
mTheme = value;
} else {
mTheme = null;
}
// Get UI options.
value = activity.getAttributeNS(NS_RESOURCES, ATTRIBUTE_UI_OPTIONS);
if (value != null && value.length() > 0) {
mUiOptions = value;
} else {
mUiOptions = null;
}
}
@Nullable
public String getIcon() {
return mIcon;
}
@Nullable
public String getLabel() {
return mLabel;
}
public String getName() {
return mName;
}
@Nullable
public String getParentActivity() {
return mParentActivity;
}
@Nullable
public String getTheme() {
return mTheme;
}
@Nullable
public String getUiOptions() {
return mUiOptions;
}
}
/**
* The maximum number of milliseconds to search for an activity in the codebase when
* attempting to associate layouts with activities in
* {@link #guessActivity(IFile, String)}
*/
private static final int SEARCH_TIMEOUT_MS = 3000;
private final IProject mProject;
private String mPackage;
private String mManifestTheme;
private Map<String, ActivityAttributes> mActivityAttributes;
private IAbstractFile mManifestFile;
private long mLastModified;
private long mLastChecked;
private String mMinSdkName;
private int mMinSdk;
private int mTargetSdk;
private String mApplicationIcon;
private String mApplicationLabel;
private boolean mApplicationSupportsRtl;
/**
* Qualified name for the per-project non-persistent property storing the
* {@link ManifestInfo} for this project
*/
final static QualifiedName MANIFEST_FINDER = new QualifiedName(AdtPlugin.PLUGIN_ID,
"manifest"); //$NON-NLS-1$
/**
* Constructs an {@link ManifestInfo} for the given project. Don't use this method;
* use the {@link #get} factory method instead.
*
* @param project project to create an {@link ManifestInfo} for
*/
private ManifestInfo(IProject project) {
mProject = project;
}
/**
* Clears the cached manifest information. The next get call on one of the
* properties will cause the information to be refreshed.
*/
public void clear() {
mLastChecked = 0;
}
/**
* Returns the {@link ManifestInfo} for the given project
*
* @param project the project the finder is associated with
* @return a {@ManifestInfo} for the given project, never null
*/
@NonNull
public static ManifestInfo get(IProject project) {
ManifestInfo finder = null;
try {
finder = (ManifestInfo) project.getSessionProperty(MANIFEST_FINDER);
} catch (CoreException e) {
// Not a problem; we will just create a new one
}
if (finder == null) {
finder = new ManifestInfo(project);
try {
project.setSessionProperty(MANIFEST_FINDER, finder);
} catch (CoreException e) {
AdtPlugin.log(e, "Can't store ManifestInfo");
}
}
return finder;
}
/**
* Ensure that the package, theme and activity maps are initialized and up to date
* with respect to the manifest file
*/
private void sync() {
// Since each of the accessors call sync(), allow a bunch of immediate
// accessors to all bypass the file stat() below
long now = System.currentTimeMillis();
if (now - mLastChecked < 50 && mManifestFile != null) {
return;
}
mLastChecked = now;
if (mManifestFile == null) {
IFolderWrapper projectFolder = new IFolderWrapper(mProject);
mManifestFile = AndroidManifest.getManifest(projectFolder);
if (mManifestFile == null) {
return;
}
}
// Check to see if our data is up to date
long fileModified = mManifestFile.getModificationStamp();
if (fileModified == mLastModified) {
// Already have up to date data
return;
}
mLastModified = fileModified;
mActivityAttributes = new HashMap<String, ActivityAttributes>();
mManifestTheme = null;
mTargetSdk = 1; // Default when not specified
mMinSdk = 1; // Default when not specified
mMinSdkName = "1"; // Default when not specified
mPackage = ""; //$NON-NLS-1$
mApplicationIcon = null;
mApplicationLabel = null;
mApplicationSupportsRtl = false;
Document document = null;
try {
DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
InputSource is = new InputSource(mManifestFile.getContents());
factory.setNamespaceAware(true);
factory.setValidating(false);
DocumentBuilder builder = factory.newDocumentBuilder();
document = builder.parse(is);
Element root = document.getDocumentElement();
mPackage = root.getAttribute(ATTRIBUTE_PACKAGE);
NodeList activities = document.getElementsByTagName(NODE_ACTIVITY);
for (int i = 0, n = activities.getLength(); i < n; i++) {
Element activity = (Element) activities.item(i);
ActivityAttributes info = new ActivityAttributes(activity, mPackage);
mActivityAttributes.put(info.getName(), info);
}
NodeList applications = root.getElementsByTagName(AndroidManifest.NODE_APPLICATION);
if (applications.getLength() > 0) {
assert applications.getLength() == 1;
Element application = (Element) applications.item(0);
if (application.hasAttributeNS(NS_RESOURCES, ATTRIBUTE_ICON)) {
mApplicationIcon = application.getAttributeNS(NS_RESOURCES, ATTRIBUTE_ICON);
}
if (application.hasAttributeNS(NS_RESOURCES, ATTRIBUTE_LABEL)) {
mApplicationLabel = application.getAttributeNS(NS_RESOURCES, ATTRIBUTE_LABEL);
}
if (SdkConstants.VALUE_TRUE.equals(application.getAttributeNS(NS_RESOURCES,
ATTRIBUTE_SUPPORTS_RTL))) {
mApplicationSupportsRtl = true;
}
String defaultTheme = application.getAttributeNS(NS_RESOURCES, ATTRIBUTE_THEME);
if (defaultTheme != null && !defaultTheme.isEmpty()) {
// From manifest theme documentation:
// "If that attribute is also not set, the default system theme is used."
mManifestTheme = defaultTheme;
}
}
// Look up target SDK
NodeList usesSdks = root.getElementsByTagName(NODE_USES_SDK);
if (usesSdks.getLength() > 0) {
Element usesSdk = (Element) usesSdks.item(0);
mMinSdk = getApiVersion(usesSdk, ATTRIBUTE_MIN_SDK_VERSION, 1);
mTargetSdk = getApiVersion(usesSdk, ATTRIBUTE_TARGET_SDK_VERSION, mMinSdk);
}
} catch (SAXException e) {
AdtPlugin.log(e, "Malformed manifest");
} catch (Exception e) {
AdtPlugin.log(e, "Could not read Manifest data");
}
}
private int getApiVersion(Element usesSdk, String attribute, int defaultApiLevel) {
String valueString = null;
if (usesSdk.hasAttributeNS(NS_RESOURCES, attribute)) {
valueString = usesSdk.getAttributeNS(NS_RESOURCES, attribute);
if (attribute.equals(ATTRIBUTE_MIN_SDK_VERSION)) {
mMinSdkName = valueString;
}
}
if (valueString != null) {
int apiLevel = -1;
try {
apiLevel = Integer.valueOf(valueString);
} catch (NumberFormatException e) {
// Handle codename
if (Sdk.getCurrent() != null) {
IAndroidTarget target = Sdk.getCurrent().getTargetFromHashString(
"android-" + valueString); //$NON-NLS-1$
if (target != null) {
// codename future API level is current api + 1
apiLevel = target.getVersion().getApiLevel() + 1;
}
}
}
return apiLevel;
}
return defaultApiLevel;
}
/**
* Returns the default package registered in the Android manifest
*
* @return the default package registered in the manifest
*/
@NonNull
public String getPackage() {
sync();
return mPackage;
}
/**
* Returns a map from activity full class names to the corresponding {@link ActivityAttributes}.
*
* @return a map from activity fqcn to ActivityAttributes
*/
@NonNull
public Map<String, ActivityAttributes> getActivityAttributesMap() {
sync();
return mActivityAttributes;
}
/**
* Returns the attributes of an activity given its full class name.
*/
@Nullable
public ActivityAttributes getActivityAttributes(String activity) {
return getActivityAttributesMap().get(activity);
}
/**
* Returns the manifest theme registered on the application, if any
*
* @return a manifest theme, or null if none was registered
*/
@Nullable
public String getManifestTheme() {
sync();
return mManifestTheme;
}
/**
* Returns the default theme for this project, by looking at the manifest default
* theme registration, target SDK, rendering target, etc.
*
* @param renderingTarget the rendering target use to render the theme, or null
* @param screenSize the screen size to obtain a default theme for, or null if unknown
* @return the theme to use for this project, never null
*/
@NonNull
public String getDefaultTheme(IAndroidTarget renderingTarget, ScreenSize screenSize) {
sync();
if (mManifestTheme != null) {
return mManifestTheme;
}
int renderingTargetSdk = mTargetSdk;
if (renderingTarget != null) {
renderingTargetSdk = renderingTarget.getVersion().getApiLevel();
}
int apiLevel = Math.min(mTargetSdk, renderingTargetSdk);
// For now this theme works only on XLARGE screens. When it works for all sizes,
// add that new apiLevel to this check.
if (apiLevel >= 11 && screenSize == ScreenSize.XLARGE || apiLevel >= 14) {
return ANDROID_STYLE_RESOURCE_PREFIX + "Theme.Holo"; //$NON-NLS-1$
} else {
return ANDROID_STYLE_RESOURCE_PREFIX + "Theme"; //$NON-NLS-1$
}
}
/**
* Returns the application icon, or null
*
* @return the application icon, or null
*/
@Nullable
public String getApplicationIcon() {
sync();
return mApplicationIcon;
}
/**
* Returns the application label, or null
*
* @return the application label, or null
*/
@Nullable
public String getApplicationLabel() {
sync();
return mApplicationLabel;
}
/**
* Returns true if the application has RTL support.
*
* @return true if the application has RTL support.
*/
public boolean isRtlSupported() {
sync();
return mApplicationSupportsRtl;
}
/**
* Returns the target SDK version
*
* @return the target SDK version
*/
public int getTargetSdkVersion() {
sync();
return mTargetSdk;
}
/**
* Returns the minimum SDK version
*
* @return the minimum SDK version
*/
public int getMinSdkVersion() {
sync();
return mMinSdk;
}
/**
* Returns the minimum SDK version name (which may not be a numeric string, e.g.
* it could be a codename). It will never be null or empty; if no min sdk version
* was specified in the manifest, the return value will be "1". Use
* {@link #getMinSdkCodeName()} instead if you want to look up whether there is a code name.
*
* @return the minimum SDK version
*/
@NonNull
public String getMinSdkName() {
sync();
if (mMinSdkName == null || mMinSdkName.isEmpty()) {
mMinSdkName = "1"; //$NON-NLS-1$
}
return mMinSdkName;
}
/**
* Returns the code name used for the minimum SDK version, if any.
*
* @return the minSdkVersion codename or null
*/
@Nullable
public String getMinSdkCodeName() {
String minSdkName = getMinSdkName();
if (!Character.isDigit(minSdkName.charAt(0))) {
return minSdkName;
}
return null;
}
/**
* Returns the {@link IPackageFragment} for the package registered in the manifest
*
* @return the {@link IPackageFragment} for the package registered in the manifest
*/
@Nullable
public IPackageFragment getPackageFragment() {
sync();
try {
IJavaProject javaProject = BaseProjectHelper.getJavaProject(mProject);
if (javaProject != null) {
IPackageFragmentRoot root = ManifestInfo.getSourcePackageRoot(javaProject);
if (root != null) {
return root.getPackageFragment(mPackage);
}
}
} catch (CoreException e) {
AdtPlugin.log(e, null);
}
return null;
}
/**
* Returns the activity associated with the given layout file. Makes an educated guess
* by peeking at the usages of the R.layout.name field corresponding to the layout and
* if it finds a usage.
*
* @param project the project containing the layout
* @param layoutName the layout whose activity we want to look up
* @param pkg the package containing activities
* @return the activity name
*/
@Nullable
public static String guessActivity(IProject project, String layoutName, String pkg) {
List<String> activities = guessActivities(project, layoutName, pkg);
if (activities.size() > 0) {
return activities.get(0);
} else {
return null;
}
}
/**
* Returns the activities associated with the given layout file. Makes an educated guess
* by peeking at the usages of the R.layout.name field corresponding to the layout and
* if it finds a usage.
*
* @param project the project containing the layout
* @param layoutName the layout whose activity we want to look up
* @param pkg the package containing activities
* @return the activity name
*/
@NonNull
public static List<String> guessActivities(IProject project, String layoutName, String pkg) {
final LinkedList<String> activities = new LinkedList<String>();
SearchRequestor requestor = new SearchRequestor() {
@Override
public void acceptSearchMatch(SearchMatch match) throws CoreException {
Object element = match.getElement();
if (element instanceof IMethod) {
IMethod method = (IMethod) element;
IType declaringType = method.getDeclaringType();
String fqcn = declaringType.getFullyQualifiedName();
if ((declaringType.getSuperclassName() != null &&
declaringType.getSuperclassName().endsWith("Activity")) //$NON-NLS-1$
|| method.getElementName().equals("onCreate")) { //$NON-NLS-1$
activities.addFirst(fqcn);
} else {
activities.addLast(fqcn);
}
}
}
};
try {
IJavaProject javaProject = BaseProjectHelper.getJavaProject(project);
if (javaProject == null) {
return Collections.emptyList();
}
// TODO - look around a bit more and see if we can figure out whether the
// call if from within a setContentView call!
// Search for which java classes call setContentView(R.layout.layoutname);
String typeFqcn = "R.layout"; //$NON-NLS-1$
if (pkg != null) {
typeFqcn = pkg + '.' + typeFqcn;
}
IType type = javaProject.findType(typeFqcn);
if (type != null) {
IField field = type.getField(layoutName);
if (field.exists()) {
SearchPattern pattern = SearchPattern.createPattern(field, REFERENCES);
try {
search(requestor, javaProject, pattern);
} catch (OperationCanceledException canceled) {
// pass
}
}
}
} catch (CoreException e) {
AdtPlugin.log(e, null);
}
return activities;
}
/**
* Returns all activities found in the given project (including those in libraries,
* except for android.jar itself)
*
* @param project the project
* @return a list of activity classes as fully qualified class names
*/
@SuppressWarnings("restriction") // BinaryType
@NonNull
public static List<String> getProjectActivities(IProject project) {
final List<String> activities = new ArrayList<String>();
try {
final IJavaProject javaProject = BaseProjectHelper.getJavaProject(project);
if (javaProject != null) {
IType[] activityTypes = new IType[0];
IType activityType = javaProject.findType(CLASS_ACTIVITY);
if (activityType != null) {
ITypeHierarchy hierarchy =
activityType.newTypeHierarchy(javaProject, new NullProgressMonitor());
activityTypes = hierarchy.getAllSubtypes(activityType);
for (IType type : activityTypes) {
if (type instanceof BinaryType && (type.getClassFile() == null
|| type.getClassFile().getResource() == null)) {
continue;
}
activities.add(type.getFullyQualifiedName());
}
}
}
} catch (CoreException e) {
AdtPlugin.log(e, null);
}
return activities;
}
/**
* Returns the activity associated with the given layout file.
* <p>
* This is an alternative to {@link #guessActivity(IFile, String)}. Whereas
* guessActivity simply looks for references to "R.layout.foo", this method searches
* for all usages of Activity#setContentView(int), and for each match it looks up the
* corresponding call text (such as "setContentView(R.layout.foo)"). From this it uses
* a regexp to pull out "foo" from this, and stores the association that layout "foo"
* is associated with the activity class that contained the setContentView call.
* <p>
* This has two potential advantages:
* <ol>
* <li>It can be faster. We do the reference search -once-, and we've built a map of
* all the layout-to-activity mappings which we can then immediately look up other
* layouts for, which is particularly useful at startup when we have to compute the
* layout activity associations to populate the theme choosers.
* <li>It can be more accurate. Just because an activity references an "R.layout.foo"
* field doesn't mean it's setting it as a content view.
* </ol>
* However, this second advantage is also its chief problem. There are some common
* code constructs which means that the associated layout is not explicitly referenced
* in a direct setContentView call; on a couple of sample projects I tested I found
* patterns like for example "setContentView(v)" where "v" had been computed earlier.
* Therefore, for now we're going to stick with the more general approach of just
* looking up each field when needed. We're keeping the code around, though statically
* compiled out with the "if (false)" construct below in case we revisit this.
*
* @param layoutFile the layout whose activity we want to look up
* @return the activity name
*/
@SuppressWarnings("all")
@Nullable
public String guessActivityBySetContentView(String layoutName) {
if (false) {
// These should be fields
final Pattern LAYOUT_FIELD_PATTERN =
Pattern.compile("R\\.layout\\.([a-z0-9_]+)"); //$NON-NLS-1$
Map<String, String> mUsages = null;
sync();
if (mUsages == null) {
final Map<String, String> usages = new HashMap<String, String>();
mUsages = usages;
SearchRequestor requestor = new SearchRequestor() {
@Override
public void acceptSearchMatch(SearchMatch match) throws CoreException {
Object element = match.getElement();
if (element instanceof IMethod) {
IMethod method = (IMethod) element;
IType declaringType = method.getDeclaringType();
String fqcn = declaringType.getFullyQualifiedName();
IDocumentProvider provider = new TextFileDocumentProvider();
IResource resource = match.getResource();
try {
provider.connect(resource);
IDocument document = provider.getDocument(resource);
if (document != null) {
String matchText = document.get(match.getOffset(),
match.getLength());
Matcher matcher = LAYOUT_FIELD_PATTERN.matcher(matchText);
if (matcher.find()) {
usages.put(matcher.group(1), fqcn);
}
}
} catch (Exception e) {
AdtPlugin.log(e, "Can't find range information for %1$s",
resource.getName());
} finally {
provider.disconnect(resource);
}
}
}
};
try {
IJavaProject javaProject = BaseProjectHelper.getJavaProject(mProject);
if (javaProject == null) {
return null;
}
// Search for which java classes call setContentView(R.layout.layoutname);
String typeFqcn = "R.layout"; //$NON-NLS-1$
if (mPackage != null) {
typeFqcn = mPackage + '.' + typeFqcn;
}
IType activityType = javaProject.findType(CLASS_ACTIVITY);
if (activityType != null) {
IMethod method = activityType.getMethod(
"setContentView", new String[] {"I"}); //$NON-NLS-1$ //$NON-NLS-2$
if (method.exists()) {
SearchPattern pattern = SearchPattern.createPattern(method,
REFERENCES);
search(requestor, javaProject, pattern);
}
}
} catch (CoreException e) {
AdtPlugin.log(e, null);
}
}
return mUsages.get(layoutName);
}
return null;
}
/**
* Performs a search using the given pattern, scope and handler. The search will abort
* if it takes longer than {@link #SEARCH_TIMEOUT_MS} milliseconds.
*/
private static void search(SearchRequestor requestor, IJavaProject javaProject,
SearchPattern pattern) throws CoreException {
// Find the package fragment specified in the manifest; the activities should
// live there.
IJavaSearchScope scope = createPackageScope(javaProject);
SearchParticipant[] participants = new SearchParticipant[] {
SearchEngine.getDefaultSearchParticipant()
};
SearchEngine engine = new SearchEngine();
final long searchStart = System.currentTimeMillis();
NullProgressMonitor monitor = new NullProgressMonitor() {
private boolean mCancelled;
@Override
public void internalWorked(double work) {
long searchEnd = System.currentTimeMillis();
if (searchEnd - searchStart > SEARCH_TIMEOUT_MS) {
mCancelled = true;
}
}
@Override
public boolean isCanceled() {
return mCancelled;
}
};
engine.search(pattern, participants, scope, requestor, monitor);
}
/** Creates a package search scope for the first package root in the given java project */
private static IJavaSearchScope createPackageScope(IJavaProject javaProject) {
IPackageFragmentRoot packageRoot = getSourcePackageRoot(javaProject);
IJavaSearchScope scope;
if (packageRoot != null) {
IJavaElement[] scopeElements = new IJavaElement[] { packageRoot };
scope = SearchEngine.createJavaSearchScope(scopeElements);
} else {
scope = SearchEngine.createWorkspaceScope();
}
return scope;
}
/**
* Returns the first package root for the given java project
*
* @param javaProject the project to search in
* @return the first package root, or null
*/
@Nullable
public static IPackageFragmentRoot getSourcePackageRoot(IJavaProject javaProject) {
IPackageFragmentRoot packageRoot = null;
List<IPath> sources = BaseProjectHelper.getSourceClasspaths(javaProject);
IWorkspace workspace = ResourcesPlugin.getWorkspace();
for (IPath path : sources) {
IResource firstSource = workspace.getRoot().findMember(path);
if (firstSource != null) {
packageRoot = javaProject.getPackageFragmentRoot(firstSource);
if (packageRoot != null) {
break;
}
}
}
return packageRoot;
}
/**
* Computes the minimum SDK and target SDK versions for the project
*
* @param project the project to look up the versions for
* @return a pair of (minimum SDK, target SDK) versions, never null
*/
@NonNull
public static Pair<Integer, Integer> computeSdkVersions(IProject project) {
int mMinSdkVersion = 1;
int mTargetSdkVersion = 1;
IAbstractFile manifestFile = AndroidManifest.getManifest(new IFolderWrapper(project));
if (manifestFile != null) {
try {
Object value = AndroidManifest.getMinSdkVersion(manifestFile);
mMinSdkVersion = 1; // Default case if missing
if (value instanceof Integer) {
mMinSdkVersion = ((Integer) value).intValue();
} else if (value instanceof String) {
// handle codename, only if we can resolve it.
if (Sdk.getCurrent() != null) {
IAndroidTarget target = Sdk.getCurrent().getTargetFromHashString(
"android-" + value); //$NON-NLS-1$
if (target != null) {
// codename future API level is current api + 1
mMinSdkVersion = target.getVersion().getApiLevel() + 1;
}
}
}
value = AndroidManifest.getTargetSdkVersion(manifestFile);
if (value == null) {
mTargetSdkVersion = mMinSdkVersion;
} else if (value instanceof String) {
// handle codename, only if we can resolve it.
if (Sdk.getCurrent() != null) {
IAndroidTarget target = Sdk.getCurrent().getTargetFromHashString(
"android-" + value); //$NON-NLS-1$
if (target != null) {
// codename future API level is current api + 1
mTargetSdkVersion = target.getVersion().getApiLevel() + 1;
}
}
}
} catch (XPathExpressionException e) {
// do nothing we'll use 1 below.
} catch (StreamException e) {
// do nothing we'll use 1 below.
}
}
return Pair.of(mMinSdkVersion, mTargetSdkVersion);
}
}