• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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      */
ManifestInfo(IProject project)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      */
get(IProject project)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      */
sync()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      */
getPackage()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      */
getActivityThemes()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      */
getDefaultTheme(IAndroidTarget renderingTarget, ScreenSize screenSize)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      */
getApplicationIcon()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      */
getApplicationLabel()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      */
getPackageFragment()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      */
guessActivity(IProject project, String layoutName, String pkg)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")
guessActivityBySetContentView(String layoutName)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      */
search(SearchRequestor requestor, IJavaProject javaProject, SearchPattern pattern)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 */
createPackageScope(IJavaProject javaProject)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 */
getSourcePackageRoot(IJavaProject javaProject)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      */
computeSdkVersions(IProject project)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