• 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.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      */
ManifestInfo(IProject project)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      */
clear()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
get(IProject project)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      */
sync()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 
getApiVersion(Element usesSdk, String attribute, int defaultApiLevel)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
getPackage()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
getActivityThemes()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
getManifestTheme()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
getDefaultTheme(IAndroidTarget renderingTarget, ScreenSize screenSize)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
getApplicationIcon()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
getApplicationLabel()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      */
getTargetSdkVersion()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      */
getMinSdkVersion()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
getMinSdkName()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
getMinSdkCodeName()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
getPackageFragment()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
guessActivity(IProject project, String layoutName, String pkg)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
guessActivities(IProject project, String layoutName, String pkg)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
getProjectActivities(IProject project)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
guessActivityBySetContentView(String layoutName)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      */
search(SearchRequestor requestor, IJavaProject javaProject, SearchPattern pattern)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 */
createPackageScope(IJavaProject javaProject)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
getSourcePackageRoot(IJavaProject javaProject)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
computeSdkVersions(IProject project)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