com.android.ide.eclipse.adt.internal.editors.manifest.ManifestInfo.java Source code

Java tutorial

Introduction

Here is the source code for com.android.ide.eclipse.adt.internal.editors.manifest.ManifestInfo.java

Source

/*
 * 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);
    }
}