Home | History | Annotate | Download | only in manifest
      1 /*
      2  * Copyright (C) 2011 The Android Open Source Project
      3  *
      4  * Licensed under the Eclipse Public License, Version 1.0 (the "License");
      5  * you may not use this file except in compliance with the License.
      6  * You may obtain a copy of the License at
      7  *
      8  *      http://www.eclipse.org/org/documents/epl-v10.php
      9  *
     10  * Unless required by applicable law or agreed to in writing, software
     11  * distributed under the License is distributed on an "AS IS" BASIS,
     12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     13  * See the License for the specific language governing permissions and
     14  * limitations under the License.
     15  */
     16 
     17 package com.android.ide.eclipse.adt.internal.editors.manifest;
     18 
     19 import static com.android.SdkConstants.ANDROID_STYLE_RESOURCE_PREFIX;
     20 import static com.android.SdkConstants.CLASS_ACTIVITY;
     21 import static com.android.SdkConstants.NS_RESOURCES;
     22 import static com.android.xml.AndroidManifest.ATTRIBUTE_ICON;
     23 import static com.android.xml.AndroidManifest.ATTRIBUTE_LABEL;
     24 import static com.android.xml.AndroidManifest.ATTRIBUTE_MIN_SDK_VERSION;
     25 import static com.android.xml.AndroidManifest.ATTRIBUTE_NAME;
     26 import static com.android.xml.AndroidManifest.ATTRIBUTE_PACKAGE;
     27 import static com.android.xml.AndroidManifest.ATTRIBUTE_TARGET_SDK_VERSION;
     28 import static com.android.xml.AndroidManifest.ATTRIBUTE_THEME;
     29 import static com.android.xml.AndroidManifest.NODE_ACTIVITY;
     30 import static com.android.xml.AndroidManifest.NODE_USES_SDK;
     31 import static org.eclipse.jdt.core.search.IJavaSearchConstants.REFERENCES;
     32 
     33 import com.android.annotations.NonNull;
     34 import com.android.annotations.Nullable;
     35 import com.android.ide.eclipse.adt.AdtPlugin;
     36 import com.android.ide.eclipse.adt.internal.project.BaseProjectHelper;
     37 import com.android.ide.eclipse.adt.internal.sdk.Sdk;
     38 import com.android.ide.eclipse.adt.io.IFolderWrapper;
     39 import com.android.io.IAbstractFile;
     40 import com.android.io.StreamException;
     41 import com.android.resources.ScreenSize;
     42 import com.android.sdklib.IAndroidTarget;
     43 import com.android.utils.Pair;
     44 import com.android.xml.AndroidManifest;
     45 
     46 import org.eclipse.core.resources.IFile;
     47 import org.eclipse.core.resources.IProject;
     48 import org.eclipse.core.resources.IResource;
     49 import org.eclipse.core.resources.IWorkspace;
     50 import org.eclipse.core.resources.ResourcesPlugin;
     51 import org.eclipse.core.runtime.CoreException;
     52 import org.eclipse.core.runtime.IPath;
     53 import org.eclipse.core.runtime.NullProgressMonitor;
     54 import org.eclipse.core.runtime.OperationCanceledException;
     55 import org.eclipse.core.runtime.QualifiedName;
     56 import org.eclipse.jdt.core.IField;
     57 import org.eclipse.jdt.core.IJavaElement;
     58 import org.eclipse.jdt.core.IJavaProject;
     59 import org.eclipse.jdt.core.IMethod;
     60 import org.eclipse.jdt.core.IPackageFragment;
     61 import org.eclipse.jdt.core.IPackageFragmentRoot;
     62 import org.eclipse.jdt.core.IType;
     63 import org.eclipse.jdt.core.ITypeHierarchy;
     64 import org.eclipse.jdt.core.search.IJavaSearchScope;
     65 import org.eclipse.jdt.core.search.SearchEngine;
     66 import org.eclipse.jdt.core.search.SearchMatch;
     67 import org.eclipse.jdt.core.search.SearchParticipant;
     68 import org.eclipse.jdt.core.search.SearchPattern;
     69 import org.eclipse.jdt.core.search.SearchRequestor;
     70 import org.eclipse.jdt.internal.core.BinaryType;
     71 import org.eclipse.jface.text.IDocument;
     72 import org.eclipse.ui.editors.text.TextFileDocumentProvider;
     73 import org.eclipse.ui.texteditor.IDocumentProvider;
     74 import org.w3c.dom.Document;
     75 import org.w3c.dom.Element;
     76 import org.w3c.dom.NodeList;
     77 import org.xml.sax.InputSource;
     78 import org.xml.sax.SAXException;
     79 
     80 import java.util.ArrayList;
     81 import java.util.Collections;
     82 import java.util.HashMap;
     83 import java.util.LinkedList;
     84 import java.util.List;
     85 import java.util.Map;
     86 import java.util.regex.Matcher;
     87 import java.util.regex.Pattern;
     88 
     89 import javax.xml.parsers.DocumentBuilder;
     90 import javax.xml.parsers.DocumentBuilderFactory;
     91 import javax.xml.xpath.XPathExpressionException;
     92 
     93 /**
     94  * Retrieves and caches manifest information such as the themes to be used for
     95  * a given activity.
     96  *
     97  * @see AndroidManifest
     98  */
     99 public class ManifestInfo {
    100     /**
    101      * The maximum number of milliseconds to search for an activity in the codebase when
    102      * attempting to associate layouts with activities in
    103      * {@link #guessActivity(IFile, String)}
    104      */
    105     private static final int SEARCH_TIMEOUT_MS = 3000;
    106 
    107     private final IProject mProject;
    108     private String mPackage;
    109     private String mManifestTheme;
    110     private Map<String, String> mActivityThemes;
    111     private IAbstractFile mManifestFile;
    112     private long mLastModified;
    113     private long mLastChecked;
    114     private String mMinSdkName;
    115     private int mMinSdk;
    116     private int mTargetSdk;
    117     private String mApplicationIcon;
    118     private String mApplicationLabel;
    119 
    120     /**
    121      * Qualified name for the per-project non-persistent property storing the
    122      * {@link ManifestInfo} for this project
    123      */
    124     final static QualifiedName MANIFEST_FINDER = new QualifiedName(AdtPlugin.PLUGIN_ID,
    125             "manifest"); //$NON-NLS-1$
    126 
    127     /**
    128      * Constructs an {@link ManifestInfo} for the given project. Don't use this method;
    129      * use the {@link #get} factory method instead.
    130      *
    131      * @param project project to create an {@link ManifestInfo} for
    132      */
    133     private ManifestInfo(IProject project) {
    134         mProject = project;
    135     }
    136 
    137     /**
    138      * Clears the cached manifest information. The next get call on one of the
    139      * properties will cause the information to be refreshed.
    140      */
    141     public void clear() {
    142         mLastChecked = 0;
    143     }
    144 
    145     /**
    146      * Returns the {@link ManifestInfo} for the given project
    147      *
    148      * @param project the project the finder is associated with
    149      * @return a {@ManifestInfo} for the given project, never null
    150      */
    151     @NonNull
    152     public static ManifestInfo get(IProject project) {
    153         ManifestInfo finder = null;
    154         try {
    155             finder = (ManifestInfo) project.getSessionProperty(MANIFEST_FINDER);
    156         } catch (CoreException e) {
    157             // Not a problem; we will just create a new one
    158         }
    159 
    160         if (finder == null) {
    161             finder = new ManifestInfo(project);
    162             try {
    163                 project.setSessionProperty(MANIFEST_FINDER, finder);
    164             } catch (CoreException e) {
    165                 AdtPlugin.log(e, "Can't store ManifestInfo");
    166             }
    167         }
    168 
    169         return finder;
    170     }
    171 
    172     /**
    173      * Ensure that the package, theme and activity maps are initialized and up to date
    174      * with respect to the manifest file
    175      */
    176     private void sync() {
    177         // Since each of the accessors call sync(), allow a bunch of immediate
    178         // accessors to all bypass the file stat() below
    179         long now = System.currentTimeMillis();
    180         if (now - mLastChecked < 50 && mManifestFile != null) {
    181             return;
    182         }
    183         mLastChecked = now;
    184 
    185         if (mManifestFile == null) {
    186             IFolderWrapper projectFolder = new IFolderWrapper(mProject);
    187             mManifestFile = AndroidManifest.getManifest(projectFolder);
    188             if (mManifestFile == null) {
    189                 return;
    190             }
    191         }
    192 
    193         // Check to see if our data is up to date
    194         long fileModified = mManifestFile.getModificationStamp();
    195         if (fileModified == mLastModified) {
    196             // Already have up to date data
    197             return;
    198         }
    199         mLastModified = fileModified;
    200 
    201         mActivityThemes = new HashMap<String, String>();
    202         mManifestTheme = null;
    203         mTargetSdk = 1; // Default when not specified
    204         mMinSdk = 1; // Default when not specified
    205         mMinSdkName = "1"; // Default when not specified
    206         mPackage = ""; //$NON-NLS-1$
    207         mApplicationIcon = null;
    208         mApplicationLabel = null;
    209 
    210         Document document = null;
    211         try {
    212             DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
    213             InputSource is = new InputSource(mManifestFile.getContents());
    214 
    215             factory.setNamespaceAware(true);
    216             factory.setValidating(false);
    217             DocumentBuilder builder = factory.newDocumentBuilder();
    218             document = builder.parse(is);
    219 
    220             Element root = document.getDocumentElement();
    221             mPackage = root.getAttribute(ATTRIBUTE_PACKAGE);
    222             NodeList activities = document.getElementsByTagName(NODE_ACTIVITY);
    223             for (int i = 0, n = activities.getLength(); i < n; i++) {
    224                 Element activity = (Element) activities.item(i);
    225                 String theme = activity.getAttributeNS(NS_RESOURCES, ATTRIBUTE_THEME);
    226                 if (theme != null && theme.length() > 0) {
    227                     String name = activity.getAttributeNS(NS_RESOURCES, ATTRIBUTE_NAME);
    228                     int index = name.indexOf('.');
    229                     if (index <= 0 && mPackage != null && !mPackage.isEmpty()) {
    230                       name =  mPackage + (index == -1 ? "." : "") + name;
    231                     }
    232                     mActivityThemes.put(name, theme);
    233                 }
    234             }
    235 
    236             NodeList applications = root.getElementsByTagName(AndroidManifest.NODE_APPLICATION);
    237             if (applications.getLength() > 0) {
    238                 assert applications.getLength() == 1;
    239                 Element application = (Element) applications.item(0);
    240                 if (application.hasAttributeNS(NS_RESOURCES, ATTRIBUTE_ICON)) {
    241                     mApplicationIcon = application.getAttributeNS(NS_RESOURCES, ATTRIBUTE_ICON);
    242                 }
    243                 if (application.hasAttributeNS(NS_RESOURCES, ATTRIBUTE_LABEL)) {
    244                     mApplicationLabel = application.getAttributeNS(NS_RESOURCES, ATTRIBUTE_LABEL);
    245                 }
    246 
    247                 String defaultTheme = application.getAttributeNS(NS_RESOURCES, ATTRIBUTE_THEME);
    248                 if (defaultTheme != null && !defaultTheme.isEmpty()) {
    249                     // From manifest theme documentation:
    250                     // "If that attribute is also not set, the default system theme is used."
    251                     mManifestTheme = defaultTheme;
    252                 }
    253             }
    254 
    255             // Look up target SDK
    256             NodeList usesSdks = root.getElementsByTagName(NODE_USES_SDK);
    257             if (usesSdks.getLength() > 0) {
    258                 Element usesSdk = (Element) usesSdks.item(0);
    259                 mMinSdk = getApiVersion(usesSdk, ATTRIBUTE_MIN_SDK_VERSION, 1);
    260                 mTargetSdk = getApiVersion(usesSdk, ATTRIBUTE_TARGET_SDK_VERSION, mMinSdk);
    261             }
    262 
    263         } catch (SAXException e) {
    264             AdtPlugin.log(e, "Malformed manifest");
    265         } catch (Exception e) {
    266             AdtPlugin.log(e, "Could not read Manifest data");
    267         }
    268     }
    269 
    270     private int getApiVersion(Element usesSdk, String attribute, int defaultApiLevel) {
    271         String valueString = null;
    272         if (usesSdk.hasAttributeNS(NS_RESOURCES, attribute)) {
    273             valueString = usesSdk.getAttributeNS(NS_RESOURCES, attribute);
    274             if (attribute.equals(ATTRIBUTE_MIN_SDK_VERSION)) {
    275                 mMinSdkName = valueString;
    276             }
    277         }
    278 
    279         if (valueString != null) {
    280             int apiLevel = -1;
    281             try {
    282                 apiLevel = Integer.valueOf(valueString);
    283             } catch (NumberFormatException e) {
    284                 // Handle codename
    285                 if (Sdk.getCurrent() != null) {
    286                     IAndroidTarget target = Sdk.getCurrent().getTargetFromHashString(
    287                             "android-" + valueString); //$NON-NLS-1$
    288                     if (target != null) {
    289                         // codename future API level is current api + 1
    290                         apiLevel = target.getVersion().getApiLevel() + 1;
    291                     }
    292                 }
    293             }
    294 
    295             return apiLevel;
    296         }
    297 
    298         return defaultApiLevel;
    299     }
    300 
    301     /**
    302      * Returns the default package registered in the Android manifest
    303      *
    304      * @return the default package registered in the manifest
    305      */
    306     @NonNull
    307     public String getPackage() {
    308         sync();
    309         return mPackage;
    310     }
    311 
    312     /**
    313      * Returns a map from activity full class names to the corresponding theme style to be
    314      * used
    315      *
    316      * @return a map from activity fqcn to theme style
    317      */
    318     @NonNull
    319     public Map<String, String> getActivityThemes() {
    320         sync();
    321         return mActivityThemes;
    322     }
    323 
    324     /**
    325      * Returns the manifest theme registered on the application, if any
    326      *
    327      * @return a manifest theme, or null if none was registered
    328      */
    329     @Nullable
    330     public String getManifestTheme() {
    331         sync();
    332         return mManifestTheme;
    333     }
    334 
    335     /**
    336      * Returns the default theme for this project, by looking at the manifest default
    337      * theme registration, target SDK, rendering target, etc.
    338      *
    339      * @param renderingTarget the rendering target use to render the theme, or null
    340      * @param screenSize the screen size to obtain a default theme for, or null if unknown
    341      * @return the theme to use for this project, never null
    342      */
    343     @NonNull
    344     public String getDefaultTheme(IAndroidTarget renderingTarget, ScreenSize screenSize) {
    345         sync();
    346 
    347         if (mManifestTheme != null) {
    348             return mManifestTheme;
    349         }
    350 
    351         int renderingTargetSdk = mTargetSdk;
    352         if (renderingTarget != null) {
    353             renderingTargetSdk = renderingTarget.getVersion().getApiLevel();
    354         }
    355 
    356         int apiLevel = Math.min(mTargetSdk, renderingTargetSdk);
    357         // For now this theme works only on XLARGE screens. When it works for all sizes,
    358         // add that new apiLevel to this check.
    359         if (apiLevel >= 11 && screenSize == ScreenSize.XLARGE || apiLevel >= 14) {
    360             return ANDROID_STYLE_RESOURCE_PREFIX + "Theme.Holo"; //$NON-NLS-1$
    361         } else {
    362             return ANDROID_STYLE_RESOURCE_PREFIX + "Theme"; //$NON-NLS-1$
    363         }
    364     }
    365 
    366     /**
    367      * Returns the application icon, or null
    368      *
    369      * @return the application icon, or null
    370      */
    371     @Nullable
    372     public String getApplicationIcon() {
    373         sync();
    374         return mApplicationIcon;
    375     }
    376 
    377     /**
    378      * Returns the application label, or null
    379      *
    380      * @return the application label, or null
    381      */
    382     @Nullable
    383     public String getApplicationLabel() {
    384         sync();
    385         return mApplicationLabel;
    386     }
    387 
    388     /**
    389      * Returns the target SDK version
    390      *
    391      * @return the target SDK version
    392      */
    393     public int getTargetSdkVersion() {
    394         sync();
    395         return mTargetSdk;
    396     }
    397 
    398     /**
    399      * Returns the minimum SDK version
    400      *
    401      * @return the minimum SDK version
    402      */
    403     public int getMinSdkVersion() {
    404         sync();
    405         return mMinSdk;
    406     }
    407 
    408     /**
    409      * Returns the minimum SDK version name (which may not be a numeric string, e.g.
    410      * it could be a codename). It will never be null or empty; if no min sdk version
    411      * was specified in the manifest, the return value will be "1". Use
    412      * {@link #getMinSdkCodeName()} instead if you want to look up whether there is a code name.
    413      *
    414      * @return the minimum SDK version
    415      */
    416     @NonNull
    417     public String getMinSdkName() {
    418         sync();
    419         if (mMinSdkName == null || mMinSdkName.isEmpty()) {
    420             mMinSdkName = "1"; //$NON-NLS-1$
    421         }
    422 
    423         return mMinSdkName;
    424     }
    425 
    426     /**
    427      * Returns the code name used for the minimum SDK version, if any.
    428      *
    429      * @return the minSdkVersion codename or null
    430      */
    431     @Nullable
    432     public String getMinSdkCodeName() {
    433         String minSdkName = getMinSdkName();
    434         if (!Character.isDigit(minSdkName.charAt(0))) {
    435             return minSdkName;
    436         }
    437 
    438         return null;
    439     }
    440 
    441     /**
    442      * Returns the {@link IPackageFragment} for the package registered in the manifest
    443      *
    444      * @return the {@link IPackageFragment} for the package registered in the manifest
    445      */
    446     @Nullable
    447     public IPackageFragment getPackageFragment() {
    448         sync();
    449         try {
    450             IJavaProject javaProject = BaseProjectHelper.getJavaProject(mProject);
    451             if (javaProject != null) {
    452                 IPackageFragmentRoot root = ManifestInfo.getSourcePackageRoot(javaProject);
    453                 if (root != null) {
    454                     return root.getPackageFragment(mPackage);
    455                 }
    456             }
    457         } catch (CoreException e) {
    458             AdtPlugin.log(e, null);
    459         }
    460 
    461         return null;
    462     }
    463 
    464     /**
    465      * Returns the activity associated with the given layout file. Makes an educated guess
    466      * by peeking at the usages of the R.layout.name field corresponding to the layout and
    467      * if it finds a usage.
    468      *
    469      * @param project the project containing the layout
    470      * @param layoutName the layout whose activity we want to look up
    471      * @param pkg the package containing activities
    472      * @return the activity name
    473      */
    474     @Nullable
    475     public static String guessActivity(IProject project, String layoutName, String pkg) {
    476         List<String> activities = guessActivities(project, layoutName, pkg);
    477         if (activities.size() > 0) {
    478             return activities.get(0);
    479         } else {
    480             return null;
    481         }
    482     }
    483 
    484     /**
    485      * Returns the activities associated with the given layout file. Makes an educated guess
    486      * by peeking at the usages of the R.layout.name field corresponding to the layout and
    487      * if it finds a usage.
    488      *
    489      * @param project the project containing the layout
    490      * @param layoutName the layout whose activity we want to look up
    491      * @param pkg the package containing activities
    492      * @return the activity name
    493      */
    494     @NonNull
    495     public static List<String> guessActivities(IProject project, String layoutName, String pkg) {
    496         final LinkedList<String> activities = new LinkedList<String>();
    497         SearchRequestor requestor = new SearchRequestor() {
    498             @Override
    499             public void acceptSearchMatch(SearchMatch match) throws CoreException {
    500                 Object element = match.getElement();
    501                 if (element instanceof IMethod) {
    502                     IMethod method = (IMethod) element;
    503                     IType declaringType = method.getDeclaringType();
    504                     String fqcn = declaringType.getFullyQualifiedName();
    505 
    506                     if ((declaringType.getSuperclassName() != null &&
    507                             declaringType.getSuperclassName().endsWith("Activity")) //$NON-NLS-1$
    508                         || method.getElementName().equals("onCreate")) { //$NON-NLS-1$
    509                         activities.addFirst(fqcn);
    510                     } else {
    511                         activities.addLast(fqcn);
    512                     }
    513                 }
    514             }
    515         };
    516         try {
    517             IJavaProject javaProject = BaseProjectHelper.getJavaProject(project);
    518             if (javaProject == null) {
    519                 return Collections.emptyList();
    520             }
    521             // TODO - look around a bit more and see if we can figure out whether the
    522             // call if from within a setContentView call!
    523 
    524             // Search for which java classes call setContentView(R.layout.layoutname);
    525             String typeFqcn = "R.layout"; //$NON-NLS-1$
    526             if (pkg != null) {
    527                 typeFqcn = pkg + '.' + typeFqcn;
    528             }
    529 
    530             IType type = javaProject.findType(typeFqcn);
    531             if (type != null) {
    532                 IField field = type.getField(layoutName);
    533                 if (field.exists()) {
    534                     SearchPattern pattern = SearchPattern.createPattern(field, REFERENCES);
    535                     try {
    536                         search(requestor, javaProject, pattern);
    537                     } catch (OperationCanceledException canceled) {
    538                         // pass
    539                     }
    540                 }
    541             }
    542         } catch (CoreException e) {
    543             AdtPlugin.log(e, null);
    544         }
    545 
    546         return activities;
    547     }
    548 
    549     /**
    550      * Returns all activities found in the given project (including those in libraries,
    551      * except for android.jar itself)
    552      *
    553      * @param project the project
    554      * @return a list of activity classes as fully qualified class names
    555      */
    556     @SuppressWarnings("restriction") // BinaryType
    557     @NonNull
    558     public static List<String> getProjectActivities(IProject project) {
    559         final List<String> activities = new ArrayList<String>();
    560         try {
    561             final IJavaProject javaProject = BaseProjectHelper.getJavaProject(project);
    562             if (javaProject != null) {
    563                 IType[] activityTypes = new IType[0];
    564                 IType activityType = javaProject.findType(CLASS_ACTIVITY);
    565                 if (activityType != null) {
    566                     ITypeHierarchy hierarchy =
    567                         activityType.newTypeHierarchy(javaProject, new NullProgressMonitor());
    568                     activityTypes = hierarchy.getAllSubtypes(activityType);
    569                     for (IType type : activityTypes) {
    570                         if (type instanceof BinaryType && (type.getClassFile() == null
    571                                     || type.getClassFile().getResource() == null)) {
    572                             continue;
    573                         }
    574                         activities.add(type.getFullyQualifiedName());
    575                     }
    576                 }
    577             }
    578         } catch (CoreException e) {
    579             AdtPlugin.log(e, null);
    580         }
    581 
    582         return activities;
    583     }
    584 
    585 
    586     /**
    587      * Returns the activity associated with the given layout file.
    588      * <p>
    589      * This is an alternative to {@link #guessActivity(IFile, String)}. Whereas
    590      * guessActivity simply looks for references to "R.layout.foo", this method searches
    591      * for all usages of Activity#setContentView(int), and for each match it looks up the
    592      * corresponding call text (such as "setContentView(R.layout.foo)"). From this it uses
    593      * a regexp to pull out "foo" from this, and stores the association that layout "foo"
    594      * is associated with the activity class that contained the setContentView call.
    595      * <p>
    596      * This has two potential advantages:
    597      * <ol>
    598      * <li>It can be faster. We do the reference search -once-, and we've built a map of
    599      * all the layout-to-activity mappings which we can then immediately look up other
    600      * layouts for, which is particularly useful at startup when we have to compute the
    601      * layout activity associations to populate the theme choosers.
    602      * <li>It can be more accurate. Just because an activity references an "R.layout.foo"
    603      * field doesn't mean it's setting it as a content view.
    604      * </ol>
    605      * However, this second advantage is also its chief problem. There are some common
    606      * code constructs which means that the associated layout is not explicitly referenced
    607      * in a direct setContentView call; on a couple of sample projects I tested I found
    608      * patterns like for example "setContentView(v)" where "v" had been computed earlier.
    609      * Therefore, for now we're going to stick with the more general approach of just
    610      * looking up each field when needed. We're keeping the code around, though statically
    611      * compiled out with the "if (false)" construct below in case we revisit this.
    612      *
    613      * @param layoutFile the layout whose activity we want to look up
    614      * @return the activity name
    615      */
    616     @SuppressWarnings("all")
    617     @Nullable
    618     public String guessActivityBySetContentView(String layoutName) {
    619         if (false) {
    620             // These should be fields
    621             final Pattern LAYOUT_FIELD_PATTERN =
    622                 Pattern.compile("R\\.layout\\.([a-z0-9_]+)"); //$NON-NLS-1$
    623             Map<String, String> mUsages = null;
    624 
    625             sync();
    626             if (mUsages == null) {
    627                 final Map<String, String> usages = new HashMap<String, String>();
    628                 mUsages = usages;
    629                 SearchRequestor requestor = new SearchRequestor() {
    630                     @Override
    631                     public void acceptSearchMatch(SearchMatch match) throws CoreException {
    632                         Object element = match.getElement();
    633                         if (element instanceof IMethod) {
    634                             IMethod method = (IMethod) element;
    635                             IType declaringType = method.getDeclaringType();
    636                             String fqcn = declaringType.getFullyQualifiedName();
    637                             IDocumentProvider provider = new TextFileDocumentProvider();
    638                             IResource resource = match.getResource();
    639                             try {
    640                                 provider.connect(resource);
    641                                 IDocument document = provider.getDocument(resource);
    642                                 if (document != null) {
    643                                     String matchText = document.get(match.getOffset(),
    644                                             match.getLength());
    645                                     Matcher matcher = LAYOUT_FIELD_PATTERN.matcher(matchText);
    646                                     if (matcher.find()) {
    647                                         usages.put(matcher.group(1), fqcn);
    648                                     }
    649                                 }
    650                             } catch (Exception e) {
    651                                 AdtPlugin.log(e, "Can't find range information for %1$s",
    652                                         resource.getName());
    653                             } finally {
    654                                 provider.disconnect(resource);
    655                             }
    656                         }
    657                     }
    658                 };
    659                 try {
    660                     IJavaProject javaProject = BaseProjectHelper.getJavaProject(mProject);
    661                     if (javaProject == null) {
    662                         return null;
    663                     }
    664 
    665                     // Search for which java classes call setContentView(R.layout.layoutname);
    666                     String typeFqcn = "R.layout"; //$NON-NLS-1$
    667                     if (mPackage != null) {
    668                         typeFqcn = mPackage + '.' + typeFqcn;
    669                     }
    670 
    671                     IType activityType = javaProject.findType(CLASS_ACTIVITY);
    672                     if (activityType != null) {
    673                         IMethod method = activityType.getMethod(
    674                                 "setContentView", new String[] {"I"}); //$NON-NLS-1$ //$NON-NLS-2$
    675                         if (method.exists()) {
    676                             SearchPattern pattern = SearchPattern.createPattern(method,
    677                                     REFERENCES);
    678                             search(requestor, javaProject, pattern);
    679                         }
    680                     }
    681                 } catch (CoreException e) {
    682                     AdtPlugin.log(e, null);
    683                 }
    684             }
    685 
    686             return mUsages.get(layoutName);
    687         }
    688 
    689         return null;
    690     }
    691 
    692     /**
    693      * Performs a search using the given pattern, scope and handler. The search will abort
    694      * if it takes longer than {@link #SEARCH_TIMEOUT_MS} milliseconds.
    695      */
    696     private static void search(SearchRequestor requestor, IJavaProject javaProject,
    697             SearchPattern pattern) throws CoreException {
    698         // Find the package fragment specified in the manifest; the activities should
    699         // live there.
    700         IJavaSearchScope scope = createPackageScope(javaProject);
    701 
    702         SearchParticipant[] participants = new SearchParticipant[] {
    703             SearchEngine.getDefaultSearchParticipant()
    704         };
    705         SearchEngine engine = new SearchEngine();
    706 
    707         final long searchStart = System.currentTimeMillis();
    708         NullProgressMonitor monitor = new NullProgressMonitor() {
    709             private boolean mCancelled;
    710             @Override
    711             public void internalWorked(double work) {
    712                 long searchEnd = System.currentTimeMillis();
    713                 if (searchEnd - searchStart > SEARCH_TIMEOUT_MS) {
    714                     mCancelled = true;
    715                 }
    716             }
    717 
    718             @Override
    719             public boolean isCanceled() {
    720                 return mCancelled;
    721             }
    722         };
    723         engine.search(pattern, participants, scope, requestor, monitor);
    724     }
    725 
    726     /** Creates a package search scope for the first package root in the given java project */
    727     private static IJavaSearchScope createPackageScope(IJavaProject javaProject) {
    728         IPackageFragmentRoot packageRoot = getSourcePackageRoot(javaProject);
    729 
    730         IJavaSearchScope scope;
    731         if (packageRoot != null) {
    732             IJavaElement[] scopeElements = new IJavaElement[] { packageRoot };
    733             scope = SearchEngine.createJavaSearchScope(scopeElements);
    734         } else {
    735             scope = SearchEngine.createWorkspaceScope();
    736         }
    737         return scope;
    738     }
    739 
    740     /**
    741      * Returns the first package root for the given java project
    742      *
    743      * @param javaProject the project to search in
    744      * @return the first package root, or null
    745      */
    746     @Nullable
    747     public static IPackageFragmentRoot getSourcePackageRoot(IJavaProject javaProject) {
    748         IPackageFragmentRoot packageRoot = null;
    749         List<IPath> sources = BaseProjectHelper.getSourceClasspaths(javaProject);
    750 
    751         IWorkspace workspace = ResourcesPlugin.getWorkspace();
    752         for (IPath path : sources) {
    753             IResource firstSource = workspace.getRoot().findMember(path);
    754             if (firstSource != null) {
    755                 packageRoot = javaProject.getPackageFragmentRoot(firstSource);
    756                 if (packageRoot != null) {
    757                     break;
    758                 }
    759             }
    760         }
    761         return packageRoot;
    762     }
    763 
    764     /**
    765      * Computes the minimum SDK and target SDK versions for the project
    766      *
    767      * @param project the project to look up the versions for
    768      * @return a pair of (minimum SDK, target SDK) versions, never null
    769      */
    770     @NonNull
    771     public static Pair<Integer, Integer> computeSdkVersions(IProject project) {
    772         int mMinSdkVersion = 1;
    773         int mTargetSdkVersion = 1;
    774 
    775         IAbstractFile manifestFile = AndroidManifest.getManifest(new IFolderWrapper(project));
    776         if (manifestFile != null) {
    777             try {
    778                 Object value = AndroidManifest.getMinSdkVersion(manifestFile);
    779                 mMinSdkVersion = 1; // Default case if missing
    780                 if (value instanceof Integer) {
    781                     mMinSdkVersion = ((Integer) value).intValue();
    782                 } else if (value instanceof String) {
    783                     // handle codename, only if we can resolve it.
    784                     if (Sdk.getCurrent() != null) {
    785                         IAndroidTarget target = Sdk.getCurrent().getTargetFromHashString(
    786                                 "android-" + value); //$NON-NLS-1$
    787                         if (target != null) {
    788                             // codename future API level is current api + 1
    789                             mMinSdkVersion = target.getVersion().getApiLevel() + 1;
    790                         }
    791                     }
    792                 }
    793 
    794                 Integer i = AndroidManifest.getTargetSdkVersion(manifestFile);
    795                 if (i == null) {
    796                     mTargetSdkVersion = mMinSdkVersion;
    797                 } else {
    798                     mTargetSdkVersion = i.intValue();
    799                 }
    800             } catch (XPathExpressionException e) {
    801                 // do nothing we'll use 1 below.
    802             } catch (StreamException e) {
    803                 // do nothing we'll use 1 below.
    804             }
    805         }
    806 
    807         return Pair.of(mMinSdkVersion, mTargetSdkVersion);
    808     }
    809 }
    810