aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--common/src/com/android/io/FileWrapper.java4
-rw-r--r--common/src/com/android/io/IAbstractFile.java5
-rw-r--r--eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/configuration/ConfigurationComposite.java140
-rw-r--r--eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/CustomViewFinder.java4
-rw-r--r--eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/GraphicalEditorPart.java5
-rw-r--r--eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/ImageControl.java8
-rw-r--r--eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/IncludeFinder.java21
-rw-r--r--eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/manifest/ManifestInfo.java514
-rw-r--r--eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/xml/Hyperlinks.java4
-rw-r--r--eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/resources/ResourceHelper.java34
-rw-r--r--eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/io/IFileWrapper.java4
-rw-r--r--eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/manifest/ManifestInfoTest.java156
-rw-r--r--eclipse/plugins/com.android.ide.eclipse.tests/unittests/com/android/ide/eclipse/adt/internal/resources/ResourceHelperTest.java6
-rw-r--r--ide_common/src/com/android/ide/common/resources/ResourceResolver.java12
-rw-r--r--sdkmanager/libs/sdklib/src/com/android/sdklib/xml/AndroidManifest.java1
15 files changed, 899 insertions, 19 deletions
diff --git a/common/src/com/android/io/FileWrapper.java b/common/src/com/android/io/FileWrapper.java
index c1e8f81..2859c0d 100644
--- a/common/src/com/android/io/FileWrapper.java
+++ b/common/src/com/android/io/FileWrapper.java
@@ -137,6 +137,10 @@ public class FileWrapper extends File implements IAbstractFile {
return isFile();
}
+ public long getModificationStamp() {
+ return lastModified();
+ }
+
public IAbstractFolder getParentFolder() {
String p = this.getParent();
if (p == null) {
diff --git a/common/src/com/android/io/IAbstractFile.java b/common/src/com/android/io/IAbstractFile.java
index d8d794d..6dfc8d8 100644
--- a/common/src/com/android/io/IAbstractFile.java
+++ b/common/src/com/android/io/IAbstractFile.java
@@ -50,4 +50,9 @@ public interface IAbstractFile extends IAbstractResource {
* Returns the preferred mode to write into the file.
*/
PreferredWriteMode getPreferredWriteMode();
+
+ /**
+ * Returns the last modification timestamp
+ */
+ long getModificationStamp();
}
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/configuration/ConfigurationComposite.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/configuration/ConfigurationComposite.java
index 77d74d8..f941ceb 100644
--- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/configuration/ConfigurationComposite.java
+++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/configuration/ConfigurationComposite.java
@@ -17,6 +17,7 @@
package com.android.ide.eclipse.adt.internal.editors.layout.configuration;
import static com.android.ide.common.layout.LayoutConstants.ANDROID_NS_NAME_PREFIX;
+import static com.android.ide.common.resources.ResourceResolver.PREFIX_ANDROID_STYLE;
import com.android.ide.common.rendering.api.ResourceValue;
import com.android.ide.common.rendering.api.StyleResourceValue;
@@ -32,9 +33,12 @@ import com.android.ide.common.resources.configuration.RegionQualifier;
import com.android.ide.common.resources.configuration.ResourceQualifier;
import com.android.ide.common.resources.configuration.ScreenDimensionQualifier;
import com.android.ide.common.resources.configuration.ScreenOrientationQualifier;
+import com.android.ide.common.resources.configuration.ScreenSizeQualifier;
import com.android.ide.common.resources.configuration.VersionQualifier;
import com.android.ide.common.sdk.LoadStatus;
import com.android.ide.eclipse.adt.AdtPlugin;
+import com.android.ide.eclipse.adt.internal.editors.manifest.ManifestInfo;
+import com.android.ide.eclipse.adt.internal.resources.ResourceHelper;
import com.android.ide.eclipse.adt.internal.resources.manager.ProjectResources;
import com.android.ide.eclipse.adt.internal.resources.manager.ResourceManager;
import com.android.ide.eclipse.adt.internal.sdk.AndroidTargetData;
@@ -73,9 +77,11 @@ import org.eclipse.swt.widgets.Label;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
+import java.util.HashSet;
import java.util.List;
import java.util.Locale;
import java.util.Map;
+import java.util.Set;
import java.util.SortedSet;
/**
@@ -137,7 +143,13 @@ public class ConfigurationComposite extends Composite {
private Combo mThemeCombo;
private Combo mTargetCombo;
- private int mPlatformThemeCount = 0;
+ /**
+ * List of booleans, matching item for item the theme names in the mThemeCombo
+ * combobox, where each boolean represents whether the corresponding theme is a
+ * project theme
+ */
+ private List<Boolean> mIsProjectTheme = new ArrayList<Boolean>(40);
+
/** updates are disabled if > 0 */
private int mDisableUpdates = 0;
@@ -219,6 +231,7 @@ public class ConfigurationComposite extends Composite {
ResourceRepository getFrameworkResources(IAndroidTarget target);
Map<ResourceType, Map<String, ResourceValue>> getConfiguredProjectResources();
Map<ResourceType, Map<String, ResourceValue>> getConfiguredFrameworkResources();
+ String getIncludedWithin();
}
/**
@@ -681,8 +694,6 @@ public class ConfigurationComposite extends Composite {
loadedConfigData = mState.setData(data);
}
- // update the themes and locales.
- updateThemes();
updateLocales();
// If the current state was loaded from the persistent storage, we update the
@@ -712,6 +723,14 @@ public class ConfigurationComposite extends Composite {
}
}
+ // Update themes. This is done after updating the devices above,
+ // since we want to look at the chosen device size to decide
+ // what the default theme (for example, with Honeycomb we choose
+ // Holo as the default theme but only if the screen size is XLARGE
+ // (and of course only if the manifest does not specify another
+ // default theme).
+ updateThemes();
+
// update the string showing the config value
updateConfigDisplay(mEditedConfig);
@@ -1344,11 +1363,109 @@ public class ConfigurationComposite extends Composite {
try {
// Reset the combo
mThemeCombo.removeAll();
- mPlatformThemeCount = 0;
+ mIsProjectTheme.clear();
ArrayList<String> themes = new ArrayList<String>();
+ String includedIn = mListener != null ? mListener.getIncludedWithin() : null;
+
+ // First list any themes that are declared by the manifest
+ if (mEditedFile != null) {
+ IProject project = mEditedFile.getProject();
+ ManifestInfo manifest = ManifestInfo.get(project);
+
+ // Look up the screen size for the current configuration
+ ScreenSize screenSize = null;
+ if (mState.device != null) {
+ List<DeviceConfig> configs = mState.device.getConfigs();
+ for (DeviceConfig config : configs) {
+ ScreenSizeQualifier qualifier =
+ config.getConfig().getScreenSizeQualifier();
+ screenSize = qualifier.getValue();
+ break;
+ }
+ }
+ // Look up the default/fallback theme to use for this project (which
+ // depends on the screen size when no particular theme is specified
+ // in the manifest)
+ String defaultTheme = manifest.getDefaultTheme(screenSize);
+
+ Map<String, String> activityThemes = manifest.getActivityThemes();
+ String pkg = manifest.getPackage();
+ String preferred = null;
+ boolean isIncluded = includedIn != null;
+ if (mState.theme == null || isIncluded) {
+ String layoutName = ResourceHelper.getLayoutName(mEditedFile);
+
+ // If we are rendering a layout in included context, pick the theme
+ // from the outer layout instead
+ if (includedIn != null) {
+ layoutName = includedIn;
+ }
+
+ String activity = ManifestInfo.guessActivity(project, layoutName, pkg);
+ if (activity != null) {
+ preferred = activityThemes.get(activity);
+ }
+ if (preferred == null) {
+ preferred = defaultTheme;
+ }
+ String preferredTheme = ResourceHelper.styleToTheme(preferred);
+ if (includedIn == null) {
+ mState.theme = preferredTheme;
+ }
+ boolean isProjectTheme = !preferred.startsWith(PREFIX_ANDROID_STYLE);
+ mThemeCombo.add(preferredTheme);
+ mIsProjectTheme.add(Boolean.valueOf(isProjectTheme));
+
+ mThemeCombo.add(THEME_SEPARATOR);
+ mIsProjectTheme.add(Boolean.FALSE);
+ }
+
+ // Create a sorted list of unique themes referenced in the manifest
+ // (sort alphabetically, but place the preferred theme at the
+ // top of the list)
+ Set<String> themeSet = new HashSet<String>(activityThemes.values());
+ themeSet.add(defaultTheme);
+ List<String> themeList = new ArrayList<String>(themeSet);
+ final String first = preferred;
+ Collections.sort(themeList, new Comparator<String>() {
+ public int compare(String s1, String s2) {
+ if (s1 == first) {
+ return -1;
+ } else if (s1 == first) {
+ return 1;
+ } else {
+ return s1.compareTo(s2);
+ }
+ }
+ });
+
+ if (themeList.size() > 1 ||
+ (themeList.size() == 1 && (preferred == null ||
+ !preferred.equals(themeList.get(0))))) {
+ for (String style : themeList) {
+ String theme = ResourceHelper.styleToTheme(style);
+
+ // Initialize the chosen theme to the first item
+ // in the used theme list (that's what would be chosen
+ // anyway) such that we stop attempting to look up
+ // the associated activity (during initialization,
+ // this method can be called repeatedly.)
+ if (mState.theme == null) {
+ mState.theme = theme;
+ }
+
+ boolean isProjectTheme = !style.startsWith(PREFIX_ANDROID_STYLE);
+ mThemeCombo.add(theme);
+ mIsProjectTheme.add(Boolean.valueOf(isProjectTheme));
+ }
+ mThemeCombo.add(THEME_SEPARATOR);
+ mIsProjectTheme.add(Boolean.FALSE);
+ }
+ }
// get the themes, and languages from the Framework.
+ int platformThemeCount = 0;
if (frameworkRes != null) {
// get the configured resources for the framework
Map<ResourceType, Map<String, ResourceValue>> frameworResources =
@@ -1364,7 +1481,6 @@ public class ConfigurationComposite extends Composite {
String name = value.getName();
if (name.startsWith("Theme.") || name.equals("Theme")) {
themes.add(value.getName());
- mPlatformThemeCount++;
}
}
@@ -1373,9 +1489,10 @@ public class ConfigurationComposite extends Composite {
for (String theme : themes) {
mThemeCombo.add(theme);
+ mIsProjectTheme.add(Boolean.FALSE);
}
- mPlatformThemeCount = themes.size();
+ platformThemeCount = themes.size();
themes.clear();
}
}
@@ -1403,14 +1520,16 @@ public class ConfigurationComposite extends Composite {
}
// sort them and add them the to the combo.
- if (mPlatformThemeCount > 0 && themes.size() > 0) {
+ if (platformThemeCount > 0 && themes.size() > 0) {
mThemeCombo.add(THEME_SEPARATOR);
+ mIsProjectTheme.add(Boolean.FALSE);
}
Collections.sort(themes);
for (String theme : themes) {
mThemeCombo.add(theme);
+ mIsProjectTheme.add(Boolean.TRUE);
}
}
}
@@ -1419,7 +1538,7 @@ public class ConfigurationComposite extends Composite {
// try to reselect the previous theme.
boolean needDefaultSelection = true;
- if (mState.theme != null) {
+ if (mState.theme != null && includedIn == null) {
final int count = mThemeCombo.getItemCount();
for (int i = 0 ; i < count ; i++) {
if (mState.theme.equals(mThemeCombo.getItem(i))) {
@@ -1444,6 +1563,8 @@ public class ConfigurationComposite extends Composite {
} finally {
mDisableUpdates--;
}
+
+ assert mIsProjectTheme.size() == mThemeCombo.getItemCount();
}
// ---- getters for the config selection values ----
@@ -1573,7 +1694,7 @@ public class ConfigurationComposite extends Composite {
* @return true for project theme, false for framework theme
*/
public boolean isProjectTheme() {
- return mThemeCombo.getSelectionIndex() >= mPlatformThemeCount;
+ return mIsProjectTheme.get(mThemeCombo.getSelectionIndex()).booleanValue();
}
public IAndroidTarget getRenderingTarget() {
@@ -2323,4 +2444,3 @@ public class ConfigurationComposite extends Composite {
}
}
}
-
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/CustomViewFinder.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/CustomViewFinder.java
index 8cb0d26..02a674c 100644
--- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/CustomViewFinder.java
+++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/CustomViewFinder.java
@@ -20,6 +20,7 @@ import static com.android.sdklib.SdkConstants.CLASS_VIEWGROUP;
import static com.android.sdklib.SdkConstants.FN_FRAMEWORK_LIBRARY;
import com.android.ide.eclipse.adt.AdtPlugin;
+import com.android.ide.eclipse.adt.internal.project.BaseProjectHelper;
import com.android.ide.eclipse.adt.internal.sdk.ProjectState;
import com.android.ide.eclipse.adt.internal.sdk.Sdk;
import com.android.util.Pair;
@@ -38,7 +39,6 @@ import org.eclipse.jdt.core.IJavaProject;
import org.eclipse.jdt.core.IMember;
import org.eclipse.jdt.core.IPackageFragment;
import org.eclipse.jdt.core.IType;
-import org.eclipse.jdt.core.JavaCore;
import org.eclipse.jdt.core.JavaModelException;
import org.eclipse.jdt.core.search.IJavaSearchConstants;
import org.eclipse.jdt.core.search.IJavaSearchScope;
@@ -225,7 +225,7 @@ public class CustomViewFinder {
}
};
try {
- IJavaProject javaProject = (IJavaProject) mProject.getNature(JavaCore.NATURE_ID);
+ IJavaProject javaProject = BaseProjectHelper.getJavaProject(mProject);
if (javaProject != null) {
String className = layoutsOnly ? CLASS_VIEWGROUP : CLASS_VIEW;
IType activityType = javaProject.findType(className);
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/GraphicalEditorPart.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/GraphicalEditorPart.java
index 0a65865..4700560 100644
--- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/GraphicalEditorPart.java
+++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/GraphicalEditorPart.java
@@ -594,7 +594,6 @@ public class GraphicalEditorPart extends EditorPart
return null;
}
-
public ProjectResources getProjectResources() {
if (mEditedFile != null) {
ResourceManager manager = ResourceManager.getInstance();
@@ -732,6 +731,10 @@ public class GraphicalEditorPart extends EditorPart
getCanvasControl().setFitScale(true);
}
}
+
+ public String getIncludedWithin() {
+ return mIncludedWithin != null ? mIncludedWithin.getName() : null;
+ }
}
/**
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/ImageControl.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/ImageControl.java
index c33c4fe..7af89f8 100644
--- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/ImageControl.java
+++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/ImageControl.java
@@ -49,14 +49,16 @@ public class ImageControl extends Canvas implements MouseTrackListener {
private float mScale = 1.0f;
/**
- * Creates an ImageControl rendering the given image, which will be dispose when this
- * control is disposed
+ * Creates an ImageControl rendering the given image, which will be disposed when this
+ * control is disposed (unless the {@link #setDisposeImage} method is called to turn
+ * off auto dispose).
*
* @param parent the parent to add the image control to
* @param style the SWT style to use
* @param image the image to be rendered, which must not be null and should be unique
* for this image control since it will be disposed by this control when
- * the control is disposed
+ * the control is disposed (unless the {@link #setDisposeImage} method is
+ * called to turn off auto dispose)
*/
public ImageControl(Composite parent, int style, Image image) {
super(parent, style | SWT.NO_FOCUS | SWT.DOUBLE_BUFFERED);
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/IncludeFinder.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/IncludeFinder.java
index 86e33be..7832197 100644
--- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/IncludeFinder.java
+++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/IncludeFinder.java
@@ -186,6 +186,27 @@ public class IncludeFinder {
}
}
+ /**
+ * Returns true if the given resource is included from some other layout in the
+ * project
+ *
+ * @param included the resource to check
+ * @return true if the file is included by some other layout
+ */
+ public boolean isIncluded(IResource included) {
+ ensureInitialized();
+ String mapKey = getMapKey(included);
+ List<String> result = mIncludedBy.get(mapKey);
+ if (result == null) {
+ String name = getResourceName(included);
+ if (!name.equals(mapKey)) {
+ result = mIncludedBy.get(name);
+ }
+ }
+
+ return result != null && result.size() > 0;
+ }
+
@VisibleForTesting
/* package */ List<String> getIncludedBy(String included) {
ensureInitialized();
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/manifest/ManifestInfo.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/manifest/ManifestInfo.java
new file mode 100644
index 0000000..05c4a5e
--- /dev/null
+++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/manifest/ManifestInfo.java
@@ -0,0 +1,514 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Eclipse Public License, Version 1.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.eclipse.org/org/documents/epl-v10.php
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.ide.eclipse.adt.internal.editors.manifest;
+
+import static com.android.ide.common.resources.ResourceResolver.PREFIX_ANDROID_STYLE;
+import static com.android.sdklib.SdkConstants.NS_RESOURCES;
+import static com.android.sdklib.xml.AndroidManifest.ATTRIBUTE_MIN_SDK_VERSION;
+import static com.android.sdklib.xml.AndroidManifest.ATTRIBUTE_NAME;
+import static com.android.sdklib.xml.AndroidManifest.ATTRIBUTE_PACKAGE;
+import static com.android.sdklib.xml.AndroidManifest.ATTRIBUTE_TARGET_SDK_VERSION;
+import static com.android.sdklib.xml.AndroidManifest.ATTRIBUTE_THEME;
+import static com.android.sdklib.xml.AndroidManifest.NODE_ACTIVITY;
+import static com.android.sdklib.xml.AndroidManifest.NODE_USES_SDK;
+import static org.eclipse.jdt.core.search.IJavaSearchConstants.REFERENCES;
+
+import com.android.ide.eclipse.adt.AdtPlugin;
+import com.android.ide.eclipse.adt.internal.project.BaseProjectHelper;
+import com.android.ide.eclipse.adt.internal.sdk.Sdk;
+import com.android.ide.eclipse.adt.io.IFolderWrapper;
+import com.android.io.IAbstractFile;
+import com.android.resources.ScreenSize;
+import com.android.sdklib.IAndroidTarget;
+import com.android.sdklib.SdkConstants;
+import com.android.sdklib.xml.AndroidManifest;
+
+import org.eclipse.core.resources.IFile;
+import org.eclipse.core.resources.IProject;
+import org.eclipse.core.resources.IResource;
+import org.eclipse.core.resources.IWorkspace;
+import org.eclipse.core.resources.ResourcesPlugin;
+import org.eclipse.core.runtime.CoreException;
+import org.eclipse.core.runtime.IPath;
+import org.eclipse.core.runtime.NullProgressMonitor;
+import org.eclipse.core.runtime.QualifiedName;
+import org.eclipse.jdt.core.IField;
+import org.eclipse.jdt.core.IJavaElement;
+import org.eclipse.jdt.core.IJavaProject;
+import org.eclipse.jdt.core.IMethod;
+import org.eclipse.jdt.core.IPackageFragmentRoot;
+import org.eclipse.jdt.core.IType;
+import org.eclipse.jdt.core.search.IJavaSearchScope;
+import org.eclipse.jdt.core.search.SearchEngine;
+import org.eclipse.jdt.core.search.SearchMatch;
+import org.eclipse.jdt.core.search.SearchParticipant;
+import org.eclipse.jdt.core.search.SearchPattern;
+import org.eclipse.jdt.core.search.SearchRequestor;
+import org.eclipse.jface.text.IDocument;
+import org.eclipse.ui.editors.text.TextFileDocumentProvider;
+import org.eclipse.ui.texteditor.IDocumentProvider;
+import org.w3c.dom.Document;
+import org.w3c.dom.Element;
+import org.w3c.dom.NodeList;
+import org.xml.sax.InputSource;
+import org.xml.sax.SAXException;
+
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.concurrent.atomic.AtomicReference;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+import javax.xml.parsers.DocumentBuilder;
+import javax.xml.parsers.DocumentBuilderFactory;
+
+/**
+ * Retrieves and caches manifest information such as the themes to be used for
+ * a given activity.
+ *
+ * @see AndroidManifest
+ */
+public class ManifestInfo {
+ /**
+ * The maximum number of milliseconds to search for an activity in the codebase when
+ * attempting to associate layouts with activities in
+ * {@link #guessActivity(IFile, String)}
+ */
+ private static final int SEARCH_TIMEOUT_MS = 3000;
+
+ private final IProject mProject;
+ private String mPackage;
+ private String mDefaultTheme;
+ private String mLargeDefaultTheme;
+ private Map<String, String> mActivityThemes;
+ private IAbstractFile mManifestFile;
+ private long mLastModified;
+
+ /**
+ * Qualified name for the per-project non-persistent property storing the
+ * {@link ManifestInfo} for this project
+ */
+ final static QualifiedName MANIFEST_FINDER = new QualifiedName(AdtPlugin.PLUGIN_ID,
+ "manifest"); //$NON-NLS-1$
+
+ /**
+ * Constructs an {@link ManifestInfo} for the given project. Don't use this method;
+ * use the {@link #get} factory method instead.
+ *
+ * @param project project to create an {@link ManifestInfo} for
+ */
+ private ManifestInfo(IProject project) {
+ mProject = project;
+ }
+
+ /**
+ * Returns the {@link ManifestInfo} for the given project
+ *
+ * @param project the project the finder is associated with
+ * @return a {@ManifestInfo} for the given project, never null
+ */
+ public static ManifestInfo get(IProject project) {
+ ManifestInfo finder = null;
+ try {
+ finder = (ManifestInfo) project.getSessionProperty(MANIFEST_FINDER);
+ } catch (CoreException e) {
+ // Not a problem; we will just create a new one
+ }
+
+ if (finder == null) {
+ finder = new ManifestInfo(project);
+ try {
+ project.setSessionProperty(MANIFEST_FINDER, finder);
+ } catch (CoreException e) {
+ AdtPlugin.log(e, "Can't store ManifestInfo");
+ }
+ }
+
+ return finder;
+ }
+
+ /**
+ * Ensure that the package, theme and activity maps are initialized and up to date
+ * with respect to the manifest file
+ */
+ private void sync() {
+ if (mManifestFile == null) {
+ IFolderWrapper projectFolder = new IFolderWrapper(mProject);
+ mManifestFile = AndroidManifest.getManifest(projectFolder);
+ if (mManifestFile == null) {
+ return;
+ }
+ }
+
+ // Check to see if our data is up to date
+ long fileModified = mManifestFile.getModificationStamp();
+ if (fileModified == mLastModified) {
+ // Already have up to date data
+ return;
+ }
+ mLastModified = fileModified;
+
+ mActivityThemes = new HashMap<String, String>();
+ mDefaultTheme = PREFIX_ANDROID_STYLE + "Theme"; //$NON-NLS-1$
+ mLargeDefaultTheme = mDefaultTheme;
+
+ mPackage = ""; //$NON-NLS-1$
+ Document document = null;
+ try {
+ DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
+ InputSource is = new InputSource(mManifestFile.getContents());
+
+ factory.setNamespaceAware(true);
+ factory.setValidating(false);
+ DocumentBuilder builder = factory.newDocumentBuilder();
+ document = builder.parse(is);
+
+ Element root = document.getDocumentElement();
+ mPackage = root.getAttribute(ATTRIBUTE_PACKAGE);
+ NodeList activities = document.getElementsByTagName(NODE_ACTIVITY);
+ for (int i = 0, n = activities.getLength(); i < n; i++) {
+ Element activity = (Element) activities.item(i);
+ String theme = activity.getAttributeNS(NS_RESOURCES, ATTRIBUTE_THEME);
+ if (theme != null && theme.length() > 0) {
+ String name = activity.getAttributeNS(NS_RESOURCES, ATTRIBUTE_NAME);
+ if (name.startsWith(".") //$NON-NLS-1$
+ && mPackage != null && mPackage.length() > 0) {
+ name = mPackage + name;
+ }
+ mActivityThemes.put(name, theme);
+ }
+ }
+
+ // Look up target SDK
+ String defaultTheme = root.getAttributeNS(NS_RESOURCES, ATTRIBUTE_THEME);
+ if (defaultTheme == null || defaultTheme.length() == 0) {
+ // From manifest theme documentation:
+ // "If that attribute is also not set, the default system theme is used."
+
+ NodeList usesSdks = root.getElementsByTagName(NODE_USES_SDK);
+ if (usesSdks.getLength() > 0) {
+ Element usesSdk = (Element) usesSdks.item(0);
+ String targetSdk = null;
+ if (usesSdk.hasAttributeNS(NS_RESOURCES, ATTRIBUTE_TARGET_SDK_VERSION)) {
+ targetSdk = usesSdk.getAttributeNS(NS_RESOURCES,
+ ATTRIBUTE_TARGET_SDK_VERSION);
+ } else if (usesSdk.hasAttributeNS(NS_RESOURCES, ATTRIBUTE_MIN_SDK_VERSION)) {
+ targetSdk = usesSdk.getAttributeNS(NS_RESOURCES,
+ ATTRIBUTE_MIN_SDK_VERSION);
+ }
+ if (targetSdk != null) {
+ int apiLevel = -1;
+ try {
+ apiLevel = Integer.valueOf(targetSdk);
+ } catch (NumberFormatException e) {
+ // Handle codename
+ if (Sdk.getCurrent() != null) {
+ IAndroidTarget target = Sdk.getCurrent().getTargetFromHashString(
+ "android-" + targetSdk); //$NON-NLS-1$
+ if (target != null) {
+ // codename future API level is current api + 1
+ apiLevel = target.getVersion().getApiLevel() + 1;
+ }
+ }
+ }
+
+ if (apiLevel >= 11) {
+ mLargeDefaultTheme = PREFIX_ANDROID_STYLE + "Theme.Holo"; //$NON-NLS-1$
+ }
+ // When Holo works everywhere:
+ // if (apiLevel >= N) {
+ // mDefaultTheme = PREFIX_ANDROID_STYLE + "Theme.Holo"; //$NON-NLS-1$
+ // }
+ }
+ }
+ } else {
+ mDefaultTheme = mLargeDefaultTheme = defaultTheme;
+ }
+ } catch (SAXException e) {
+ AdtPlugin.log(e, "Malformed manifest");
+ } catch (Exception e) {
+ AdtPlugin.log(e, "Could not read Manifest data");
+ }
+ }
+
+ /**
+ * Returns the default package registered in the Android manifest
+ *
+ * @return the default package registered in the manifest
+ */
+ public String getPackage() {
+ sync();
+ return mPackage;
+ }
+
+ /**
+ * Returns a map from activity full class names to the corresponding theme style to be
+ * used
+ *
+ * @return a map from activity fqcn to theme style
+ */
+ public Map<String, String> getActivityThemes() {
+ sync();
+ return mActivityThemes;
+ }
+
+ /**
+ * Returns the default theme for this project, by looking at the manifest default
+ * theme registration, target SDK, etc.
+ *
+ * @param screenSize the screen size to obtain a default theme for, or null if unknown
+ * @return the theme to use for this project, never null
+ */
+ public String getDefaultTheme(ScreenSize screenSize) {
+ sync();
+
+ if (screenSize == ScreenSize.XLARGE) {
+ return mLargeDefaultTheme;
+ } else {
+ return mDefaultTheme;
+ }
+ }
+
+ /**
+ * Returns the activity associated with the given layout file. Makes an educated guess
+ * by peeking at the usages of the R.layout.name field corresponding to the layout and
+ * if it finds a usage.
+ *
+ * @param project the project containing the layout
+ * @param layoutName the layout whose activity we want to look up
+ * @param pkg the package containing activities
+ * @return the activity name
+ */
+ public static String guessActivity(IProject project, String layoutName, String pkg) {
+ final AtomicReference<String> activity = new AtomicReference<String>();
+ SearchRequestor requestor = new SearchRequestor() {
+ @Override
+ public void acceptSearchMatch(SearchMatch match) throws CoreException {
+ Object element = match.getElement();
+ if (element instanceof IMethod) {
+ IMethod method = (IMethod) element;
+ IType declaringType = method.getDeclaringType();
+ String fqcn = declaringType.getFullyQualifiedName();
+ if (activity.get() == null
+ || declaringType.getSuperclassName().endsWith("Activity") //$NON-NLS-1$
+ || method.getElementName().equals("onCreate")) { //$NON-NLS-1$
+ activity.set(fqcn);
+ }
+ }
+ }
+ };
+ try {
+ IJavaProject javaProject = BaseProjectHelper.getJavaProject(project);
+ if (javaProject == null) {
+ return null;
+ }
+ // TODO - look around a bit more and see if we can figure out whether the
+ // call if from within a setContentView call!
+
+ // Search for which java classes call setContentView(R.layout.layoutname);
+ String typeFqcn = "R.layout"; //$NON-NLS-1$
+ if (pkg != null) {
+ typeFqcn = pkg + '.' + typeFqcn;
+ }
+
+ IType type = javaProject.findType(typeFqcn);
+ if (type != null) {
+ IField field = type.getField(layoutName);
+ if (field.exists()) {
+ SearchPattern pattern = SearchPattern.createPattern(field, REFERENCES);
+ search(requestor, javaProject, pattern);
+ }
+ }
+ } catch (CoreException e) {
+ AdtPlugin.log(e, null);
+ }
+
+ return activity.get();
+ }
+
+ /**
+ * Returns the activity associated with the given layout file.
+ * <p>
+ * This is an alternative to {@link #guessActivity(IFile, String)}. Whereas
+ * guessActivity simply looks for references to "R.layout.foo", this method searches
+ * for all usages of Activity#setContentView(int), and for each match it looks up the
+ * corresponding call text (such as "setContentView(R.layout.foo)"). From this it uses
+ * a regexp to pull out "foo" from this, and stores the association that layout "foo"
+ * is associated with the activity class that contained the setContentView call.
+ * <p>
+ * This has two potential advantages:
+ * <ol>
+ * <li>It can be faster. We do the reference search -once-, and we've built a map of
+ * all the layout-to-activity mappings which we can then immediately look up other
+ * layouts for, which is particularly useful at startup when we have to compute the
+ * layout activity associations to populate the theme choosers.
+ * <li>It can be more accurate. Just because an activity references an "R.layout.foo"
+ * field doesn't mean it's setting it as a content view.
+ * </ol>
+ * However, this second advantage is also its chief problem. There are some common
+ * code constructs which means that the associated layout is not explicitly referenced
+ * in a direct setContentView call; on a couple of sample projects I tested I found
+ * patterns like for example "setContentView(v)" where "v" had been computed earlier.
+ * Therefore, for now we're going to stick with the more general approach of just
+ * looking up each field when needed. We're keeping the code around, though statically
+ * compiled out with the "if (false)" construct below in case we revisit this.
+ *
+ * @param layoutFile the layout whose activity we want to look up
+ * @return the activity name
+ */
+ @SuppressWarnings("all")
+ public String guessActivityBySetContentView(String layoutName) {
+ if (false) {
+ // These should be fields
+ final Pattern LAYOUT_FIELD_PATTERN =
+ Pattern.compile("R\\.layout\\.([a-z0-9_]+)"); //$NON-NLS-1$
+ Map<String, String> mUsages = null;
+
+ sync();
+ if (mUsages == null) {
+ final Map<String, String> usages = new HashMap<String, String>();
+ mUsages = usages;
+ SearchRequestor requestor = new SearchRequestor() {
+ @Override
+ public void acceptSearchMatch(SearchMatch match) throws CoreException {
+ Object element = match.getElement();
+ if (element instanceof IMethod) {
+ IMethod method = (IMethod) element;
+ IType declaringType = method.getDeclaringType();
+ String fqcn = declaringType.getFullyQualifiedName();
+ IDocumentProvider provider = new TextFileDocumentProvider();
+ IResource resource = match.getResource();
+ try {
+ provider.connect(resource);
+ IDocument document = provider.getDocument(resource);
+ if (document != null) {
+ String matchText = document.get(match.getOffset(),
+ match.getLength());
+ Matcher matcher = LAYOUT_FIELD_PATTERN.matcher(matchText);
+ if (matcher.find()) {
+ usages.put(matcher.group(1), fqcn);
+ }
+ }
+ } catch (Exception e) {
+ AdtPlugin.log(e, "Can't find range information for %1$s",
+ resource.getName());
+ } finally {
+ provider.disconnect(resource);
+ }
+ }
+ }
+ };
+ try {
+ IJavaProject javaProject = BaseProjectHelper.getJavaProject(mProject);
+ if (javaProject == null) {
+ return null;
+ }
+
+ // Search for which java classes call setContentView(R.layout.layoutname);
+ String typeFqcn = "R.layout"; //$NON-NLS-1$
+ if (mPackage != null) {
+ typeFqcn = mPackage + '.' + typeFqcn;
+ }
+
+ IType activityType = javaProject.findType(SdkConstants.CLASS_ACTIVITY);
+ if (activityType != null) {
+ IMethod method = activityType.getMethod(
+ "setContentView", new String[] {"I"}); //$NON-NLS-1$ //$NON-NLS-2$
+ if (method.exists()) {
+ SearchPattern pattern = SearchPattern.createPattern(method,
+ REFERENCES);
+ search(requestor, javaProject, pattern);
+ }
+ }
+ } catch (CoreException e) {
+ AdtPlugin.log(e, null);
+ }
+ }
+
+ return mUsages.get(layoutName);
+ }
+
+ return null;
+ }
+
+ /**
+ * Performs a search using the given pattern, scope and handler. The search will abort
+ * if it takes longer than {@link #SEARCH_TIMEOUT_MS} milliseconds.
+ */
+ private static void search(SearchRequestor requestor, IJavaProject javaProject,
+ SearchPattern pattern) throws CoreException {
+ // Find the package fragment specified in the manifest; the activities should
+ // live there.
+ IJavaSearchScope scope = createPackageScope(javaProject);
+
+ SearchParticipant[] participants = new SearchParticipant[] {
+ SearchEngine.getDefaultSearchParticipant()
+ };
+ SearchEngine engine = new SearchEngine();
+
+ final long searchStart = System.currentTimeMillis();
+ NullProgressMonitor monitor = new NullProgressMonitor() {
+ private boolean mCancelled;
+ @Override
+ public void internalWorked(double work) {
+ long searchEnd = System.currentTimeMillis();
+ if (searchEnd - searchStart > SEARCH_TIMEOUT_MS) {
+ mCancelled = true;
+ }
+ }
+
+ @Override
+ public boolean isCanceled() {
+ return mCancelled;
+ }
+ };
+ engine.search(pattern, participants, scope, requestor, monitor);
+ }
+
+ /** Creates a package search scope for the first package root in the given java project */
+ private static IJavaSearchScope createPackageScope(IJavaProject javaProject) {
+ IPackageFragmentRoot packageRoot = getSourcePackageRoot(javaProject);
+
+ IJavaSearchScope scope;
+ if (packageRoot != null) {
+ IJavaElement[] scopeElements = new IJavaElement[] { packageRoot };
+ scope = SearchEngine.createJavaSearchScope(scopeElements);
+ } else {
+ scope = SearchEngine.createWorkspaceScope();;
+ }
+ return scope;
+ }
+
+ /** Returns the first package root for the given java project */
+ private static IPackageFragmentRoot getSourcePackageRoot(IJavaProject javaProject) {
+ IPackageFragmentRoot packageRoot = null;
+ List<IPath> sources = BaseProjectHelper.getSourceClasspaths(javaProject);
+
+ IWorkspace workspace = ResourcesPlugin.getWorkspace();
+ for (IPath path : sources) {
+ IResource firstSource = workspace.getRoot().findMember(path);
+ if (firstSource != null) {
+ packageRoot = javaProject.getPackageFragmentRoot(firstSource);
+ if (packageRoot != null) {
+ break;
+ }
+ }
+ }
+ return packageRoot;
+ }
+}
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/xml/Hyperlinks.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/xml/Hyperlinks.java
index b7533be..b315cf5 100644
--- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/xml/Hyperlinks.java
+++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/xml/Hyperlinks.java
@@ -47,6 +47,7 @@ import com.android.ide.eclipse.adt.internal.editors.layout.LayoutEditor;
import com.android.ide.eclipse.adt.internal.editors.layout.gle2.GraphicalEditorPart;
import com.android.ide.eclipse.adt.internal.editors.manifest.ManifestEditor;
import com.android.ide.eclipse.adt.internal.editors.resources.descriptors.ResourcesDescriptors;
+import com.android.ide.eclipse.adt.internal.project.BaseProjectHelper;
import com.android.ide.eclipse.adt.internal.resources.ResourceHelper;
import com.android.ide.eclipse.adt.internal.resources.manager.ProjectResources;
import com.android.ide.eclipse.adt.internal.resources.manager.ResourceManager;
@@ -89,7 +90,6 @@ import org.eclipse.jdt.core.IJavaElement;
import org.eclipse.jdt.core.IJavaProject;
import org.eclipse.jdt.core.IMethod;
import org.eclipse.jdt.core.IType;
-import org.eclipse.jdt.core.JavaCore;
import org.eclipse.jdt.core.JavaModelException;
import org.eclipse.jdt.core.search.IJavaSearchConstants;
import org.eclipse.jdt.core.search.IJavaSearchScope;
@@ -617,7 +617,7 @@ public class Hyperlinks {
try {
IJavaSearchScope scope = null;
IType activityType = null;
- IJavaProject javaProject = (IJavaProject) project.getNature(JavaCore.NATURE_ID);
+ IJavaProject javaProject = BaseProjectHelper.getJavaProject(project);
if (javaProject != null) {
activityType = javaProject.findType(SdkConstants.CLASS_ACTIVITY);
if (activityType != null) {
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/resources/ResourceHelper.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/resources/ResourceHelper.java
index 428dbbf..467ae49 100644
--- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/resources/ResourceHelper.java
+++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/resources/ResourceHelper.java
@@ -17,6 +17,8 @@
package com.android.ide.eclipse.adt.internal.resources;
import static com.android.AndroidConstants.FD_RES_VALUES;
+import static com.android.ide.common.resources.ResourceResolver.PREFIX_ANDROID_STYLE;
+import static com.android.ide.common.resources.ResourceResolver.PREFIX_STYLE;
import static com.android.ide.eclipse.adt.AdtConstants.ANDROID_PKG;
import static com.android.ide.eclipse.adt.AdtConstants.EXT_XML;
import static com.android.ide.eclipse.adt.AdtConstants.WS_SEP;
@@ -409,4 +411,36 @@ public class ResourceHelper {
}
return null;
}
+
+ /**
+ * Returns the theme name to be shown for theme styles, e.g. for "@style/Theme" it
+ * returns "Theme"
+ *
+ * @param style a theme style string
+ * @return the user visible theme name
+ */
+ public static String styleToTheme(String style) {
+ if (style.startsWith(PREFIX_STYLE)) {
+ style = style.substring(PREFIX_STYLE.length());
+ } else if (style.startsWith(PREFIX_ANDROID_STYLE)) {
+ style = style.substring(PREFIX_ANDROID_STYLE.length());
+ }
+ return style;
+ }
+
+ /**
+ * Returns the layout resource name for the given layout file, e.g. for
+ * /res/layout/foo.xml returns foo.
+ *
+ * @param layoutFile the layout file whose name we want to look up
+ * @return the layout name
+ */
+ public static String getLayoutName(IFile layoutFile) {
+ String layoutName = layoutFile.getName();
+ int dotIndex = layoutName.indexOf('.');
+ if (dotIndex != -1) {
+ layoutName = layoutName.substring(0, dotIndex);
+ }
+ return layoutName;
+ }
}
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/io/IFileWrapper.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/io/IFileWrapper.java
index a7c895b..b4e7a3f 100644
--- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/io/IFileWrapper.java
+++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/io/IFileWrapper.java
@@ -97,6 +97,10 @@ public class IFileWrapper implements IAbstractFile {
return mFile;
}
+ public long getModificationStamp() {
+ return mFile.getModificationStamp();
+ }
+
@Override
public boolean equals(Object obj) {
if (obj instanceof IFileWrapper) {
diff --git a/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/manifest/ManifestInfoTest.java b/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/manifest/ManifestInfoTest.java
new file mode 100644
index 0000000..590c611
--- /dev/null
+++ b/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/manifest/ManifestInfoTest.java
@@ -0,0 +1,156 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Eclipse Public License, Version 1.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.eclipse.org/org/documents/epl-v10.php
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.ide.eclipse.adt.internal.editors.manifest;
+
+import static com.android.resources.ScreenSize.LARGE;
+import static com.android.resources.ScreenSize.NORMAL;
+import static com.android.resources.ScreenSize.XLARGE;
+
+import com.android.ide.eclipse.adt.internal.editors.layout.refactoring.AdtProjectTest;
+import com.android.ide.eclipse.adt.internal.resources.ResourceHelper;
+
+import org.eclipse.core.resources.IFile;
+import org.eclipse.core.runtime.NullProgressMonitor;
+
+import java.io.ByteArrayInputStream;
+import java.io.InputStream;
+import java.util.Map;
+
+public class ManifestInfoTest extends AdtProjectTest {
+ @Override
+ protected boolean testCaseNeedsUniqueProject() {
+ return true;
+ }
+
+ public void testGetActivityThemes1() throws Exception {
+ ManifestInfo info = getManifestInfo(
+ "<manifest xmlns:android='http://schemas.android.com/apk/res/android'\n" +
+ " package='com.android.unittest'>\n" +
+ " <uses-sdk android:minSdkVersion='3' android:targetSdkVersion='4'/>\n" +
+ "</manifest>\n");
+ Map<String, String> map = info.getActivityThemes();
+ assertEquals(map.toString(), 0, map.size());
+ assertEquals("com.android.unittest", info.getPackage());
+ assertEquals("Theme", ResourceHelper.styleToTheme(info.getDefaultTheme(NORMAL)));
+ assertEquals("@android:style/Theme", info.getDefaultTheme(null));
+ assertEquals("Theme", ResourceHelper.styleToTheme(info.getDefaultTheme(XLARGE)));
+ }
+
+ public void testGetActivityThemes2() throws Exception {
+ ManifestInfo info = getManifestInfo(
+ "<manifest xmlns:android='http://schemas.android.com/apk/res/android'\n" +
+ " package='com.android.unittest'>\n" +
+ " <uses-sdk android:minSdkVersion='3' android:targetSdkVersion='11'/>\n" +
+ "</manifest>\n");
+ Map<String, String> map = info.getActivityThemes();
+ assertEquals(map.toString(), 0, map.size());
+ assertEquals("com.android.unittest", info.getPackage());
+ assertEquals("Theme.Holo", ResourceHelper.styleToTheme(info.getDefaultTheme(XLARGE)));
+ assertEquals("Theme", ResourceHelper.styleToTheme(info.getDefaultTheme(LARGE)));
+ }
+
+ public void testGetActivityThemes3() throws Exception {
+ ManifestInfo info = getManifestInfo(
+ "<manifest xmlns:android='http://schemas.android.com/apk/res/android'\n" +
+ " package='com.android.unittest'>\n" +
+ " <uses-sdk android:minSdkVersion='11'/>\n" +
+ "</manifest>\n");
+ Map<String, String> map = info.getActivityThemes();
+ assertEquals(map.toString(), 0, map.size());
+ assertEquals("com.android.unittest", info.getPackage());
+ assertEquals("Theme.Holo", ResourceHelper.styleToTheme(info.getDefaultTheme(XLARGE)));
+ assertEquals("Theme", ResourceHelper.styleToTheme(info.getDefaultTheme(NORMAL)));
+ }
+
+ public void testGetActivityThemes4() throws Exception {
+ ManifestInfo info = getManifestInfo(
+ "<manifest xmlns:android='http://schemas.android.com/apk/res/android'\n" +
+ " package='com.android.unittest'>\n" +
+ " <application\n" +
+ " android:label='@string/app_name'\n" +
+ " android:name='.app.TestApp' android:icon='@drawable/app_icon'>\n" +
+ "\n" +
+ " <activity\n" +
+ " android:name='.prefs.PrefsActivity'\n" +
+ " android:label='@string/prefs_title' />\n" +
+ "\n" +
+ " <activity\n" +
+ " android:name='.app.IntroActivity'\n" +
+ " android:label='@string/intro_title'\n" +
+ " android:theme='@android:style/Theme.Dialog' />\n" +
+ " </application>\n" +
+ " <uses-sdk android:minSdkVersion='3' android:targetSdkVersion='4'/>\n" +
+ "</manifest>\n" +
+ ""
+ );
+ assertEquals("com.android.unittest", info.getPackage());
+ assertEquals("Theme", ResourceHelper.styleToTheme(info.getDefaultTheme(XLARGE)));
+
+ Map<String, String> map = info.getActivityThemes();
+ assertEquals(map.toString(), 1, map.size());
+ assertNull(map.get("com.android.unittest.prefs.PrefsActivity"));
+ assertEquals("@android:style/Theme.Dialog",
+ map.get("com.android.unittest.app.IntroActivity"));
+ }
+
+ public void testGetActivityThemes5() throws Exception {
+ ManifestInfo info = getManifestInfo(
+ "<manifest xmlns:android='http://schemas.android.com/apk/res/android'\n" +
+ " package='com.android.unittest'" +
+ " android:theme='@style/NoBackground'>\n" +
+ " <application\n" +
+ " android:label='@string/app_name'\n" +
+ " android:name='.app.TestApp' android:icon='@drawable/app_icon'>\n" +
+ "\n" +
+ " <activity\n" +
+ " android:name='.prefs.PrefsActivity'\n" +
+ " android:label='@string/prefs_title' />\n" +
+ "\n" +
+ " <activity\n" +
+ " android:name='.app.IntroActivity'\n" +
+ " android:label='@string/intro_title'\n" +
+ " android:theme='@android:style/Theme.Dialog' />\n" +
+ " </application>\n" +
+ " <uses-sdk android:minSdkVersion='3' android:targetSdkVersion='4'/>\n" +
+ "</manifest>\n" +
+ ""
+ );
+
+ assertEquals("@style/NoBackground", info.getDefaultTheme(XLARGE));
+ assertEquals("@style/NoBackground", info.getDefaultTheme(NORMAL));
+ assertEquals("NoBackground", ResourceHelper.styleToTheme(info.getDefaultTheme(NORMAL)));
+
+ Map<String, String> map = info.getActivityThemes();
+ assertEquals(map.toString(), 1, map.size());
+ assertNull(map.get("com.android.unittest.prefs.PrefsActivity"));
+ assertEquals("@android:style/Theme.Dialog",
+ map.get("com.android.unittest.app.IntroActivity"));
+
+ }
+
+ private ManifestInfo getManifestInfo(String manifestContents) throws Exception {
+ InputStream bstream = new ByteArrayInputStream(
+ manifestContents.getBytes("UTF-8")); //$NON-NLS-1$
+
+ IFile file = getProject().getFile("AndroidManifest.xml");
+ if (file.exists()) {
+ file.setContents(bstream, IFile.FORCE, new NullProgressMonitor());
+ } else {
+ file.create(bstream, false /* force */, new NullProgressMonitor());
+ }
+ return ManifestInfo.get(getProject());
+ }
+}
diff --git a/eclipse/plugins/com.android.ide.eclipse.tests/unittests/com/android/ide/eclipse/adt/internal/resources/ResourceHelperTest.java b/eclipse/plugins/com.android.ide.eclipse.tests/unittests/com/android/ide/eclipse/adt/internal/resources/ResourceHelperTest.java
index 1a69847..a653ae2 100644
--- a/eclipse/plugins/com.android.ide.eclipse.tests/unittests/com/android/ide/eclipse/adt/internal/resources/ResourceHelperTest.java
+++ b/eclipse/plugins/com.android.ide.eclipse.tests/unittests/com/android/ide/eclipse/adt/internal/resources/ResourceHelperTest.java
@@ -170,4 +170,10 @@ public class ResourceHelperTest extends TestCase {
assertFalse(ResourceHelper.canCreateResource("@android:dimen/foo"));
assertFalse(ResourceHelper.canCreateResource("@android:color/foo"));
}
+
+ public void testStyleToTheme() throws Exception {
+ assertEquals("Foo", ResourceHelper.styleToTheme("Foo"));
+ assertEquals("Theme", ResourceHelper.styleToTheme("@android:style/Theme"));
+ assertEquals("LocalTheme", ResourceHelper.styleToTheme("@style/LocalTheme"));
+ }
}
diff --git a/ide_common/src/com/android/ide/common/resources/ResourceResolver.java b/ide_common/src/com/android/ide/common/resources/ResourceResolver.java
index 216d694..89a4cba 100644
--- a/ide_common/src/com/android/ide/common/resources/ResourceResolver.java
+++ b/ide_common/src/com/android/ide/common/resources/ResourceResolver.java
@@ -28,13 +28,23 @@ import java.util.Map;
public class ResourceResolver extends RenderResources {
+ /** The constant {@code style/} */
public final static String REFERENCE_STYLE = ResourceType.STYLE.getName() + "/";
+ /** The constant {@code @android:} */
public final static String PREFIX_ANDROID_RESOURCE_REF = "@android:";
+ /** The constant {@code @} */
public final static String PREFIX_RESOURCE_REF = "@";
+ /** The constant {@code ?android:} */
public final static String PREFIX_ANDROID_THEME_REF = "?android:";
+ /** The constant {@code ?} */
public final static String PREFIX_THEME_REF = "?";
+ /** The constant {@code android:} */
public final static String PREFIX_ANDROID = "android:";
-
+ /** The constant {@code @style/} */
+ public static final String PREFIX_STYLE = PREFIX_RESOURCE_REF + REFERENCE_STYLE;
+ /** The constant {@code @android:style/} */
+ public static final String PREFIX_ANDROID_STYLE = PREFIX_ANDROID_RESOURCE_REF
+ + REFERENCE_STYLE;
private final Map<ResourceType, Map<String, ResourceValue>> mProjectResources;
private final Map<ResourceType, Map<String, ResourceValue>> mFrameworkResources;
diff --git a/sdkmanager/libs/sdklib/src/com/android/sdklib/xml/AndroidManifest.java b/sdkmanager/libs/sdklib/src/com/android/sdklib/xml/AndroidManifest.java
index f06b773..26fcd7b 100644
--- a/sdkmanager/libs/sdklib/src/com/android/sdklib/xml/AndroidManifest.java
+++ b/sdkmanager/libs/sdklib/src/com/android/sdklib/xml/AndroidManifest.java
@@ -76,6 +76,7 @@ public final class AndroidManifest {
public final static String ATTRIBUTE_REQ_HARDKEYBOARD = "reqHardKeyboard";
public final static String ATTRIBUTE_REQ_KEYBOARDTYPE = "reqKeyboardType";
public final static String ATTRIBUTE_REQ_TOUCHSCREEN = "reqTouchScreen";
+ public static final String ATTRIBUTE_THEME = "theme";
/**
* Returns an {@link IAbstractFile} object representing the manifest for the given project.