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