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.ide.common.resources.ResourceResolver.PREFIX_ANDROID_STYLE;
     20 import static com.android.sdklib.SdkConstants.NS_RESOURCES;
     21 import static com.android.sdklib.xml.AndroidManifest.ATTRIBUTE_ICON;
     22 import static com.android.sdklib.xml.AndroidManifest.ATTRIBUTE_LABEL;
     23 import static com.android.sdklib.xml.AndroidManifest.ATTRIBUTE_MIN_SDK_VERSION;
     24 import static com.android.sdklib.xml.AndroidManifest.ATTRIBUTE_NAME;
     25 import static com.android.sdklib.xml.AndroidManifest.ATTRIBUTE_PACKAGE;
     26 import static com.android.sdklib.xml.AndroidManifest.ATTRIBUTE_TARGET_SDK_VERSION;
     27 import static com.android.sdklib.xml.AndroidManifest.ATTRIBUTE_THEME;
     28 import static com.android.sdklib.xml.AndroidManifest.NODE_ACTIVITY;
     29 import static com.android.sdklib.xml.AndroidManifest.NODE_USES_SDK;
     30 import static org.eclipse.jdt.core.search.IJavaSearchConstants.REFERENCES;
     31 
     32 import com.android.ide.eclipse.adt.AdtPlugin;
     33 import com.android.ide.eclipse.adt.internal.project.BaseProjectHelper;
     34 import com.android.ide.eclipse.adt.internal.sdk.Sdk;
     35 import com.android.ide.eclipse.adt.io.IFolderWrapper;
     36 import com.android.io.IAbstractFile;
     37 import com.android.io.StreamException;
     38 import com.android.resources.ScreenSize;
     39 import com.android.sdklib.IAndroidTarget;
     40 import com.android.sdklib.SdkConstants;
     41 import com.android.sdklib.xml.AndroidManifest;
     42 import com.android.util.Pair;
     43 
     44 import org.eclipse.core.resources.IFile;
     45 import org.eclipse.core.resources.IProject;
     46 import org.eclipse.core.resources.IResource;
     47 import org.eclipse.core.resources.IWorkspace;
     48 import org.eclipse.core.resources.ResourcesPlugin;
     49 import org.eclipse.core.runtime.CoreException;
     50 import org.eclipse.core.runtime.IPath;
     51 import org.eclipse.core.runtime.NullProgressMonitor;
     52 import org.eclipse.core.runtime.QualifiedName;
     53 import org.eclipse.jdt.core.IField;
     54 import org.eclipse.jdt.core.IJavaElement;
     55 import org.eclipse.jdt.core.IJavaProject;
     56 import org.eclipse.jdt.core.IMethod;
     57 import org.eclipse.jdt.core.IPackageFragment;
     58 import org.eclipse.jdt.core.IPackageFragmentRoot;
     59 import org.eclipse.jdt.core.IType;
     60 import org.eclipse.jdt.core.search.IJavaSearchScope;
     61 import org.eclipse.jdt.core.search.SearchEngine;
     62 import org.eclipse.jdt.core.search.SearchMatch;
     63 import org.eclipse.jdt.core.search.SearchParticipant;
     64 import org.eclipse.jdt.core.search.SearchPattern;
     65 import org.eclipse.jdt.core.search.SearchRequestor;
     66 import org.eclipse.jface.text.IDocument;
     67 import org.eclipse.ui.editors.text.TextFileDocumentProvider;
     68 import org.eclipse.ui.texteditor.IDocumentProvider;
     69 import org.w3c.dom.Document;
     70 import org.w3c.dom.Element;
     71 import org.w3c.dom.NodeList;
     72 import org.xml.sax.InputSource;
     73 import org.xml.sax.SAXException;
     74 
     75 import java.util.HashMap;
     76 import java.util.List;
     77 import java.util.Map;
     78 import java.util.concurrent.atomic.AtomicReference;
     79 import java.util.regex.Matcher;
     80 import java.util.regex.Pattern;
     81 
     82 import javax.xml.parsers.DocumentBuilder;
     83 import javax.xml.parsers.DocumentBuilderFactory;
     84 import javax.xml.xpath.XPathExpressionException;
     85 
     86 /**
     87  * Retrieves and caches manifest information such as the themes to be used for
     88  * a given activity.
     89  *
     90  * @see AndroidManifest
     91  */
     92 public class ManifestInfo {
     93     /**
     94      * The maximum number of milliseconds to search for an activity in the codebase when
     95      * attempting to associate layouts with activities in
     96      * {@link #guessActivity(IFile, String)}
     97      */
     98     private static final int SEARCH_TIMEOUT_MS = 3000;
     99 
    100     private final IProject mProject;
    101     private String mPackage;
    102     private String mManifestTheme;
    103     private Map<String, String> mActivityThemes;
    104     private IAbstractFile mManifestFile;
    105     private long mLastModified;
    106     private int mTargetSdk;
    107     private String mApplicationIcon;
    108     private String mApplicationLabel;
    109 
    110     /**
    111      * Qualified name for the per-project non-persistent property storing the
    112      * {@link ManifestInfo} for this project
    113      */
    114     final static QualifiedName MANIFEST_FINDER = new QualifiedName(AdtPlugin.PLUGIN_ID,
    115             "manifest"); //$NON-NLS-1$
    116 
    117     /**
    118      * Constructs an {@link ManifestInfo} for the given project. Don't use this method;
    119      * use the {@link #get} factory method instead.
    120      *
    121      * @param project project to create an {@link ManifestInfo} for
    122      */
    123     private ManifestInfo(IProject project) {
    124         mProject = project;
    125     }
    126 
    127     /**
    128      * Returns the {@link ManifestInfo} for the given project
    129      *
    130      * @param project the project the finder is associated with
    131      * @return a {@ManifestInfo} for the given project, never null
    132      */
    133     public static ManifestInfo get(IProject project) {
    134         ManifestInfo finder = null;
    135         try {
    136             finder = (ManifestInfo) project.getSessionProperty(MANIFEST_FINDER);
    137         } catch (CoreException e) {
    138             // Not a problem; we will just create a new one
    139         }
    140 
    141         if (finder == null) {
    142             finder = new ManifestInfo(project);
    143             try {
    144                 project.setSessionProperty(MANIFEST_FINDER, finder);
    145             } catch (CoreException e) {
    146                 AdtPlugin.log(e, "Can't store ManifestInfo");
    147             }
    148         }
    149 
    150         return finder;
    151     }
    152 
    153     /**
    154      * Ensure that the package, theme and activity maps are initialized and up to date
    155      * with respect to the manifest file
    156      */
    157     private void sync() {
    158         if (mManifestFile == null) {
    159             IFolderWrapper projectFolder = new IFolderWrapper(mProject);
    160             mManifestFile = AndroidManifest.getManifest(projectFolder);
    161             if (mManifestFile == null) {
    162                 return;
    163             }
    164         }
    165 
    166         // Check to see if our data is up to date
    167         long fileModified = mManifestFile.getModificationStamp();
    168         if (fileModified == mLastModified) {
    169             // Already have up to date data
    170             return;
    171         }
    172         mLastModified = fileModified;
    173 
    174         mActivityThemes = new HashMap<String, String>();
    175         mManifestTheme = null;
    176         mTargetSdk = 1; // Default when not specified
    177         mPackage = ""; //$NON-NLS-1$
    178         mApplicationIcon = null;
    179         mApplicationLabel = null;
    180 
    181         Document document = null;
    182         try {
    183             DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
    184             InputSource is = new InputSource(mManifestFile.getContents());
    185 
    186             factory.setNamespaceAware(true);
    187             factory.setValidating(false);
    188             DocumentBuilder builder = factory.newDocumentBuilder();
    189             document = builder.parse(is);
    190 
    191             Element root = document.getDocumentElement();
    192             mPackage = root.getAttribute(ATTRIBUTE_PACKAGE);
    193             NodeList activities = document.getElementsByTagName(NODE_ACTIVITY);
    194             for (int i = 0, n = activities.getLength(); i < n; i++) {
    195                 Element activity = (Element) activities.item(i);
    196                 String theme = activity.getAttributeNS(NS_RESOURCES, ATTRIBUTE_THEME);
    197                 if (theme != null && theme.length() > 0) {
    198                     String name = activity.getAttributeNS(NS_RESOURCES, ATTRIBUTE_NAME);
    199                     if (name.startsWith(".")  //$NON-NLS-1$
    200                             && mPackage != null && mPackage.length() > 0) {
    201                         name = mPackage + name;
    202                     }
    203                     mActivityThemes.put(name, theme);
    204                 }
    205             }
    206 
    207             NodeList applications = root.getElementsByTagName(AndroidManifest.NODE_APPLICATION);
    208             if (applications.getLength() > 0) {
    209                 assert applications.getLength() == 1;
    210                 Element application = (Element) applications.item(0);
    211                 if (application.hasAttributeNS(NS_RESOURCES, ATTRIBUTE_ICON)) {
    212                     mApplicationIcon = application.getAttributeNS(NS_RESOURCES, ATTRIBUTE_ICON);
    213                 }
    214                 if (application.hasAttributeNS(NS_RESOURCES, ATTRIBUTE_LABEL)) {
    215                     mApplicationLabel = application.getAttributeNS(NS_RESOURCES, ATTRIBUTE_LABEL);
    216                 }
    217             }
    218 
    219             // Look up target SDK
    220             String defaultTheme = root.getAttributeNS(NS_RESOURCES, ATTRIBUTE_THEME);
    221             if (defaultTheme == null || defaultTheme.length() == 0) {
    222                 // From manifest theme documentation:
    223                 // "If that attribute is also not set, the default system theme is used."
    224 
    225                 NodeList usesSdks = root.getElementsByTagName(NODE_USES_SDK);
    226                 if (usesSdks.getLength() > 0) {
    227                     Element usesSdk = (Element) usesSdks.item(0);
    228                     String targetSdk = null;
    229                     if (usesSdk.hasAttributeNS(NS_RESOURCES, ATTRIBUTE_TARGET_SDK_VERSION)) {
    230                         targetSdk = usesSdk.getAttributeNS(NS_RESOURCES,
    231                                 ATTRIBUTE_TARGET_SDK_VERSION);
    232                     } else if (usesSdk.hasAttributeNS(NS_RESOURCES, ATTRIBUTE_MIN_SDK_VERSION)) {
    233                         targetSdk = usesSdk.getAttributeNS(NS_RESOURCES,
    234                                 ATTRIBUTE_MIN_SDK_VERSION);
    235                     }
    236                     if (targetSdk != null) {
    237                         int apiLevel = -1;
    238                         try {
    239                             apiLevel = Integer.valueOf(targetSdk);
    240                         } catch (NumberFormatException e) {
    241                             // Handle codename
    242                             if (Sdk.getCurrent() != null) {
    243                                 IAndroidTarget target = Sdk.getCurrent().getTargetFromHashString(
    244                                         "android-" + targetSdk); //$NON-NLS-1$
    245                                 if (target != null) {
    246                                     // codename future API level is current api + 1
    247                                     apiLevel = target.getVersion().getApiLevel() + 1;
    248                                 }
    249                             }
    250                         }
    251 
    252                         mTargetSdk = apiLevel;
    253                     }
    254                 }
    255             } else {
    256                 mManifestTheme = defaultTheme;
    257             }
    258         } catch (SAXException e) {
    259             AdtPlugin.log(e, "Malformed manifest");
    260         } catch (Exception e) {
    261             AdtPlugin.log(e, "Could not read Manifest data");
    262         }
    263     }
    264 
    265     /**
    266      * Returns the default package registered in the Android manifest
    267      *
    268      * @return the default package registered in the manifest
    269      */
    270     public String getPackage() {
    271         sync();
    272         return mPackage;
    273     }
    274 
    275     /**
    276      * Returns a map from activity full class names to the corresponding theme style to be
    277      * used
    278      *
    279      * @return a map from activity fqcn to theme style
    280      */
    281     public Map<String, String> getActivityThemes() {
    282         sync();
    283         return mActivityThemes;
    284     }
    285 
    286     /**
    287      * Returns the default theme for this project, by looking at the manifest default
    288      * theme registration, target SDK, rendering target, etc.
    289      *
    290      * @param renderingTarget the rendering target use to render the theme, or null
    291      * @param screenSize the screen size to obtain a default theme for, or null if unknown
    292      * @return the theme to use for this project, never null
    293      */
    294     public String getDefaultTheme(IAndroidTarget renderingTarget, ScreenSize screenSize) {
    295         sync();
    296 
    297         if (mManifestTheme != null) {
    298             return mManifestTheme;
    299         }
    300 
    301         int renderingTargetSdk = mTargetSdk;
    302         if (renderingTarget != null) {
    303             renderingTargetSdk = renderingTarget.getVersion().getApiLevel();
    304         }
    305 
    306         int apiLevel = Math.min(mTargetSdk, renderingTargetSdk);
    307         // For now this theme works only on XLARGE screens. When it works for all sizes,
    308         // add that new apiLevel to this check.
    309         if (apiLevel >= 11 && screenSize == ScreenSize.XLARGE) {
    310             return PREFIX_ANDROID_STYLE + "Theme.Holo"; //$NON-NLS-1$
    311         } else {
    312             return PREFIX_ANDROID_STYLE + "Theme"; //$NON-NLS-1$
    313         }
    314     }
    315 
    316     /**
    317      * Returns the application icon, or null
    318      *
    319      * @return the application icon, or null
    320      */
    321     public String getApplicationIcon() {
    322         sync();
    323         return mApplicationIcon;
    324     }
    325 
    326     /**
    327      * Returns the application label, or null
    328      *
    329      * @return the application label, or null
    330      */
    331     public String getApplicationLabel() {
    332         sync();
    333         return mApplicationLabel;
    334     }
    335 
    336     /**
    337      * Returns the {@link IPackageFragment} for the package registered in the manifest
    338      *
    339      * @return the {@link IPackageFragment} for the package registered in the manifest
    340      */
    341     public IPackageFragment getPackageFragment() {
    342         sync();
    343         try {
    344             IJavaProject javaProject = BaseProjectHelper.getJavaProject(mProject);
    345             if (javaProject != null) {
    346                 IPackageFragmentRoot root = ManifestInfo.getSourcePackageRoot(javaProject);
    347                 if (root != null) {
    348                     return root.getPackageFragment(mPackage);
    349                 }
    350             }
    351         } catch (CoreException e) {
    352             AdtPlugin.log(e, null);
    353         }
    354 
    355         return null;
    356     }
    357 
    358     /**
    359      * Returns the activity associated with the given layout file. Makes an educated guess
    360      * by peeking at the usages of the R.layout.name field corresponding to the layout and
    361      * if it finds a usage.
    362      *
    363      * @param project the project containing the layout
    364      * @param layoutName the layout whose activity we want to look up
    365      * @param pkg the package containing activities
    366      * @return the activity name
    367      */
    368     public static String guessActivity(IProject project, String layoutName, String pkg) {
    369         final AtomicReference<String> activity = new AtomicReference<String>();
    370         SearchRequestor requestor = new SearchRequestor() {
    371             @Override
    372             public void acceptSearchMatch(SearchMatch match) throws CoreException {
    373                 Object element = match.getElement();
    374                 if (element instanceof IMethod) {
    375                     IMethod method = (IMethod) element;
    376                     IType declaringType = method.getDeclaringType();
    377                     String fqcn = declaringType.getFullyQualifiedName();
    378                     if (activity.get() == null
    379                             || (declaringType.getSuperclassName() != null &&
    380                                     declaringType.getSuperclassName().endsWith("Activity")) //$NON-NLS-1$
    381                             || method.getElementName().equals("onCreate")) { //$NON-NLS-1$
    382                         activity.set(fqcn);
    383                     }
    384                 }
    385             }
    386         };
    387         try {
    388             IJavaProject javaProject = BaseProjectHelper.getJavaProject(project);
    389             if (javaProject == null) {
    390                 return null;
    391             }
    392             // TODO - look around a bit more and see if we can figure out whether the
    393             // call if from within a setContentView call!
    394 
    395             // Search for which java classes call setContentView(R.layout.layoutname);
    396             String typeFqcn = "R.layout"; //$NON-NLS-1$
    397             if (pkg != null) {
    398                 typeFqcn = pkg + '.' + typeFqcn;
    399             }
    400 
    401             IType type = javaProject.findType(typeFqcn);
    402             if (type != null) {
    403                 IField field = type.getField(layoutName);
    404                 if (field.exists()) {
    405                     SearchPattern pattern = SearchPattern.createPattern(field, REFERENCES);
    406                     search(requestor, javaProject, pattern);
    407                 }
    408             }
    409         } catch (CoreException e) {
    410             AdtPlugin.log(e, null);
    411         }
    412 
    413         return activity.get();
    414     }
    415 
    416     /**
    417      * Returns the activity associated with the given layout file.
    418      * <p>
    419      * This is an alternative to {@link #guessActivity(IFile, String)}. Whereas
    420      * guessActivity simply looks for references to "R.layout.foo", this method searches
    421      * for all usages of Activity#setContentView(int), and for each match it looks up the
    422      * corresponding call text (such as "setContentView(R.layout.foo)"). From this it uses
    423      * a regexp to pull out "foo" from this, and stores the association that layout "foo"
    424      * is associated with the activity class that contained the setContentView call.
    425      * <p>
    426      * This has two potential advantages:
    427      * <ol>
    428      * <li>It can be faster. We do the reference search -once-, and we've built a map of
    429      * all the layout-to-activity mappings which we can then immediately look up other
    430      * layouts for, which is particularly useful at startup when we have to compute the
    431      * layout activity associations to populate the theme choosers.
    432      * <li>It can be more accurate. Just because an activity references an "R.layout.foo"
    433      * field doesn't mean it's setting it as a content view.
    434      * </ol>
    435      * However, this second advantage is also its chief problem. There are some common
    436      * code constructs which means that the associated layout is not explicitly referenced
    437      * in a direct setContentView call; on a couple of sample projects I tested I found
    438      * patterns like for example "setContentView(v)" where "v" had been computed earlier.
    439      * Therefore, for now we're going to stick with the more general approach of just
    440      * looking up each field when needed. We're keeping the code around, though statically
    441      * compiled out with the "if (false)" construct below in case we revisit this.
    442      *
    443      * @param layoutFile the layout whose activity we want to look up
    444      * @return the activity name
    445      */
    446     @SuppressWarnings("all")
    447     public String guessActivityBySetContentView(String layoutName) {
    448         if (false) {
    449             // These should be fields
    450             final Pattern LAYOUT_FIELD_PATTERN =
    451                 Pattern.compile("R\\.layout\\.([a-z0-9_]+)"); //$NON-NLS-1$
    452             Map<String, String> mUsages = null;
    453 
    454             sync();
    455             if (mUsages == null) {
    456                 final Map<String, String> usages = new HashMap<String, String>();
    457                 mUsages = usages;
    458                 SearchRequestor requestor = new SearchRequestor() {
    459                     @Override
    460                     public void acceptSearchMatch(SearchMatch match) throws CoreException {
    461                         Object element = match.getElement();
    462                         if (element instanceof IMethod) {
    463                             IMethod method = (IMethod) element;
    464                             IType declaringType = method.getDeclaringType();
    465                             String fqcn = declaringType.getFullyQualifiedName();
    466                             IDocumentProvider provider = new TextFileDocumentProvider();
    467                             IResource resource = match.getResource();
    468                             try {
    469                                 provider.connect(resource);
    470                                 IDocument document = provider.getDocument(resource);
    471                                 if (document != null) {
    472                                     String matchText = document.get(match.getOffset(),
    473                                             match.getLength());
    474                                     Matcher matcher = LAYOUT_FIELD_PATTERN.matcher(matchText);
    475                                     if (matcher.find()) {
    476                                         usages.put(matcher.group(1), fqcn);
    477                                     }
    478                                 }
    479                             } catch (Exception e) {
    480                                 AdtPlugin.log(e, "Can't find range information for %1$s",
    481                                         resource.getName());
    482                             } finally {
    483                                 provider.disconnect(resource);
    484                             }
    485                         }
    486                     }
    487                 };
    488                 try {
    489                     IJavaProject javaProject = BaseProjectHelper.getJavaProject(mProject);
    490                     if (javaProject == null) {
    491                         return null;
    492                     }
    493 
    494                     // Search for which java classes call setContentView(R.layout.layoutname);
    495                     String typeFqcn = "R.layout"; //$NON-NLS-1$
    496                     if (mPackage != null) {
    497                         typeFqcn = mPackage + '.' + typeFqcn;
    498                     }
    499 
    500                     IType activityType = javaProject.findType(SdkConstants.CLASS_ACTIVITY);
    501                     if (activityType != null) {
    502                         IMethod method = activityType.getMethod(
    503                                 "setContentView", new String[] {"I"}); //$NON-NLS-1$ //$NON-NLS-2$
    504                         if (method.exists()) {
    505                             SearchPattern pattern = SearchPattern.createPattern(method,
    506                                     REFERENCES);
    507                             search(requestor, javaProject, pattern);
    508                         }
    509                     }
    510                 } catch (CoreException e) {
    511                     AdtPlugin.log(e, null);
    512                 }
    513             }
    514 
    515             return mUsages.get(layoutName);
    516         }
    517 
    518         return null;
    519     }
    520 
    521     /**
    522      * Performs a search using the given pattern, scope and handler. The search will abort
    523      * if it takes longer than {@link #SEARCH_TIMEOUT_MS} milliseconds.
    524      */
    525     private static void search(SearchRequestor requestor, IJavaProject javaProject,
    526             SearchPattern pattern) throws CoreException {
    527         // Find the package fragment specified in the manifest; the activities should
    528         // live there.
    529         IJavaSearchScope scope = createPackageScope(javaProject);
    530 
    531         SearchParticipant[] participants = new SearchParticipant[] {
    532             SearchEngine.getDefaultSearchParticipant()
    533         };
    534         SearchEngine engine = new SearchEngine();
    535 
    536         final long searchStart = System.currentTimeMillis();
    537         NullProgressMonitor monitor = new NullProgressMonitor() {
    538             private boolean mCancelled;
    539             @Override
    540             public void internalWorked(double work) {
    541                 long searchEnd = System.currentTimeMillis();
    542                 if (searchEnd - searchStart > SEARCH_TIMEOUT_MS) {
    543                     mCancelled = true;
    544                 }
    545             }
    546 
    547             @Override
    548             public boolean isCanceled() {
    549                 return mCancelled;
    550             }
    551         };
    552         engine.search(pattern, participants, scope, requestor, monitor);
    553     }
    554 
    555     /** Creates a package search scope for the first package root in the given java project */
    556     private static IJavaSearchScope createPackageScope(IJavaProject javaProject) {
    557         IPackageFragmentRoot packageRoot = getSourcePackageRoot(javaProject);
    558 
    559         IJavaSearchScope scope;
    560         if (packageRoot != null) {
    561             IJavaElement[] scopeElements = new IJavaElement[] { packageRoot };
    562             scope = SearchEngine.createJavaSearchScope(scopeElements);
    563         } else {
    564             scope = SearchEngine.createWorkspaceScope();
    565         }
    566         return scope;
    567     }
    568 
    569     /** Returns the first package root for the given java project */
    570     public static IPackageFragmentRoot getSourcePackageRoot(IJavaProject javaProject) {
    571         IPackageFragmentRoot packageRoot = null;
    572         List<IPath> sources = BaseProjectHelper.getSourceClasspaths(javaProject);
    573 
    574         IWorkspace workspace = ResourcesPlugin.getWorkspace();
    575         for (IPath path : sources) {
    576             IResource firstSource = workspace.getRoot().findMember(path);
    577             if (firstSource != null) {
    578                 packageRoot = javaProject.getPackageFragmentRoot(firstSource);
    579                 if (packageRoot != null) {
    580                     break;
    581                 }
    582             }
    583         }
    584         return packageRoot;
    585     }
    586 
    587     /**
    588      * Computes the minimum SDK and target SDK versions for the project
    589      *
    590      * @return a pair of (minimum SDK, target SDK) versions, never null
    591      */
    592     public static Pair<Integer, Integer> computeSdkVersions(IProject project) {
    593         int mMinSdkVersion = 1;
    594         int mTargetSdkVersion = 1;
    595 
    596         IAbstractFile manifestFile = AndroidManifest.getManifest(new IFolderWrapper(project));
    597         if (manifestFile != null) {
    598             try {
    599                 Object value = AndroidManifest.getMinSdkVersion(manifestFile);
    600                 mMinSdkVersion = 1; // Default case if missing
    601                 if (value instanceof Integer) {
    602                     mMinSdkVersion = ((Integer) value).intValue();
    603                 } else if (value instanceof String) {
    604                     // handle codename, only if we can resolve it.
    605                     if (Sdk.getCurrent() != null) {
    606                         IAndroidTarget target = Sdk.getCurrent().getTargetFromHashString(
    607                                 "android-" + value); //$NON-NLS-1$
    608                         if (target != null) {
    609                             // codename future API level is current api + 1
    610                             mMinSdkVersion = target.getVersion().getApiLevel() + 1;
    611                         }
    612                     }
    613                 }
    614 
    615                 Integer i = AndroidManifest.getTargetSdkVersion(manifestFile);
    616                 if (i == null) {
    617                     mTargetSdkVersion = mMinSdkVersion;
    618                 } else {
    619                     mTargetSdkVersion = i.intValue();
    620                 }
    621             } catch (XPathExpressionException e) {
    622                 // do nothing we'll use 1 below.
    623             } catch (StreamException e) {
    624                 // do nothing we'll use 1 below.
    625             }
    626         }
    627 
    628         return Pair.of(mMinSdkVersion, mTargetSdkVersion);
    629     }
    630 }
    631