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