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