aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--anttasks/src/com/android/ant/AaptExecTask.java3
-rw-r--r--build/tools.atree1
-rw-r--r--common/src/com/android/SdkConstants.java3
-rw-r--r--common/src/com/android/utils/SdkUtils.java136
-rw-r--r--common/src/com/android/utils/XmlUtils.java21
-rw-r--r--common/tests/src/com/android/utils/SdkUtilsTest.java84
-rw-r--r--common/tests/src/com/android/utils/XmlUtilsTest.java5
-rwxr-xr-xeclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/common/resources/platform/AttributeInfo.java178
-rw-r--r--eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/common/resources/platform/AttrsXmlParser.java28
-rw-r--r--eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/AdtUtils.java116
-rwxr-xr-xeclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/actions/DexDumpAction.java4
-rw-r--r--eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/build/builders/PreCompilerBuilder.java2
-rw-r--r--eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/AndroidContentAssist.java9
-rw-r--r--eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/formatting/XmlPrettyPrinter.java8
-rw-r--r--eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/UiElementPullParser.java25
-rw-r--r--eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/descriptors/CustomViewDescriptorService.java2
-rw-r--r--eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/ImageUtils.java2
-rw-r--r--eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/properties/ResourceValueCompleter.java4
-rw-r--r--eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/properties/ValueCompleter.java8
-rw-r--r--eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/manifest/pages/ApplicationToggle.java4
-rw-r--r--eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/uimodel/UiElementNode.java4
-rw-r--r--eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/lint/ConvertToDpFix.java3
-rw-r--r--eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/lint/EclipseLintClient.java7
-rw-r--r--eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/lint/LintFixGenerator.java3
-rw-r--r--eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/lint/LintJob.java3
-rw-r--r--eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/resources/manager/IdeScanningContext.java48
-rw-r--r--eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/resources/manager/ResourceManager.java4
-rw-r--r--eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/sdk/AndroidTargetData.java25
-rw-r--r--eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/sdk/AndroidTargetParser.java8
-rw-r--r--eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/sdk/Sdk.java28
-rw-r--r--eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/ui/ResourcePreviewHelper.java2
-rw-r--r--eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/wizards/newproject/ProjectNamePage.java2
-rw-r--r--eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/wizards/newxmlfile/NewXmlFileCreationPage.java3
-rw-r--r--eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/wizards/templates/FmEscapeXmlAttributeMethod.java40
-rw-r--r--eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/wizards/templates/FmEscapeXmlStringMethod.java43
-rw-r--r--eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/wizards/templates/FmEscapeXmlTextMethod.java40
-rw-r--r--eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/wizards/templates/FmExtractLettersMethod.java45
-rw-r--r--eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/wizards/templates/TemplateHandler.java7
-rw-r--r--eclipse/plugins/com.android.ide.eclipse.tests/unittests/com/android/ide/common/resources/platform/AttributeInfoTest.java371
-rwxr-xr-xeclipse/plugins/com.android.ide.eclipse.tests/unittests/com/android/ide/common/resources/platform/AttrsXmlParserManifestTest.java4
-rw-r--r--eclipse/plugins/com.android.ide.eclipse.tests/unittests/com/android/ide/common/resources/platform/AttrsXmlParserTest.java2
-rw-r--r--eclipse/plugins/com.android.ide.eclipse.tests/unittests/com/android/ide/eclipse/adt/AdtUtilsTest.java67
-rw-r--r--eclipse/plugins/com.android.ide.eclipse.tests/unittests/com/android/ide/eclipse/adt/internal/resources/manager/IdeScanningContextTest.java22
-rw-r--r--eclipse/plugins/com.android.ide.eclipse.tests/unittests/com/android/ide/eclipse/adt/internal/sdk/LayoutParamsParserTest.java2
-rw-r--r--eclipse/plugins/com.android.ide.eclipse.tests/unittests/com/android/ide/eclipse/adt/internal/wizards/templates/FmEscapeXmlAttributeMethodTest.java50
-rw-r--r--eclipse/plugins/com.android.ide.eclipse.tests/unittests/com/android/ide/eclipse/adt/internal/wizards/templates/FmEscapeXmlStringMethodTest.java66
-rw-r--r--eclipse/plugins/com.android.ide.eclipse.tests/unittests/com/android/ide/eclipse/adt/internal/wizards/templates/FmEscapeXmlTextMethodTest.java46
-rw-r--r--eclipse/plugins/com.android.ide.eclipse.tests/unittests/com/android/ide/eclipse/adt/internal/wizards/templates/FmExtractLettersMethodTest.java46
-rw-r--r--files/ant/build.xml2
-rw-r--r--find_java/find_java_exe.cpp3
-rw-r--r--sdk_common/src/com/android/ide/common/resources/IdResourceParser.java45
-rwxr-xr-xsdk_common/src/com/android/ide/common/resources/ResourceRepository.java2
-rw-r--r--sdk_common/src/com/android/ide/common/resources/ScanningContext.java24
-rw-r--r--sdk_common/src/com/android/ide/common/resources/SingleResourceFile.java26
-rw-r--r--sdk_common/src/com/android/ide/common/resources/ValidatingResourceParser.java136
-rw-r--r--templates/activities/BlankActivity/root/res/values/strings.xml.ftl2
-rw-r--r--templates/activities/FullscreenActivity/root/res/values/strings.xml.ftl2
-rw-r--r--templates/activities/LoginActivity/root/res/values/strings.xml.ftl2
-rw-r--r--templates/activities/MasterDetailFlow/globals.xml.ftl8
-rw-r--r--templates/activities/MasterDetailFlow/recipe.xml.ftl2
-rw-r--r--templates/activities/MasterDetailFlow/root/res/values-large/refs.xml.ftl2
-rw-r--r--templates/activities/MasterDetailFlow/root/res/values-sw600dp/refs.xml.ftl2
-rw-r--r--templates/activities/MasterDetailFlow/root/res/values/strings.xml.ftl4
-rw-r--r--templates/activities/SettingsActivity/root/res/values/strings.xml.ftl2
-rw-r--r--templates/docs/index.html52
-rw-r--r--templates/projects/NewAndroidApplication/root/res/values/strings.xml.ftl2
66 files changed, 1654 insertions, 298 deletions
diff --git a/anttasks/src/com/android/ant/AaptExecTask.java b/anttasks/src/com/android/ant/AaptExecTask.java
index 5fd57b8..c82f0d6 100644
--- a/anttasks/src/com/android/ant/AaptExecTask.java
+++ b/anttasks/src/com/android/ant/AaptExecTask.java
@@ -675,8 +675,9 @@ public final class AaptExecTask extends SingleDependencyTask {
task.execute();
// now if the project has libraries, R needs to be created for each libraries
+ // but only if the project is not a library.
try {
- if (libPkgProp != null && !libPkgProp.isEmpty()) {
+ if (!mNonConstantId && libPkgProp != null && !libPkgProp.isEmpty()) {
SymbolLoader symbolValues = new SymbolLoader(new File(mBinFolder, "R.txt"));
symbolValues.load();
diff --git a/build/tools.atree b/build/tools.atree
index adb666c..78da7fc 100644
--- a/build/tools.atree
+++ b/build/tools.atree
@@ -173,7 +173,6 @@ framework/annotations.jar tools/support/annotations.jar
# systrace
external/chromium-trace/systrace.py tools/systrace/systrace.py
-external/chromium-trace/config.py tools/systrace/config.py
external/chromium-trace/script.js tools/systrace/script.js
external/chromium-trace/style.css tools/systrace/style.css
external/chromium-trace/LICENSE tools/systrace/LICENSE
diff --git a/common/src/com/android/SdkConstants.java b/common/src/com/android/SdkConstants.java
index 1aa3853..cf64967 100644
--- a/common/src/com/android/SdkConstants.java
+++ b/common/src/com/android/SdkConstants.java
@@ -816,6 +816,9 @@ public final class SdkConstants {
public static final String UNIT_DIP = "dip"; //$NON-NLS-1$
public static final String UNIT_SP = "sp"; //$NON-NLS-1$
public static final String UNIT_PX = "px"; //$NON-NLS-1$
+ public static final String UNIT_IN = "in"; //$NON-NLS-1$
+ public static final String UNIT_MM = "mm"; //$NON-NLS-1$
+ public static final String UNIT_PT = "pt"; //$NON-NLS-1$
// Filenames and folder names
public static final String ANDROID_MANIFEST_XML = "AndroidManifest.xml"; //$NON-NLS-1$
diff --git a/common/src/com/android/utils/SdkUtils.java b/common/src/com/android/utils/SdkUtils.java
new file mode 100644
index 0000000..b49c120
--- /dev/null
+++ b/common/src/com/android/utils/SdkUtils.java
@@ -0,0 +1,136 @@
+/*
+ * Copyright (C) 2012 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.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.apache.org/licenses/LICENSE-2.0
+ *
+ * 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.utils;
+
+import com.android.annotations.NonNull;
+
+public class SdkUtils {
+ /**
+ * Returns true if the given string ends with the given suffix, using a
+ * case-insensitive comparison.
+ *
+ * @param string the full string to be checked
+ * @param suffix the suffix to be checked for
+ * @return true if the string case-insensitively ends with the given suffix
+ */
+ public static boolean endsWithIgnoreCase(String string, String suffix) {
+ return string.regionMatches(true /* ignoreCase */, string.length() - suffix.length(),
+ suffix, 0, suffix.length());
+ }
+
+ /**
+ * Returns true if the given sequence ends with the given suffix (case
+ * sensitive).
+ *
+ * @param sequence the character sequence to be checked
+ * @param suffix the suffix to look for
+ * @return true if the given sequence ends with the given suffix
+ */
+ public static boolean endsWith(CharSequence sequence, CharSequence suffix) {
+ return endsWith(sequence, sequence.length(), suffix);
+ }
+
+ /**
+ * Returns true if the given sequence ends at the given offset with the given suffix (case
+ * sensitive)
+ *
+ * @param sequence the character sequence to be checked
+ * @param endOffset the offset at which the sequence is considered to end
+ * @param suffix the suffix to look for
+ * @return true if the given sequence ends with the given suffix
+ */
+ public static boolean endsWith(CharSequence sequence, int endOffset, CharSequence suffix) {
+ if (endOffset < suffix.length()) {
+ return false;
+ }
+
+ for (int i = endOffset - 1, j = suffix.length() - 1; j >= 0; i--, j--) {
+ if (sequence.charAt(i) != suffix.charAt(j)) {
+ return false;
+ }
+ }
+
+ return true;
+ }
+
+ /**
+ * Returns true if the given string starts with the given prefix, using a
+ * case-insensitive comparison.
+ *
+ * @param string the full string to be checked
+ * @param prefix the prefix to be checked for
+ * @return true if the string case-insensitively starts with the given prefix
+ */
+ public static boolean startsWithIgnoreCase(String string, String prefix) {
+ return string.regionMatches(true /* ignoreCase */, 0, prefix, 0, prefix.length());
+ }
+
+ /**
+ * Returns true if the given string starts at the given offset with the
+ * given prefix, case insensitively.
+ *
+ * @param string the full string to be checked
+ * @param offset the offset in the string to start looking
+ * @param prefix the prefix to be checked for
+ * @return true if the string case-insensitively starts at the given offset
+ * with the given prefix
+ */
+ public static boolean startsWith(String string, int offset, String prefix) {
+ return string.regionMatches(true /* ignoreCase */, offset, prefix, 0, prefix.length());
+ }
+
+ /**
+ * Strips the whitespace from the given string
+ *
+ * @param string the string to be cleaned up
+ * @return the string, without whitespace
+ */
+ public static String stripWhitespace(String string) {
+ StringBuilder sb = new StringBuilder(string.length());
+ for (int i = 0, n = string.length(); i < n; i++) {
+ char c = string.charAt(i);
+ if (!Character.isWhitespace(c)) {
+ sb.append(c);
+ }
+ }
+
+ return sb.toString();
+ }
+
+ /** For use by {@link #getLineSeparator()} */
+ private static String sLineSeparator;
+
+ /**
+ * Returns the default line separator to use.
+ * <p>
+ * NOTE: If you have an associated {@link IDocument}, it is better to call
+ * {@link TextUtilities#getDefaultLineDelimiter(IDocument)} since that will
+ * allow (for example) editing a \r\n-delimited document on a \n-delimited
+ * platform and keep a consistent usage of delimiters in the file.
+ *
+ * @return the delimiter string to use
+ */
+ @NonNull
+ public static String getLineSeparator() {
+ if (sLineSeparator == null) {
+ // This is guaranteed to exist:
+ sLineSeparator = System.getProperty("line.separator"); //$NON-NLS-1$
+ }
+
+ return sLineSeparator;
+ }
+}
diff --git a/common/src/com/android/utils/XmlUtils.java b/common/src/com/android/utils/XmlUtils.java
index 73e1d11..94b7405 100644
--- a/common/src/com/android/utils/XmlUtils.java
+++ b/common/src/com/android/utils/XmlUtils.java
@@ -182,6 +182,27 @@ public class XmlUtils {
}
/**
+ * Converts the given attribute value to an XML-text-safe value, meaning that
+ * less than and ampersand characters are escaped.
+ *
+ * @param textValue the text value to be escaped
+ * @return the escaped value
+ */
+ @NonNull
+ public static String toXmlTextValue(@NonNull String textValue) {
+ for (int i = 0, n = textValue.length(); i < n; i++) {
+ char c = textValue.charAt(i);
+ if (c == '<' || c == '&') {
+ StringBuilder sb = new StringBuilder(2 * textValue.length());
+ appendXmlTextValue(sb, textValue);
+ return sb.toString();
+ }
+ }
+
+ return textValue;
+ }
+
+ /**
* Appends text to the given {@link StringBuilder} and escapes it as required for a
* DOM attribute node.
*
diff --git a/common/tests/src/com/android/utils/SdkUtilsTest.java b/common/tests/src/com/android/utils/SdkUtilsTest.java
new file mode 100644
index 0000000..29a4d51
--- /dev/null
+++ b/common/tests/src/com/android/utils/SdkUtilsTest.java
@@ -0,0 +1,84 @@
+/*
+ * Copyright (C) 2012 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.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.apache.org/licenses/LICENSE-2.0
+ *
+ * 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.utils;
+
+import junit.framework.TestCase;
+
+public class SdkUtilsTest extends TestCase {
+ public void testEndsWithIgnoreCase() {
+ assertTrue(SdkUtils.endsWithIgnoreCase("foo", "foo"));
+ assertTrue(SdkUtils.endsWithIgnoreCase("foo", "Foo"));
+ assertTrue(SdkUtils.endsWithIgnoreCase("foo", "foo"));
+ assertTrue(SdkUtils.endsWithIgnoreCase("Barfoo", "foo"));
+ assertTrue(SdkUtils.endsWithIgnoreCase("BarFoo", "foo"));
+ assertTrue(SdkUtils.endsWithIgnoreCase("BarFoo", "foO"));
+
+ assertFalse(SdkUtils.endsWithIgnoreCase("foob", "foo"));
+ assertFalse(SdkUtils.endsWithIgnoreCase("foo", "fo"));
+ }
+
+ public void testStartsWithIgnoreCase() {
+ assertTrue(SdkUtils.startsWithIgnoreCase("foo", "foo"));
+ assertTrue(SdkUtils.startsWithIgnoreCase("foo", "Foo"));
+ assertTrue(SdkUtils.startsWithIgnoreCase("foo", "foo"));
+ assertTrue(SdkUtils.startsWithIgnoreCase("barfoo", "bar"));
+ assertTrue(SdkUtils.startsWithIgnoreCase("BarFoo", "bar"));
+ assertTrue(SdkUtils.startsWithIgnoreCase("BarFoo", "bAr"));
+
+ assertFalse(SdkUtils.startsWithIgnoreCase("bfoo", "foo"));
+ assertFalse(SdkUtils.startsWithIgnoreCase("fo", "foo"));
+ }
+
+ public void testStartsWith() {
+ assertTrue(SdkUtils.startsWith("foo", 0, "foo"));
+ assertTrue(SdkUtils.startsWith("foo", 0, "Foo"));
+ assertTrue(SdkUtils.startsWith("Foo", 0, "foo"));
+ assertTrue(SdkUtils.startsWith("aFoo", 1, "foo"));
+
+ assertFalse(SdkUtils.startsWith("aFoo", 0, "foo"));
+ assertFalse(SdkUtils.startsWith("aFoo", 2, "foo"));
+ }
+
+ public void testEndsWith() {
+ assertTrue(SdkUtils.endsWith("foo", "foo"));
+ assertTrue(SdkUtils.endsWith("foobar", "obar"));
+ assertTrue(SdkUtils.endsWith("foobar", "bar"));
+ assertTrue(SdkUtils.endsWith("foobar", "ar"));
+ assertTrue(SdkUtils.endsWith("foobar", "r"));
+ assertTrue(SdkUtils.endsWith("foobar", ""));
+
+ assertTrue(SdkUtils.endsWith(new StringBuilder("foobar"), "bar"));
+ assertTrue(SdkUtils.endsWith(new StringBuilder("foobar"), new StringBuffer("obar")));
+ assertTrue(SdkUtils.endsWith("foobar", new StringBuffer("obar")));
+
+ assertFalse(SdkUtils.endsWith("foo", "fo"));
+ assertFalse(SdkUtils.endsWith("foobar", "Bar"));
+ assertFalse(SdkUtils.endsWith("foobar", "longfoobar"));
+ }
+
+ public void testEndsWith2() {
+ assertTrue(SdkUtils.endsWith("foo", "foo".length(), "foo"));
+ assertTrue(SdkUtils.endsWith("foo", "fo".length(), "fo"));
+ assertTrue(SdkUtils.endsWith("foo", "f".length(), "f"));
+ }
+
+ public void testStripWhitespace() {
+ assertEquals("foo", SdkUtils.stripWhitespace("foo"));
+ assertEquals("foobar", SdkUtils.stripWhitespace("foo bar"));
+ assertEquals("foobar", SdkUtils.stripWhitespace(" foo bar \n\t"));
+ }
+}
diff --git a/common/tests/src/com/android/utils/XmlUtilsTest.java b/common/tests/src/com/android/utils/XmlUtilsTest.java
index 6c28451..ea33346 100644
--- a/common/tests/src/com/android/utils/XmlUtilsTest.java
+++ b/common/tests/src/com/android/utils/XmlUtilsTest.java
@@ -16,7 +16,6 @@
package com.android.utils;
import com.android.SdkConstants;
-import com.android.utils.XmlUtils;
import org.w3c.dom.Attr;
import org.w3c.dom.Document;
@@ -79,6 +78,10 @@ public class XmlUtilsTest extends TestCase {
assertEquals("&lt;&quot;&apos;>&amp;", sb.toString());
}
+ public void testToXmlTextValue() throws Exception {
+ assertEquals("&lt;\"'>&amp;", XmlUtils.toXmlTextValue("<\"'>&"));
+ }
+
public void testAppendXmlTextValue() throws Exception {
StringBuilder sb = new StringBuilder();
XmlUtils.appendXmlTextValue(sb, "<\"'>&");
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/common/resources/platform/AttributeInfo.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/common/resources/platform/AttributeInfo.java
index 05f0adf..be928b0 100755
--- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/common/resources/platform/AttributeInfo.java
+++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/common/resources/platform/AttributeInfo.java
@@ -16,10 +16,31 @@
package com.android.ide.common.resources.platform;
+import static com.android.SdkConstants.ANDROID_PREFIX;
+import static com.android.SdkConstants.ANDROID_THEME_PREFIX;
+import static com.android.SdkConstants.ID_PREFIX;
+import static com.android.SdkConstants.NEW_ID_PREFIX;
+import static com.android.SdkConstants.VALUE_FALSE;
+import static com.android.SdkConstants.VALUE_TRUE;
+import static com.android.ide.common.api.IAttributeInfo.Format.BOOLEAN;
+import static com.android.ide.common.api.IAttributeInfo.Format.COLOR;
+import static com.android.ide.common.api.IAttributeInfo.Format.DIMENSION;
+import static com.android.ide.common.api.IAttributeInfo.Format.ENUM;
+import static com.android.ide.common.api.IAttributeInfo.Format.FLAG;
+import static com.android.ide.common.api.IAttributeInfo.Format.FLOAT;
+import static com.android.ide.common.api.IAttributeInfo.Format.FRACTION;
+import static com.android.ide.common.api.IAttributeInfo.Format.INTEGER;
+import static com.android.ide.common.api.IAttributeInfo.Format.STRING;
+
import com.android.annotations.NonNull;
+import com.android.annotations.Nullable;
import com.android.ide.common.api.IAttributeInfo;
+import com.android.ide.common.resources.ResourceRepository;
+import com.android.resources.ResourceType;
+import com.google.common.base.Splitter;
import java.util.EnumSet;
+import java.util.regex.Pattern;
/**
@@ -160,4 +181,161 @@ public class AttributeInfo implements IAttributeInfo {
public @NonNull String getDefinedBy() {
return mDefinedBy;
}
+
+ private final static Pattern INTEGER_PATTERN = Pattern.compile("-?[0-9]+"); //$NON-NLS-1$
+ private final static Pattern FLOAT_PATTERN =
+ Pattern.compile("-?[0-9]?(\\.[0-9]+)?"); //$NON-NLS-1$
+ private final static Pattern DIMENSION_PATTERN =
+ Pattern.compile("-?[0-9]+(\\.[0-9]+)?(dp|dip|sp|px|pt|in|mm)"); //$NON-NLS-1$
+
+ /**
+ * Checks the given value and returns true only if it is a valid XML value
+ * for this attribute.
+ *
+ * @param value the XML value to check
+ * @param projectResources project resources to validate resource URLs with,
+ * if any
+ * @param frameworkResources framework resources to validate resource URLs
+ * with, if any
+ * @return true if the value is valid, false otherwise
+ */
+ public boolean isValid(
+ @NonNull String value,
+ @Nullable ResourceRepository projectResources,
+ @Nullable ResourceRepository frameworkResources) {
+
+ if (mFormats.contains(STRING) || mFormats.isEmpty()) {
+ // Anything is allowed
+ return true;
+ }
+
+ // All other formats require a nonempty string
+ if (value.isEmpty()) {
+ return false;
+ }
+ char first = value.charAt(0);
+
+ // There are many attributes which are incorrectly marked in the attrs.xml
+ // file, such as "duration", "minHeight", etc. These are marked as only
+ // accepting "integer", but also appear to accept "reference". Therefore,
+ // in these cases, be more lenient. (This happens for theme references too,
+ // such as ?android:attr/listPreferredItemHeight)
+ if ((first == '@' || first == '?') /* && mFormats.contains(REFERENCE)*/) {
+ if (value.equals("@null")) {
+ return true;
+ }
+
+ if (value.startsWith(NEW_ID_PREFIX) || value.startsWith(ID_PREFIX)) {
+ // These are handled in the IdGeneratingResourceFile; we shouldn't
+ // complain about not finding ids in the repository yet since they may
+ // not yet have been defined (@+id's can be defined in the same layout,
+ // later on.)
+ return true;
+ }
+
+ if (value.startsWith(ANDROID_PREFIX) || value.startsWith(ANDROID_THEME_PREFIX)) {
+ if (frameworkResources != null) {
+ return frameworkResources.hasResourceItem(value);
+ }
+ } else if (projectResources != null) {
+ return projectResources.hasResourceItem(value);
+ }
+
+ // Validate resource string
+ String url = value;
+ int typeEnd = url.indexOf('/', 1);
+ if (typeEnd != -1) {
+ int typeBegin = url.startsWith("@+") ? 2 : 1; //$NON-NLS-1$
+ int colon = url.lastIndexOf(':', typeEnd);
+ if (colon != -1) {
+ typeBegin = colon + 1;
+ }
+ String typeName = url.substring(typeBegin, typeEnd);
+ ResourceType type = ResourceType.getEnum(typeName);
+ if (type != null) {
+ // TODO: Validate that the name portion conforms to the rules
+ // (is an identifier but not a keyword, etc.)
+ // Also validate that the prefix before the colon is either
+ // not there or is "android"
+
+ //int nameBegin = typeEnd + 1;
+ //String name = url.substring(nameBegin);
+ return true;
+ }
+ }
+ }
+
+ if (mFormats.contains(ENUM) && mEnumValues != null) {
+ for (String e : mEnumValues) {
+ if (value.equals(e)) {
+ return true;
+ }
+ }
+ }
+
+ if (mFormats.contains(FLAG) && mFlagValues != null) {
+ for (String v : Splitter.on('|').split(value)) {
+ for (String e : mFlagValues) {
+ if (v.equals(e)) {
+ return true;
+ }
+ }
+ }
+ }
+
+ if (mFormats.contains(DIMENSION)) {
+ if (DIMENSION_PATTERN.matcher(value).matches()) {
+ return true;
+ }
+ }
+
+ if (mFormats.contains(BOOLEAN)) {
+ if (value.equals(VALUE_TRUE) || value.equals(VALUE_FALSE)) {
+ return true;
+ }
+ }
+
+ if (mFormats.contains(FLOAT)) {
+ if (Character.isDigit(first) || first == '-' || first == '.') {
+ if (FLOAT_PATTERN.matcher(value).matches()) {
+ return true;
+ }
+ // AAPT accepts more general floats, such as ".1",
+ try {
+ Float.parseFloat(value);
+ return true;
+ } catch (NumberFormatException nufe) {
+ // Not a float
+ }
+ }
+ }
+
+ if (mFormats.contains(INTEGER)) {
+ if (Character.isDigit(first) || first == '-') {
+ if (INTEGER_PATTERN.matcher(value).matches()) {
+ return true;
+ }
+ }
+ }
+
+ if (mFormats.contains(COLOR)) {
+ if (first == '#' && value.length() <= 9) { // Only allowed 32 bit ARGB
+ try {
+ // Use Long.parseLong rather than Integer.parseInt to not overflow on
+ // 32 big hex values like "ff191919"
+ Long.parseLong(value.substring(1), 16);
+ return true;
+ } catch (NumberFormatException nufe) {
+ // Not a valid color number
+ }
+ }
+ }
+
+ if (mFormats.contains(FRACTION)) {
+ // should end with % or %p
+ return true;
+ }
+
+ return false;
+ }
}
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/common/resources/platform/AttrsXmlParser.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/common/resources/platform/AttrsXmlParser.java
index 3c1fa97..1330c50 100644
--- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/common/resources/platform/AttrsXmlParser.java
+++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/common/resources/platform/AttrsXmlParser.java
@@ -23,6 +23,7 @@ import com.android.ide.common.api.IAttributeInfo.Format;
import com.android.ide.common.resources.platform.ViewClassInfo.LayoutParamsInfo;
import com.android.ide.eclipse.adt.AdtUtils;
import com.android.utils.ILogger;
+import com.google.common.collect.Maps;
import org.w3c.dom.Document;
import org.w3c.dom.Node;
@@ -54,7 +55,7 @@ public final class AttrsXmlParser {
// all attributes that have the same name are supposed to have the same
// parameters so we'll keep a cache of them to avoid processing them twice.
- private HashMap<String, AttributeInfo> mAttributeMap;
+ private Map<String, AttributeInfo> mAttributeMap;
/** Map of all attribute names for a given element */
private final Map<String, DeclareStyleableInfo> mStyleMap =
@@ -75,7 +76,6 @@ public final class AttrsXmlParser {
*/
private final ILogger mLog;
-
/**
* Creates a new {@link AttrsXmlParser}, set to load things from the given
* XML file. Nothing has been parsed yet. Callers should call {@link #preload()}
@@ -84,9 +84,19 @@ public final class AttrsXmlParser {
* @param osAttrsXmlPath The path of the <code>attrs.xml</code> file to parse.
* Must not be null. Should point to an existing valid XML document.
* @param log A logger object. Must not be null.
+ * @param expectedAttributeCount expected number of attributes in the file
+ */
+ public AttrsXmlParser(String osAttrsXmlPath, ILogger log, int expectedAttributeCount) {
+ this(osAttrsXmlPath, null /* inheritableAttributes */, log, expectedAttributeCount);
+ }
+
+ /**
+ * Returns the parsed map of attribute infos
+ *
+ * @return a map from string name to {@link AttributeInfo}
*/
- public AttrsXmlParser(String osAttrsXmlPath, ILogger log) {
- this(osAttrsXmlPath, null /* inheritableAttributes */, log);
+ public Map<String, AttributeInfo> getAttributeMap() {
+ return mAttributeMap;
}
/**
@@ -103,24 +113,26 @@ public final class AttrsXmlParser {
* If not null, the parser must have had its {@link #preload()} method
* invoked prior to being used here.
* @param log A logger object. Must not be null.
+ * @param expectedAttributeCount expected number of attributes in the file
*/
public AttrsXmlParser(
String osAttrsXmlPath,
AttrsXmlParser inheritableAttributes,
- ILogger log) {
+ ILogger log,
+ int expectedAttributeCount) {
mOsAttrsXmlPath = osAttrsXmlPath;
mLog = log;
assert osAttrsXmlPath != null;
assert log != null;
+ mAttributeMap = Maps.newHashMapWithExpectedSize(expectedAttributeCount);
if (inheritableAttributes == null) {
- mAttributeMap = new HashMap<String, AttributeInfo>();
mEnumFlagValues = new HashMap<String, Map<String,Integer>>();
} else {
- mAttributeMap = new HashMap<String, AttributeInfo>(inheritableAttributes.mAttributeMap);
+ mAttributeMap.putAll(inheritableAttributes.mAttributeMap);
mEnumFlagValues = new HashMap<String, Map<String,Integer>>(
- inheritableAttributes.mEnumFlagValues);
+ inheritableAttributes.mEnumFlagValues);
}
// Pre-compute the set of format names such that we don't have to compute the uppercase
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/AdtUtils.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/AdtUtils.java
index cf79c90..d5fa567 100644
--- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/AdtUtils.java
+++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/AdtUtils.java
@@ -53,7 +53,6 @@ import org.eclipse.jdt.core.IJavaProject;
import org.eclipse.jface.text.BadLocationException;
import org.eclipse.jface.text.IDocument;
import org.eclipse.jface.text.IRegion;
-import org.eclipse.jface.text.TextUtilities;
import org.eclipse.jface.viewers.ISelection;
import org.eclipse.jface.viewers.IStructuredSelection;
import org.eclipse.swt.widgets.Display;
@@ -90,98 +89,6 @@ import java.util.Locale;
@SuppressWarnings("restriction") // WST API
public class AdtUtils {
/**
- * Returns true if the given string ends with the given suffix, using a
- * case-insensitive comparison.
- *
- * @param string the full string to be checked
- * @param suffix the suffix to be checked for
- * @return true if the string case-insensitively ends with the given suffix
- */
- public static boolean endsWithIgnoreCase(String string, String suffix) {
- return string.regionMatches(true /* ignoreCase */, string.length() - suffix.length(),
- suffix, 0, suffix.length());
- }
-
- /**
- * Returns true if the given sequence ends with the given suffix (case
- * sensitive).
- *
- * @param sequence the character sequence to be checked
- * @param suffix the suffix to look for
- * @return true if the given sequence ends with the given suffix
- */
- public static boolean endsWith(CharSequence sequence, CharSequence suffix) {
- return endsWith(sequence, sequence.length(), suffix);
- }
-
- /**
- * Returns true if the given sequence ends at the given offset with the given suffix (case
- * sensitive)
- *
- * @param sequence the character sequence to be checked
- * @param endOffset the offset at which the sequence is considered to end
- * @param suffix the suffix to look for
- * @return true if the given sequence ends with the given suffix
- */
- public static boolean endsWith(CharSequence sequence, int endOffset, CharSequence suffix) {
- if (endOffset < suffix.length()) {
- return false;
- }
-
- for (int i = endOffset - 1, j = suffix.length() - 1; j >= 0; i--, j--) {
- if (sequence.charAt(i) != suffix.charAt(j)) {
- return false;
- }
- }
-
- return true;
- }
-
- /**
- * Returns true if the given string starts with the given prefix, using a
- * case-insensitive comparison.
- *
- * @param string the full string to be checked
- * @param prefix the prefix to be checked for
- * @return true if the string case-insensitively starts with the given prefix
- */
- public static boolean startsWithIgnoreCase(String string, String prefix) {
- return string.regionMatches(true /* ignoreCase */, 0, prefix, 0, prefix.length());
- }
-
- /**
- * Returns true if the given string starts at the given offset with the
- * given prefix, case insensitively.
- *
- * @param string the full string to be checked
- * @param offset the offset in the string to start looking
- * @param prefix the prefix to be checked for
- * @return true if the string case-insensitively starts at the given offset
- * with the given prefix
- */
- public static boolean startsWith(String string, int offset, String prefix) {
- return string.regionMatches(true /* ignoreCase */, offset, prefix, 0, prefix.length());
- }
-
- /**
- * Strips the whitespace from the given string
- *
- * @param string the string to be cleaned up
- * @return the string, without whitespace
- */
- public static String stripWhitespace(String string) {
- StringBuilder sb = new StringBuilder(string.length());
- for (int i = 0, n = string.length(); i < n; i++) {
- char c = string.charAt(i);
- if (!Character.isWhitespace(c)) {
- sb.append(c);
- }
- }
-
- return sb.toString();
- }
-
- /**
* Creates a Java class name out of the given string, if possible. For
* example, "My Project" becomes "MyProject", "hello" becomes "Hello",
* "Java's" becomes "Java", and so on.
@@ -344,29 +251,6 @@ public class AdtUtils {
return sb.toString();
}
- /** For use by {@link #getLineSeparator()} */
- private static String sLineSeparator;
-
- /**
- * Returns the default line separator to use.
- * <p>
- * NOTE: If you have an associated {@link IDocument}, it is better to call
- * {@link TextUtilities#getDefaultLineDelimiter(IDocument)} since that will
- * allow (for example) editing a \r\n-delimited document on a \n-delimited
- * platform and keep a consistent usage of delimiters in the file.
- *
- * @return the delimiter string to use
- */
- @NonNull
- public static String getLineSeparator() {
- if (sLineSeparator == null) {
- // This is guaranteed to exist:
- sLineSeparator = System.getProperty("line.separator"); //$NON-NLS-1$
- }
-
- return sLineSeparator;
- }
-
/**
* Returns the current editor (the currently visible and active editor), or null if
* not found
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/actions/DexDumpAction.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/actions/DexDumpAction.java
index 98d931f..a483e9b 100755
--- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/actions/DexDumpAction.java
+++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/actions/DexDumpAction.java
@@ -19,12 +19,12 @@ package com.android.ide.eclipse.adt.internal.actions;
import com.android.SdkConstants;
import com.android.annotations.Nullable;
import com.android.ide.eclipse.adt.AdtPlugin;
-import com.android.ide.eclipse.adt.AdtUtils;
import com.android.ide.eclipse.adt.internal.preferences.AdtPrefs.BuildVerbosity;
import com.android.ide.eclipse.adt.internal.sdk.Sdk;
import com.android.sdklib.util.GrabProcessOutput;
import com.android.sdklib.util.GrabProcessOutput.IProcessOutput;
import com.android.sdklib.util.GrabProcessOutput.Wait;
+import com.android.utils.SdkUtils;
import org.eclipse.core.filesystem.EFS;
import org.eclipse.core.filesystem.IFileStore;
@@ -166,7 +166,7 @@ public class DexDumpAction implements IObjectActionDelegate {
final BufferedWriter writer = new BufferedWriter(new FileWriter(dstFile));
try {
- final String lineSep = AdtUtils.getLineSeparator();
+ final String lineSep = SdkUtils.getLineSeparator();
int err = GrabProcessOutput.grabProcessOutput(
process,
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/build/builders/PreCompilerBuilder.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/build/builders/PreCompilerBuilder.java
index 51ae496..1507a8d 100644
--- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/build/builders/PreCompilerBuilder.java
+++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/build/builders/PreCompilerBuilder.java
@@ -325,7 +325,7 @@ public class PreCompilerBuilder extends BaseBuilder {
if (ResourceManager.isAutoBuilding()) {
IdeScanningContext context = new IdeScanningContext(projectResources,
- project);
+ project, true);
resManager.processDelta(delta, context);
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/AndroidContentAssist.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/AndroidContentAssist.java
index 2ea70fc..b39c4cb 100644
--- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/AndroidContentAssist.java
+++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/AndroidContentAssist.java
@@ -19,6 +19,9 @@ package com.android.ide.eclipse.adt.internal.editors;
import static com.android.SdkConstants.ATTR_LAYOUT_RESOURCE_PREFIX;
import static com.android.SdkConstants.PREFIX_RESOURCE_REF;
import static com.android.SdkConstants.UNIT_DP;
+import static com.android.SdkConstants.UNIT_IN;
+import static com.android.SdkConstants.UNIT_MM;
+import static com.android.SdkConstants.UNIT_PT;
import static com.android.SdkConstants.UNIT_PX;
import static com.android.SdkConstants.UNIT_SP;
import static com.android.ide.eclipse.adt.internal.editors.descriptors.AttributeDescriptor.ATTRIBUTE_ICON_FILENAME;
@@ -1149,13 +1152,13 @@ public abstract class AndroidContentAssist implements IContentAssistProcessor {
"<b>Scale-independent Pixels</b> - this is like the dp unit, but it is also scaled by "
+ "the user's font size preference.",
- "pt", //$NON-NLS-1$
+ UNIT_PT,
"<b>Points</b> - 1/72 of an inch based on the physical size of the screen.",
- "mm", //$NON-NLS-1$
+ UNIT_MM,
"<b>Millimeters</b> - based on the physical size of the screen.",
- "in", //$NON-NLS-1$
+ UNIT_IN,
"<b>Inches</b> - based on the physical size of the screen.",
UNIT_PX,
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/formatting/XmlPrettyPrinter.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/formatting/XmlPrettyPrinter.java
index 6f15d83..1dd32c7 100644
--- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/formatting/XmlPrettyPrinter.java
+++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/formatting/XmlPrettyPrinter.java
@@ -24,8 +24,8 @@ import static com.android.SdkConstants.XMLNS;
import com.android.annotations.NonNull;
import com.android.annotations.Nullable;
-import com.android.ide.eclipse.adt.AdtUtils;
import com.android.ide.eclipse.adt.internal.editors.layout.gle2.DomUtilities;
+import com.android.utils.SdkUtils;
import com.android.utils.XmlUtils;
import org.eclipse.wst.xml.core.internal.document.DocumentTypeImpl;
@@ -86,7 +86,7 @@ public class XmlPrettyPrinter {
mPrefs = prefs;
mStyle = style;
if (lineSeparator == null) {
- lineSeparator = AdtUtils.getLineSeparator();
+ lineSeparator = SdkUtils.getLineSeparator();
}
mLineSeparator = lineSeparator;
}
@@ -877,8 +877,8 @@ public class XmlPrettyPrinter {
return false;
}
- return AdtUtils.endsWith(mOut, mLineSeparator) &&
- AdtUtils.endsWith(mOut, mOut.length() - mLineSeparator.length(), mLineSeparator);
+ return SdkUtils.endsWith(mOut, mLineSeparator) &&
+ SdkUtils.endsWith(mOut, mOut.length() - mLineSeparator.length(), mLineSeparator);
}
private boolean newlineAfterElementClose(Element element, int depth) {
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/UiElementPullParser.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/UiElementPullParser.java
index de09d00..9553bc8 100644
--- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/UiElementPullParser.java
+++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/UiElementPullParser.java
@@ -22,6 +22,13 @@ import static com.android.SdkConstants.ATTR_LAYOUT_HEIGHT;
import static com.android.SdkConstants.ATTR_LAYOUT_WIDTH;
import static com.android.SdkConstants.ATTR_PADDING;
import static com.android.SdkConstants.AUTO_URI;
+import static com.android.SdkConstants.UNIT_DIP;
+import static com.android.SdkConstants.UNIT_DP;
+import static com.android.SdkConstants.UNIT_IN;
+import static com.android.SdkConstants.UNIT_MM;
+import static com.android.SdkConstants.UNIT_PT;
+import static com.android.SdkConstants.UNIT_PX;
+import static com.android.SdkConstants.UNIT_SP;
import static com.android.SdkConstants.VALUE_FILL_PARENT;
import static com.android.SdkConstants.VALUE_MATCH_PARENT;
import static com.android.SdkConstants.VIEW_FRAGMENT;
@@ -78,7 +85,7 @@ public class UiElementPullParser extends BasePullParser {
* Number of pixels to pad views with in exploded-rendering mode.
*/
private static final String DEFAULT_PADDING_VALUE =
- ExplodedRenderingHelper.PADDING_VALUE + "px"; //$NON-NLS-1$
+ ExplodedRenderingHelper.PADDING_VALUE + UNIT_PX;
/**
* Number of pixels to pad exploded individual views with. (This is HALF the width of the
@@ -542,13 +549,13 @@ public class UiElementPullParser extends BasePullParser {
private static final int COMPLEX_UNIT_MM = 5;
private final static DimensionEntry[] sDimensions = new DimensionEntry[] {
- new DimensionEntry("px", COMPLEX_UNIT_PX),
- new DimensionEntry("dip", COMPLEX_UNIT_DIP),
- new DimensionEntry("dp", COMPLEX_UNIT_DIP),
- new DimensionEntry("sp", COMPLEX_UNIT_SP),
- new DimensionEntry("pt", COMPLEX_UNIT_PT),
- new DimensionEntry("in", COMPLEX_UNIT_IN),
- new DimensionEntry("mm", COMPLEX_UNIT_MM),
+ new DimensionEntry(UNIT_PX, COMPLEX_UNIT_PX),
+ new DimensionEntry(UNIT_DIP, COMPLEX_UNIT_DIP),
+ new DimensionEntry(UNIT_DP, COMPLEX_UNIT_DIP),
+ new DimensionEntry(UNIT_SP, COMPLEX_UNIT_SP),
+ new DimensionEntry(UNIT_PT, COMPLEX_UNIT_PT),
+ new DimensionEntry(UNIT_IN, COMPLEX_UNIT_IN),
+ new DimensionEntry(UNIT_MM, COMPLEX_UNIT_MM),
};
/**
@@ -564,7 +571,7 @@ public class UiElementPullParser extends BasePullParser {
padding += sIntOut[0];
}
- return padding + "px"; //$NON-NLS-1$
+ return padding + UNIT_PX;
}
/**
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/descriptors/CustomViewDescriptorService.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/descriptors/CustomViewDescriptorService.java
index ca229a4..6df6929 100644
--- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/descriptors/CustomViewDescriptorService.java
+++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/descriptors/CustomViewDescriptorService.java
@@ -265,7 +265,7 @@ public final class CustomViewDescriptorService {
if (parser == null) {
parser = new AttrsXmlParser(
file.getFile().getOsLocation(),
- AdtPlugin.getDefault());
+ AdtPlugin.getDefault(), 20);
parser.preload();
mParserCache.put(file, parser);
}
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/ImageUtils.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/ImageUtils.java
index 0178173..c55d0d8 100644
--- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/ImageUtils.java
+++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/ImageUtils.java
@@ -20,7 +20,7 @@ import static com.android.SdkConstants.DOT_BMP;
import static com.android.SdkConstants.DOT_GIF;
import static com.android.SdkConstants.DOT_JPG;
import static com.android.SdkConstants.DOT_PNG;
-import static com.android.ide.eclipse.adt.AdtUtils.endsWithIgnoreCase;
+import static com.android.utils.SdkUtils.endsWithIgnoreCase;
import com.android.annotations.NonNull;
import com.android.annotations.Nullable;
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/properties/ResourceValueCompleter.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/properties/ResourceValueCompleter.java
index f6b80d7..081ec80 100644
--- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/properties/ResourceValueCompleter.java
+++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/properties/ResourceValueCompleter.java
@@ -24,7 +24,6 @@ import static com.android.SdkConstants.PREFIX_THEME_REF;
import com.android.ide.common.resources.ResourceItem;
import com.android.ide.common.resources.ResourceRepository;
-import com.android.ide.eclipse.adt.AdtUtils;
import com.android.ide.eclipse.adt.internal.editors.AndroidXmlEditor;
import com.android.ide.eclipse.adt.internal.editors.common.CommonXmlEditor;
import com.android.ide.eclipse.adt.internal.editors.descriptors.AttributeDescriptor;
@@ -32,6 +31,7 @@ import com.android.ide.eclipse.adt.internal.editors.uimodel.UiResourceAttributeN
import com.android.ide.eclipse.adt.internal.resources.manager.ResourceManager;
import com.android.ide.eclipse.adt.internal.sdk.AndroidTargetData;
import com.android.resources.ResourceType;
+import com.android.utils.SdkUtils;
import org.eclipse.core.resources.IProject;
import org.eclipse.jface.fieldassist.ContentProposal;
@@ -172,7 +172,7 @@ class ResourceValueCompleter implements IContentProposalProvider {
prefix.length() <= nameStart ? "" : prefix.substring(nameStart);
for (ResourceItem item : repository.getResourceItemsOfType(type)) {
String name = item.getName();
- if (AdtUtils.startsWithIgnoreCase(name, namePrefix)) {
+ if (SdkUtils.startsWithIgnoreCase(name, namePrefix)) {
results.add(base + name);
}
}
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/properties/ValueCompleter.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/properties/ValueCompleter.java
index 132855d..5559349 100644
--- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/properties/ValueCompleter.java
+++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/properties/ValueCompleter.java
@@ -35,9 +35,9 @@ import com.android.annotations.NonNull;
import com.android.annotations.Nullable;
import com.android.ide.common.api.IAttributeInfo;
import com.android.ide.common.api.IAttributeInfo.Format;
-import com.android.ide.eclipse.adt.AdtUtils;
import com.android.ide.eclipse.adt.internal.editors.common.CommonXmlEditor;
import com.android.ide.eclipse.adt.internal.editors.descriptors.AttributeDescriptor;
+import com.android.utils.SdkUtils;
import org.eclipse.jface.fieldassist.ContentProposal;
import org.eclipse.jface.fieldassist.IContentProposal;
@@ -147,7 +147,7 @@ abstract class ValueCompleter implements IContentProposalProvider {
}
for (String value : values) {
- if (AdtUtils.startsWithIgnoreCase(value, prefix)) {
+ if (SdkUtils.startsWithIgnoreCase(value, prefix)) {
if (prepend != null && prepend.contains(value)) {
continue;
}
@@ -165,13 +165,13 @@ abstract class ValueCompleter implements IContentProposalProvider {
String[] values = info.getEnumValues();
if (values != null) {
for (String value : values) {
- if (AdtUtils.startsWithIgnoreCase(value, prefix)) {
+ if (SdkUtils.startsWithIgnoreCase(value, prefix)) {
proposals.add(new ContentProposal(value));
}
}
for (String value : values) {
- if (!AdtUtils.startsWithIgnoreCase(value, prefix)) {
+ if (!SdkUtils.startsWithIgnoreCase(value, prefix)) {
proposals.add(new ContentProposal(value));
}
}
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/manifest/pages/ApplicationToggle.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/manifest/pages/ApplicationToggle.java
index 16519fe..159f089 100644
--- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/manifest/pages/ApplicationToggle.java
+++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/manifest/pages/ApplicationToggle.java
@@ -17,7 +17,6 @@
package com.android.ide.eclipse.adt.internal.editors.manifest.pages;
import com.android.ide.eclipse.adt.AdtPlugin;
-import com.android.ide.eclipse.adt.AdtUtils;
import com.android.ide.eclipse.adt.internal.editors.descriptors.DescriptorsUtils;
import com.android.ide.eclipse.adt.internal.editors.manifest.ManifestEditor;
import com.android.ide.eclipse.adt.internal.editors.ui.UiElementPart;
@@ -25,6 +24,7 @@ import com.android.ide.eclipse.adt.internal.editors.uimodel.IUiUpdateListener;
import com.android.ide.eclipse.adt.internal.editors.uimodel.IUiUpdateListener.UiUpdateState;
import com.android.ide.eclipse.adt.internal.editors.uimodel.UiElementNode;
import com.android.ide.eclipse.adt.internal.sdk.Sdk;
+import com.android.utils.SdkUtils;
import org.eclipse.swt.SWT;
import org.eclipse.swt.events.SelectionAdapter;
@@ -220,7 +220,7 @@ final class ApplicationToggle extends UiElementPart {
}
mUndoXmlParent.insertBefore(mUndoXmlNode, next);
if (next == null) {
- Text sep = mUndoXmlDocument.createTextNode(AdtUtils.getLineSeparator());
+ Text sep = mUndoXmlDocument.createTextNode(SdkUtils.getLineSeparator());
mUndoXmlParent.insertBefore(sep, null); // insert separator before end tag
}
success = true;
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/uimodel/UiElementNode.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/uimodel/UiElementNode.java
index b521d78..f905c73 100644
--- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/uimodel/UiElementNode.java
+++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/uimodel/UiElementNode.java
@@ -27,7 +27,6 @@ import com.android.annotations.VisibleForTesting;
import com.android.ide.common.api.IAttributeInfo.Format;
import com.android.ide.common.resources.platform.AttributeInfo;
import com.android.ide.eclipse.adt.AdtPlugin;
-import com.android.ide.eclipse.adt.AdtUtils;
import com.android.ide.eclipse.adt.internal.editors.AndroidXmlEditor;
import com.android.ide.eclipse.adt.internal.editors.descriptors.AttributeDescriptor;
import com.android.ide.eclipse.adt.internal.editors.descriptors.ElementDescriptor;
@@ -42,6 +41,7 @@ import com.android.ide.eclipse.adt.internal.editors.otherxml.descriptors.OtherXm
import com.android.ide.eclipse.adt.internal.editors.uimodel.IUiUpdateListener.UiUpdateState;
import com.android.ide.eclipse.adt.internal.preferences.AdtPrefs;
import com.android.ide.eclipse.adt.internal.sdk.AndroidTargetData;
+import com.android.utils.SdkUtils;
import com.android.utils.XmlUtils;
import org.eclipse.jface.text.TextUtilities;
@@ -1035,7 +1035,7 @@ public class UiElementNode implements IPropertySource {
if (document != null) {
newLine = TextUtilities.getDefaultLineDelimiter(document);
} else {
- newLine = AdtUtils.getLineSeparator();
+ newLine = SdkUtils.getLineSeparator();
}
Text indentNode = doc.createTextNode(newLine + indent);
parentXmlNode.insertBefore(indentNode, xmlNextSibling);
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/lint/ConvertToDpFix.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/lint/ConvertToDpFix.java
index 0ca6aa2..44b676f 100644
--- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/lint/ConvertToDpFix.java
+++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/lint/ConvertToDpFix.java
@@ -15,6 +15,7 @@
*/
package com.android.ide.eclipse.adt.internal.lint;
+import static com.android.SdkConstants.UNIT_PX;
import static com.android.SdkConstants.VALUE_N_DP;
import com.android.ide.eclipse.adt.AdtPlugin;
@@ -63,7 +64,7 @@ final class ConvertToDpFix extends DocumentFix implements IInputValidator {
for (int i = 0, n = attributes.getLength(); i < n; i++) {
Attr attribute = (Attr) attributes.item(i);
String value = attribute.getValue();
- if (value.endsWith("px")) {
+ if (value.endsWith(UNIT_PX)) {
Matcher matcher = pattern.matcher(value);
if (matcher.matches()) {
String numberString = matcher.group(1);
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/lint/EclipseLintClient.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/lint/EclipseLintClient.java
index 1937a94..b3303b3 100644
--- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/lint/EclipseLintClient.java
+++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/lint/EclipseLintClient.java
@@ -15,9 +15,9 @@
*/
package com.android.ide.eclipse.adt.internal.lint;
-import static com.android.SdkConstants.FD_NATIVE_LIBS;
import static com.android.SdkConstants.DOT_JAR;
import static com.android.SdkConstants.DOT_XML;
+import static com.android.SdkConstants.FD_NATIVE_LIBS;
import static com.android.ide.eclipse.adt.AdtConstants.MARKER_LINT;
import static com.android.ide.eclipse.adt.AdtUtils.workspacePathToFile;
@@ -50,6 +50,7 @@ import com.android.tools.lint.detector.api.Project;
import com.android.tools.lint.detector.api.Severity;
import com.android.tools.lint.detector.api.XmlContext;
import com.android.utils.Pair;
+import com.android.utils.SdkUtils;
import com.google.common.collect.Maps;
import org.eclipse.core.resources.IFile;
@@ -673,7 +674,7 @@ public class EclipseLintClient extends LintClient implements IDomParser {
return readPlainFile(f);
}
- if (AdtUtils.endsWithIgnoreCase(file.getName(), DOT_XML)) {
+ if (SdkUtils.endsWithIgnoreCase(file.getName(), DOT_XML)) {
IStructuredModel model = null;
try {
IModelManager modelManager = StructuredModelManager.getModelManager();
@@ -813,7 +814,7 @@ public class EclipseLintClient extends LintClient implements IDomParser {
File[] jars = libs.listFiles();
if (jars != null) {
for (File jar : jars) {
- if (AdtUtils.endsWith(jar.getPath(), DOT_JAR)) {
+ if (SdkUtils.endsWith(jar.getPath(), DOT_JAR)) {
libraries.add(jar);
}
}
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/lint/LintFixGenerator.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/lint/LintFixGenerator.java
index 47aac0c..5d2a2bb 100644
--- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/lint/LintFixGenerator.java
+++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/lint/LintFixGenerator.java
@@ -29,6 +29,7 @@ import com.android.tools.lint.client.api.IssueRegistry;
import com.android.tools.lint.detector.api.Issue;
import com.android.tools.lint.detector.api.Project;
import com.android.tools.lint.detector.api.Severity;
+import com.android.utils.SdkUtils;
import org.eclipse.core.resources.IFile;
import org.eclipse.core.resources.IMarker;
@@ -258,7 +259,7 @@ public class LintFixGenerator implements IMarkerResolutionGenerator2, IQuickAssi
}
IFile file = (IFile) resource;
boolean isJava = file.getName().endsWith(DOT_JAVA);
- boolean isXml = AdtUtils.endsWith(file.getName(), DOT_XML);
+ boolean isXml = SdkUtils.endsWith(file.getName(), DOT_XML);
if (!isJava && !isXml) {
return;
}
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/lint/LintJob.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/lint/LintJob.java
index 0442f18..2851c47 100644
--- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/lint/LintJob.java
+++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/lint/LintJob.java
@@ -28,6 +28,7 @@ import com.android.tools.lint.client.api.IssueRegistry;
import com.android.tools.lint.client.api.LintDriver;
import com.android.tools.lint.detector.api.Issue;
import com.android.tools.lint.detector.api.Scope;
+import com.android.utils.SdkUtils;
import org.eclipse.core.resources.IFile;
import org.eclipse.core.resources.IMarker;
@@ -93,7 +94,7 @@ final class LintJob extends Job {
scope = Scope.ALL;
} else {
String name = resource.getName();
- if (AdtUtils.endsWithIgnoreCase(name, DOT_XML)) {
+ if (SdkUtils.endsWithIgnoreCase(name, DOT_XML)) {
if (name.equals(SdkConstants.FN_ANDROID_MANIFEST_XML)) {
scope = EnumSet.of(Scope.MANIFEST);
} else {
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/resources/manager/IdeScanningContext.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/resources/manager/IdeScanningContext.java
index 5a4fc75..d613249 100644
--- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/resources/manager/IdeScanningContext.java
+++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/resources/manager/IdeScanningContext.java
@@ -15,15 +15,20 @@
*/
package com.android.ide.eclipse.adt.internal.resources.manager;
+import static com.android.SdkConstants.ANDROID_URI;
import static com.android.ide.eclipse.adt.AdtConstants.MARKER_AAPT_COMPILE;
-
import static org.eclipse.core.resources.IResource.DEPTH_ONE;
import static org.eclipse.core.resources.IResource.DEPTH_ZERO;
+import com.android.annotations.NonNull;
+import com.android.annotations.Nullable;
import com.android.ide.common.resources.ResourceRepository;
import com.android.ide.common.resources.ScanningContext;
+import com.android.ide.common.resources.platform.AttributeInfo;
import com.android.ide.eclipse.adt.AdtPlugin;
import com.android.ide.eclipse.adt.internal.build.AaptParser;
+import com.android.ide.eclipse.adt.internal.sdk.AndroidTargetData;
+import com.android.ide.eclipse.adt.internal.sdk.Sdk;
import com.android.utils.Pair;
import org.eclipse.core.resources.IFolder;
@@ -36,6 +41,7 @@ import java.util.ArrayList;
import java.util.Collection;
import java.util.HashSet;
import java.util.List;
+import java.util.Map;
import java.util.Set;
/**
@@ -50,20 +56,36 @@ public class IdeScanningContext extends ScanningContext {
private IResource mCurrentFile;
private List<Pair<IResource, String>> mErrors;
private Set<IProject> mFullAaptProjects;
+ private boolean mValidate;
+ private Map<String, AttributeInfo> mAttributeMap;
+ private ResourceRepository mFrameworkResources;
/**
* Constructs a new {@link IdeScanningContext}
*
* @param repository the associated {@link ResourceRepository}
* @param project the associated project
+ * @param validate if true, check that the attributes and resources are
+ * valid and if not request a full AAPT check
*/
- public IdeScanningContext(ResourceRepository repository, IProject project) {
+ public IdeScanningContext(@NonNull ResourceRepository repository, @NonNull IProject project,
+ boolean validate) {
super(repository);
mProject = project;
+ mValidate = validate;
+
+ Sdk sdk = Sdk.getCurrent();
+ if (sdk != null) {
+ AndroidTargetData targetData = sdk.getTargetData(project);
+ if (targetData != null) {
+ mAttributeMap = targetData.getAttributeMap();
+ mFrameworkResources = targetData.getFrameworkResources();
+ }
+ }
}
@Override
- public void addError(String error) {
+ public void addError(@NonNull String error) {
super.addError(error);
if (mErrors == null) {
@@ -77,7 +99,7 @@ public class IdeScanningContext extends ScanningContext {
*
* @param resource the resource about to be scanned
*/
- public void startScanning(IResource resource) {
+ public void startScanning(@NonNull IResource resource) {
assert mCurrentFile == null : mCurrentFile;
mCurrentFile = resource;
mScannedResources.add(resource);
@@ -88,7 +110,7 @@ public class IdeScanningContext extends ScanningContext {
*
* @param resource the resource that was scanned
*/
- public void finishScanning(IResource resource) {
+ public void finishScanning(@NonNull IResource resource) {
assert mCurrentFile != null;
mCurrentFile = null;
}
@@ -193,4 +215,20 @@ public class IdeScanningContext extends ScanningContext {
public Collection<IProject> getAaptRequestedProjects() {
return mFullAaptProjects;
}
+
+ @Override
+ public boolean checkValue(@Nullable String uri, @NonNull String name, @NonNull String value) {
+ if (!mValidate) {
+ return true;
+ }
+
+ if (!needsFullAapt() && mAttributeMap != null && ANDROID_URI.equals(uri)) {
+ AttributeInfo info = mAttributeMap.get(name);
+ if (info != null && !info.isValid(value, mRepository, mFrameworkResources)) {
+ return false;
+ }
+ }
+
+ return super.checkValue(uri, name, value);
+ }
}
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/resources/manager/ResourceManager.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/resources/manager/ResourceManager.java
index b346dcd..e1a12d7 100644
--- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/resources/manager/ResourceManager.java
+++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/resources/manager/ResourceManager.java
@@ -423,7 +423,7 @@ public final class ResourceManager {
if (delta.getResource() instanceof IProject) {
IProject project = (IProject) delta.getResource();
IdeScanningContext context =
- new IdeScanningContext(getProjectResources(project), project);
+ new IdeScanningContext(getProjectResources(project), project, true);
processDelta(delta, context);
@@ -522,7 +522,7 @@ public final class ResourceManager {
mMap.put(project, projectResources);
}
}
- IdeScanningContext context = new IdeScanningContext(projectResources, project);
+ IdeScanningContext context = new IdeScanningContext(projectResources, project, true);
if (resourceFolder != null && resourceFolder.exists()) {
try {
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/sdk/AndroidTargetData.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/sdk/AndroidTargetData.java
index 59a1236..85ae9fd 100644
--- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/sdk/AndroidTargetData.java
+++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/sdk/AndroidTargetData.java
@@ -16,9 +16,12 @@
package com.android.ide.eclipse.adt.internal.sdk;
+import com.android.annotations.NonNull;
+import com.android.annotations.Nullable;
import com.android.ide.common.rendering.LayoutLibrary;
import com.android.ide.common.rendering.api.LayoutLog;
import com.android.ide.common.resources.ResourceRepository;
+import com.android.ide.common.resources.platform.AttributeInfo;
import com.android.ide.common.sdk.LoadStatus;
import com.android.ide.eclipse.adt.AdtPlugin;
import com.android.ide.eclipse.adt.internal.editors.animator.AnimDescriptors;
@@ -87,6 +90,7 @@ public class AndroidTargetData {
private ResourceRepository mFrameworkResources;
private LayoutLibrary mLayoutLibrary;
+ private Map<String, AttributeInfo> mAttributeMap;
private boolean mLayoutBridgeInit = false;
@@ -95,6 +99,27 @@ public class AndroidTargetData {
}
/**
+ * Sets the associated map from string attribute name to
+ * {@link AttributeInfo}
+ *
+ * @param attributeMap the map
+ */
+ public void setAttributeMap(@NonNull Map<String, AttributeInfo> attributeMap) {
+ mAttributeMap = attributeMap;
+ }
+
+ /**
+ * Returns the associated map from string attribute name to
+ * {@link AttributeInfo}
+ *
+ * @return the map
+ */
+ @Nullable
+ public Map<String, AttributeInfo> getAttributeMap() {
+ return mAttributeMap;
+ }
+
+ /**
* Creates an AndroidTargetData object.
*/
void setExtraData(
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/sdk/AndroidTargetParser.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/sdk/AndroidTargetParser.java
index 24eadfa..9a1fd3d 100644
--- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/sdk/AndroidTargetParser.java
+++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/sdk/AndroidTargetParser.java
@@ -129,15 +129,17 @@ public final class AndroidTargetParser {
progress.subTask("Attributes definitions");
AttrsXmlParser attrsXmlParser = new AttrsXmlParser(
mAndroidTarget.getPath(IAndroidTarget.ATTRIBUTES),
- AdtPlugin.getDefault());
+ AdtPlugin.getDefault(),
+ 1000);
attrsXmlParser.preload();
+
progress.worked(1);
progress.subTask("Manifest definitions");
AttrsXmlParser attrsManifestXmlParser = new AttrsXmlParser(
mAndroidTarget.getPath(IAndroidTarget.MANIFEST_ATTRIBUTES),
attrsXmlParser,
- AdtPlugin.getDefault());
+ AdtPlugin.getDefault(), 1100);
attrsManifestXmlParser.preload();
progress.worked(1);
@@ -290,6 +292,8 @@ public final class AndroidTargetParser {
frameworkResources,
layoutBridge);
+ targetData.setAttributeMap(attrsXmlParser.getAttributeMap());
+
Sdk.getCurrent().setTargetData(mAndroidTarget, targetData);
return Status.OK_STATUS;
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/sdk/Sdk.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/sdk/Sdk.java
index b1b057b..1a299d9 100644
--- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/sdk/Sdk.java
+++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/sdk/Sdk.java
@@ -16,9 +16,9 @@
package com.android.ide.eclipse.adt.internal.sdk;
-import static com.android.SdkConstants.FD_RES;
import static com.android.SdkConstants.DOT_XML;
import static com.android.SdkConstants.EXT_JAR;
+import static com.android.SdkConstants.FD_RES;
import com.android.SdkConstants;
import com.android.annotations.NonNull;
@@ -215,6 +215,7 @@ public final class Sdk {
* Returns the lock object used to synchronize all operations dealing with SDK, targets and
* projects.
*/
+ @NonNull
public static final Object getLock() {
return LOCK;
}
@@ -224,6 +225,7 @@ public final class Sdk {
* <p/>If the SDK failed to load, it displays an error to the user.
* @param sdkLocation the OS path to the SDK.
*/
+ @Nullable
public static Sdk loadSdk(String sdkLocation) {
synchronized (LOCK) {
if (sCurrentSdk != null) {
@@ -298,6 +300,7 @@ public final class Sdk {
/**
* Returns the current {@link Sdk} object.
*/
+ @Nullable
public static Sdk getCurrent() {
synchronized (LOCK) {
return sCurrentSdk;
@@ -336,6 +339,7 @@ public final class Sdk {
*
* @return A file:// URL on the local documentation folder if it exists or null.
*/
+ @Nullable
public String getDocumentationBaseUrl() {
return mDocBaseUrl;
}
@@ -367,7 +371,8 @@ public final class Sdk {
* @param hash the {@link IAndroidTarget} hash string.
* @return The matching {@link IAndroidTarget} or null.
*/
- public IAndroidTarget getTargetFromHashString(String hash) {
+ @Nullable
+ public IAndroidTarget getTargetFromHashString(@NonNull String hash) {
return mManager.getTargetFromHashString(hash);
}
@@ -377,9 +382,9 @@ public final class Sdk {
* @param project the project to initialize
* @param target the project's target.
* @throws IOException if creating the file failed in any way.
- * @throws StreamException
+ * @throws StreamException if processing the project property file fails
*/
- public void initProject(IProject project, IAndroidTarget target)
+ public void initProject(@Nullable IProject project, @Nullable IAndroidTarget target)
throws IOException, StreamException {
if (project == null || target == null) {
return;
@@ -423,6 +428,7 @@ public final class Sdk {
* @param project the request project
* @return the ProjectState for the project.
*/
+ @Nullable
@SuppressWarnings("deprecation")
public static ProjectState getProjectState(IProject project) {
if (project == null) {
@@ -492,6 +498,7 @@ public final class Sdk {
/**
* Returns the {@link IAndroidTarget} object associated with the given {@link IProject}.
*/
+ @Nullable
public IAndroidTarget getTarget(IProject project) {
if (project == null) {
return null;
@@ -512,6 +519,7 @@ public final class Sdk {
* @param state the state representing the project to load.
* @return the target that was loaded.
*/
+ @Nullable
public IAndroidTarget loadTarget(ProjectState state) {
IAndroidTarget target = null;
if (state != null) {
@@ -537,6 +545,7 @@ public final class Sdk {
* If the target is already loaded, nothing happens.
* @return The load status if the target data is already loaded.
*/
+ @NonNull
public LoadStatus checkAndLoadTargetData(final IAndroidTarget target, IJavaProject project) {
boolean loadData = false;
@@ -633,6 +642,7 @@ public final class Sdk {
/**
* Return the {@link AndroidTargetData} for a given {@link IAndroidTarget}.
*/
+ @Nullable
public AndroidTargetData getTargetData(IAndroidTarget target) {
synchronized (LOCK) {
return mTargetDataMap.get(target);
@@ -642,6 +652,7 @@ public final class Sdk {
/**
* Return the {@link AndroidTargetData} for a given {@link IProject}.
*/
+ @Nullable
public AndroidTargetData getTargetData(IProject project) {
synchronized (LOCK) {
IAndroidTarget target = getTarget(project);
@@ -665,11 +676,13 @@ public final class Sdk {
* Returns the {@link AvdManager}. If the AvdManager failed to parse the AVD folder, this could
* be <code>null</code>.
*/
+ @Nullable
public AvdManager getAvdManager() {
return mAvdManager;
}
- public static AndroidVersion getDeviceVersion(IDevice device) {
+ @Nullable
+ public static AndroidVersion getDeviceVersion(@NonNull IDevice device) {
try {
Map<String, String> props = device.getProperties();
String apiLevel = props.get(IDevice.PROP_BUILD_API_LEVEL);
@@ -684,11 +697,13 @@ public final class Sdk {
}
}
+ @NonNull
public DeviceManager getDeviceManager() {
return mDeviceManager;
}
/** Returns the devices provided by the SDK, including user created devices */
+ @NonNull
public List<Device> getDevices() {
return mDeviceManager.getDevices(getSdkLocation());
}
@@ -699,10 +714,11 @@ public final class Sdk {
* @param project the library project.
* @return a possibly empty list of ProjectState.
*/
+ @NonNull
public static Set<ProjectState> getMainProjectsFor(IProject project) {
synchronized (LOCK) {
// first get the project directly depending on this.
- HashSet<ProjectState> list = new HashSet<ProjectState>();
+ Set<ProjectState> list = new HashSet<ProjectState>();
// loop on all project and see if ProjectState.getLibrary returns a non null
// project.
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/ui/ResourcePreviewHelper.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/ui/ResourcePreviewHelper.java
index d97d176..eeaca0c 100644
--- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/ui/ResourcePreviewHelper.java
+++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/ui/ResourcePreviewHelper.java
@@ -16,7 +16,7 @@
package com.android.ide.eclipse.adt.internal.ui;
import static com.android.SdkConstants.DOT_9PNG;
-import static com.android.ide.eclipse.adt.AdtUtils.endsWithIgnoreCase;
+import static com.android.utils.SdkUtils.endsWithIgnoreCase;
import com.android.ide.common.rendering.api.ResourceValue;
import com.android.ide.common.resources.ResourceResolver;
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/wizards/newproject/ProjectNamePage.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/wizards/newproject/ProjectNamePage.java
index 31c7225..6bf5e28 100644
--- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/wizards/newproject/ProjectNamePage.java
+++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/wizards/newproject/ProjectNamePage.java
@@ -18,8 +18,8 @@ package com.android.ide.eclipse.adt.internal.wizards.newproject;
import static com.android.SdkConstants.FN_PROJECT_PROGUARD_FILE;
import static com.android.SdkConstants.OS_SDK_TOOLS_LIB_FOLDER;
import static com.android.ide.eclipse.adt.AdtUtils.capitalize;
-import static com.android.ide.eclipse.adt.AdtUtils.stripWhitespace;
import static com.android.ide.eclipse.adt.internal.wizards.newproject.ApplicationInfoPage.ACTIVITY_NAME_SUFFIX;
+import static com.android.utils.SdkUtils.stripWhitespace;
import com.android.SdkConstants;
import com.android.ide.common.xml.ManifestData;
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/wizards/newxmlfile/NewXmlFileCreationPage.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/wizards/newxmlfile/NewXmlFileCreationPage.java
index e2d061b..68c7f4c 100644
--- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/wizards/newxmlfile/NewXmlFileCreationPage.java
+++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/wizards/newxmlfile/NewXmlFileCreationPage.java
@@ -46,6 +46,7 @@ import com.android.ide.eclipse.adt.internal.sdk.Sdk.TargetChangeListener;
import com.android.resources.ResourceFolderType;
import com.android.sdklib.IAndroidTarget;
import com.android.utils.Pair;
+import com.android.utils.SdkUtils;
import org.eclipse.core.resources.IFile;
import org.eclipse.core.resources.IProject;
@@ -648,7 +649,7 @@ class NewXmlFileCreationPage extends WizardPage {
if (res.getType() == IResource.FOLDER) {
wsFolderPath = res.getProjectRelativePath();
} else if (res.getType() == IResource.FILE) {
- if (AdtUtils.endsWithIgnoreCase(res.getName(), DOT_XML)) {
+ if (SdkUtils.endsWithIgnoreCase(res.getName(), DOT_XML)) {
fileName = res.getName();
}
wsFolderPath = res.getParent().getProjectRelativePath();
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/wizards/templates/FmEscapeXmlAttributeMethod.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/wizards/templates/FmEscapeXmlAttributeMethod.java
new file mode 100644
index 0000000..21f33b8
--- /dev/null
+++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/wizards/templates/FmEscapeXmlAttributeMethod.java
@@ -0,0 +1,40 @@
+/*
+ * Copyright (C) 2012 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.wizards.templates;
+
+import com.android.utils.XmlUtils;
+
+import freemarker.template.SimpleScalar;
+import freemarker.template.TemplateMethodModel;
+import freemarker.template.TemplateModel;
+import freemarker.template.TemplateModelException;
+
+import java.util.List;
+
+/**
+ * Method invoked by FreeMarker to escape a string such that it can be used
+ * as an XML attribute (escaping ', ", & and <).
+ */
+public class FmEscapeXmlAttributeMethod implements TemplateMethodModel {
+ @Override
+ public TemplateModel exec(List args) throws TemplateModelException {
+ if (args.size() != 1) {
+ throw new TemplateModelException("Wrong arguments");
+ }
+ String string = args.get(0).toString();
+ return new SimpleScalar(XmlUtils.toXmlAttributeValue(string));
+ }
+} \ No newline at end of file
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/wizards/templates/FmEscapeXmlStringMethod.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/wizards/templates/FmEscapeXmlStringMethod.java
new file mode 100644
index 0000000..7e5866e
--- /dev/null
+++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/wizards/templates/FmEscapeXmlStringMethod.java
@@ -0,0 +1,43 @@
+/*
+ * Copyright (C) 2012 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.wizards.templates;
+
+import com.android.ide.eclipse.adt.internal.refactorings.extractstring.ExtractStringRefactoring;
+
+import freemarker.template.SimpleScalar;
+import freemarker.template.TemplateMethodModel;
+import freemarker.template.TemplateModel;
+import freemarker.template.TemplateModelException;
+
+import java.util.List;
+
+/**
+ * Method invoked by FreeMarker to escape a string such that it can be placed
+ * as text in a string resource file.
+ * This is similar to {@link FmEscapeXmlTextMethod}, but in addition to escaping
+ * &lt; and &amp; it also escapes characters such as quotes necessary for Android
+ *{@code <string>} elements.
+ */
+public class FmEscapeXmlStringMethod implements TemplateMethodModel {
+ @Override
+ public TemplateModel exec(List args) throws TemplateModelException {
+ if (args.size() != 1) {
+ throw new TemplateModelException("Wrong arguments");
+ }
+ String string = args.get(0).toString();
+ return new SimpleScalar(ExtractStringRefactoring.escapeString(string));
+ }
+} \ No newline at end of file
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/wizards/templates/FmEscapeXmlTextMethod.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/wizards/templates/FmEscapeXmlTextMethod.java
new file mode 100644
index 0000000..55a4bc8
--- /dev/null
+++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/wizards/templates/FmEscapeXmlTextMethod.java
@@ -0,0 +1,40 @@
+/*
+ * Copyright (C) 2012 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.wizards.templates;
+
+import com.android.utils.XmlUtils;
+
+import freemarker.template.SimpleScalar;
+import freemarker.template.TemplateMethodModel;
+import freemarker.template.TemplateModel;
+import freemarker.template.TemplateModelException;
+
+import java.util.List;
+
+/**
+ * Method invoked by FreeMarker to escape a string such that it can be used
+ * as XML text (escaping < and &, but not ' and " etc).
+ */
+public class FmEscapeXmlTextMethod implements TemplateMethodModel {
+ @Override
+ public TemplateModel exec(List args) throws TemplateModelException {
+ if (args.size() != 1) {
+ throw new TemplateModelException("Wrong arguments");
+ }
+ String string = args.get(0).toString();
+ return new SimpleScalar(XmlUtils.toXmlTextValue(string));
+ }
+} \ No newline at end of file
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/wizards/templates/FmExtractLettersMethod.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/wizards/templates/FmExtractLettersMethod.java
new file mode 100644
index 0000000..09fa81c
--- /dev/null
+++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/wizards/templates/FmExtractLettersMethod.java
@@ -0,0 +1,45 @@
+/*
+ * Copyright (C) 2012 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.wizards.templates;
+
+import freemarker.template.SimpleScalar;
+import freemarker.template.TemplateMethodModel;
+import freemarker.template.TemplateModel;
+import freemarker.template.TemplateModelException;
+
+import java.util.List;
+
+/**
+ * Method invoked by FreeMarker to extract letters from a string; this will remove
+ * any whitespace, punctuation and digits.
+ */
+public class FmExtractLettersMethod implements TemplateMethodModel {
+ @Override
+ public TemplateModel exec(List args) throws TemplateModelException {
+ if (args.size() != 1) {
+ throw new TemplateModelException("Wrong arguments");
+ }
+ String string = args.get(0).toString();
+ StringBuilder sb = new StringBuilder(string.length());
+ for (int i = 0, n = string.length(); i < n; i++) {
+ char c = string.charAt(i);
+ if (Character.isLetter(c)) {
+ sb.append(c);
+ }
+ }
+ return new SimpleScalar(sb.toString());
+ }
+} \ No newline at end of file
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/wizards/templates/TemplateHandler.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/wizards/templates/TemplateHandler.java
index cb45522..53b6c3c 100644
--- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/wizards/templates/TemplateHandler.java
+++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/wizards/templates/TemplateHandler.java
@@ -42,6 +42,7 @@ import com.android.ide.eclipse.adt.internal.sdk.AdtManifestMergeCallback;
import com.android.manifmerger.ManifestMerger;
import com.android.manifmerger.MergerLog;
import com.android.resources.ResourceFolderType;
+import com.android.utils.SdkUtils;
import com.google.common.base.Charsets;
import com.google.common.collect.Lists;
import com.google.common.io.Files;
@@ -315,6 +316,10 @@ class TemplateHandler {
paramMap.put("activityToLayout", new FmActivityToLayoutMethod()); //$NON-NLS-1$
paramMap.put("layoutToActivity", new FmLayoutToActivityMethod()); //$NON-NLS-1$
paramMap.put("classToResource", new FmClassNameToResourceMethod()); //$NON-NLS-1$
+ paramMap.put("escapeXmlAttribute", new FmEscapeXmlStringMethod()); //$NON-NLS-1$
+ paramMap.put("escapeXmlText", new FmEscapeXmlStringMethod()); //$NON-NLS-1$
+ paramMap.put("escapeXmlString", new FmEscapeXmlStringMethod()); //$NON-NLS-1$
+ paramMap.put("extractLetters", new FmExtractLettersMethod()); //$NON-NLS-1$
// This should be handled better: perhaps declared "required packages" as part of the
// inputs? (It would be better if we could conditionally disable template based
@@ -688,7 +693,7 @@ class TemplateHandler {
} else {
// Just insert into file along with comment, using the "standard" conflict
// syntax that many tools and editors recognize.
- String sep = AdtUtils.getLineSeparator();
+ String sep = SdkUtils.getLineSeparator();
contents =
"<<<<<<< Original" + sep
+ currentXml + sep
diff --git a/eclipse/plugins/com.android.ide.eclipse.tests/unittests/com/android/ide/common/resources/platform/AttributeInfoTest.java b/eclipse/plugins/com.android.ide.eclipse.tests/unittests/com/android/ide/common/resources/platform/AttributeInfoTest.java
new file mode 100644
index 0000000..7ae96a8
--- /dev/null
+++ b/eclipse/plugins/com.android.ide.eclipse.tests/unittests/com/android/ide/common/resources/platform/AttributeInfoTest.java
@@ -0,0 +1,371 @@
+/*
+ * Copyright (C) 2012 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.common.resources.platform;
+
+import static com.android.SdkConstants.ANDROID_URI;
+import static com.android.SdkConstants.DOT_XML;
+
+import com.android.annotations.NonNull;
+import com.android.ide.common.api.IAttributeInfo.Format;
+import com.android.ide.common.resources.ResourceItem;
+import com.android.ide.common.resources.ResourceRepository;
+import com.android.ide.eclipse.adt.internal.editors.layout.gle2.DomUtilities;
+import com.android.resources.ResourceType;
+import com.android.utils.StdLogger;
+import com.google.common.base.Charsets;
+import com.google.common.collect.ArrayListMultimap;
+import com.google.common.collect.Multimap;
+import com.google.common.collect.Sets;
+import com.google.common.io.Files;
+
+import org.w3c.dom.Attr;
+import org.w3c.dom.Document;
+import org.w3c.dom.Element;
+import org.w3c.dom.NamedNodeMap;
+
+import java.io.File;
+import java.io.IOException;
+import java.util.Collection;
+import java.util.EnumSet;
+import java.util.Map;
+import java.util.Set;
+
+import junit.framework.TestCase;
+
+@SuppressWarnings("javadoc")
+public class AttributeInfoTest extends TestCase {
+ public void testSimple() throws Exception {
+ AttributeInfo info = new AttributeInfo("test", EnumSet.noneOf(Format.class));
+ assertTrue(info.isValid("", null, null));
+ assertTrue(info.isValid("a b c", null, null));
+ assertTrue(info.isValid("@android foo bar", null, null));
+ }
+
+ public void testIsValidString() throws Exception {
+ AttributeInfo info = new AttributeInfo("test", Format.STRING_SET);
+ assertTrue(info.isValid("", null, null));
+ assertTrue(info.isValid("a b c", null, null));
+ assertTrue(info.isValid("@android foo bar", null, null));
+ }
+
+ public void testIsValidBoolean() throws Exception {
+ AttributeInfo info = new AttributeInfo("test", Format.BOOLEAN_SET);
+ assertTrue(info.isValid("true", null, null));
+ assertTrue(info.isValid("false", null, null));
+ assertFalse(info.isValid("", null, null));
+ assertFalse(info.isValid("TRUE", null, null));
+ }
+
+ public void testIsValidInteger() throws Exception {
+ AttributeInfo info = new AttributeInfo("test", Format.INTEGER_SET);
+ assertTrue(info.isValid("0", null, null));
+ assertTrue(info.isValid("1", null, null));
+ assertTrue(info.isValid("10", null, null));
+ assertTrue(info.isValid("-10", null, null));
+ assertTrue(info.isValid(Integer.toString(Integer.MAX_VALUE), null, null));
+
+ assertFalse(info.isValid("", null, null));
+ assertFalse(info.isValid("a", null, null));
+ assertFalse(info.isValid("a1", null, null));
+ assertFalse(info.isValid("1a", null, null));
+ assertFalse(info.isValid("1.0", null, null));
+ }
+
+ public void testIsValidFloat() throws Exception {
+ AttributeInfo info = new AttributeInfo("test", Format.FLOAT_SET);
+ assertTrue(info.isValid("0", null, null));
+ assertTrue(info.isValid("1", null, null));
+ assertTrue(info.isValid("10", null, null));
+ assertTrue(info.isValid("-10", null, null));
+ assertTrue(info.isValid("-10.1234", null, null));
+ assertTrue(info.isValid(".1", null, null));
+ assertTrue(info.isValid("-.1", null, null));
+ assertTrue(info.isValid("1.5e22", null, null));
+ assertTrue(info.isValid(Integer.toString(Integer.MAX_VALUE), null, null));
+
+ assertFalse(info.isValid("", null, null));
+ assertFalse(info.isValid(".", null, null));
+ assertFalse(info.isValid("-.", null, null));
+ assertFalse(info.isValid("a", null, null));
+ assertFalse(info.isValid("a1", null, null));
+ assertFalse(info.isValid("1a", null, null));
+ }
+
+ public void testIsValidDimension() throws Exception {
+ AttributeInfo info = new AttributeInfo("test", Format.DIMENSION_SET);
+ assertTrue(info.isValid("0dp", null, null));
+ assertTrue(info.isValid("1dp", null, null));
+ assertTrue(info.isValid("10dip", null, null));
+ assertTrue(info.isValid("-10px", null, null));
+ assertTrue(info.isValid("-10.1234mm", null, null));
+ assertTrue(info.isValid("14sp", null, null));
+ assertTrue(info.isValid("72pt", null, null));
+
+ assertFalse(info.isValid("", null, null));
+ assertFalse(info.isValid("5", null, null));
+ assertFalse(info.isValid("50ps", null, null));
+ // Since we allow resources even when not specified in format, don't assert
+ // this:
+ //assertFalse(info.isValid("@dimen/foo"));
+ }
+
+ public void testIsValidColor() throws Exception {
+ AttributeInfo info = new AttributeInfo("test", Format.COLOR_SET);
+ assertTrue(info.isValid("#fff", null, null));
+ assertTrue(info.isValid("#ffffff", null, null));
+ assertTrue(info.isValid("#12345678", null, null));
+ assertTrue(info.isValid("#abcdef00", null, null));
+
+ assertFalse(info.isValid("", null, null));
+ assertFalse(info.isValid("#fffffffff", null, null));
+ assertFalse(info.isValid("red", null, null));
+ assertFalse(info.isValid("rgb(1,2,3)", null, null));
+ }
+
+ public void testIsValidFraction() throws Exception {
+ AttributeInfo info = new AttributeInfo("test", EnumSet.<Format>of(Format.FRACTION));
+ assertTrue(info.isValid("5%", null, null));
+ assertTrue(info.isValid("25%p", null, null));
+
+ // We don't validate fractions accurately yet
+ //assertFalse(info.isValid(""));
+ //assertFalse(info.isValid("50%%"));
+ //assertFalse(info.isValid("50"));
+ //assertFalse(info.isValid("-2%"));
+ }
+
+ public void testIsValidReference() throws Exception {
+ AttributeInfo info = new AttributeInfo("test", Format.REFERENCE_SET);
+ assertTrue(info.isValid("@android:string/foo", null, null));
+ assertTrue(info.isValid("@string/foo", null, null));
+ assertTrue(info.isValid("@dimen/foo", null, null));
+ assertTrue(info.isValid("@color/foo", null, null));
+ assertTrue(info.isValid("@animator/foo", null, null));
+ assertTrue(info.isValid("@anim/foo", null, null));
+ assertTrue(info.isValid("?android:attr/textAppearanceMedium", null, null));
+
+ assertFalse(info.isValid("", null, null));
+ assertFalse(info.isValid("foo", null, null));
+ assertFalse(info.isValid("3.4", null, null));
+ }
+
+ public void testIsValidEnum() throws Exception {
+ AttributeInfo info = new AttributeInfo("test", Format.ENUM_SET);
+ info.setEnumValues(new String[] { "wrap_content", "match_parent" });
+ assertTrue(info.isValid("wrap_content", null, null));
+ assertTrue(info.isValid("match_parent", null, null));
+ assertFalse(info.isValid("", null, null));
+ assertFalse(info.isValid("other", null, null));
+ assertFalse(info.isValid("50", null, null));
+ }
+
+ public void testIsValidFlag() throws Exception {
+ AttributeInfo info = new AttributeInfo("test", Format.FLAG_SET);
+ info.setFlagValues(new String[] { "left", "top", "right", "bottom" });
+ assertTrue(info.isValid("left", null, null));
+ assertTrue(info.isValid("top", null, null));
+ assertTrue(info.isValid("left|top", null, null));
+
+ assertFalse(info.isValid("", null, null));
+ assertFalse(info.isValid("other", null, null));
+ assertFalse(info.isValid("50", null, null));
+ }
+
+ public void testCombined1() throws Exception {
+ AttributeInfo info = new AttributeInfo("test", EnumSet.<Format>of(Format.INTEGER,
+ Format.REFERENCE));
+ assertTrue(info.isValid("1", null, null));
+ assertTrue(info.isValid("@dimen/foo", null, null));
+ assertFalse(info.isValid("foo", null, null));
+ }
+
+ public void testCombined2() throws Exception {
+ AttributeInfo info = new AttributeInfo("test", EnumSet.<Format>of(Format.COLOR,
+ Format.REFERENCE));
+ assertTrue(info.isValid("#ff00ff00", null, null));
+ assertTrue(info.isValid("@color/foo", null, null));
+ assertFalse(info.isValid("foo", null, null));
+ }
+
+ public void testCombined3() throws Exception {
+ AttributeInfo info = new AttributeInfo("test", EnumSet.<Format>of(Format.STRING,
+ Format.REFERENCE));
+ assertTrue(info.isValid("test", null, null));
+ assertTrue(info.isValid("@color/foo", null, null));
+ }
+
+ public void testCombined4() throws Exception {
+ AttributeInfo info = new AttributeInfo("test", EnumSet.<Format>of(Format.ENUM,
+ Format.DIMENSION));
+ info.setEnumValues(new String[] { "wrap_content", "match_parent" });
+ assertTrue(info.isValid("wrap_content", null, null));
+ assertTrue(info.isValid("match_parent", null, null));
+ assertTrue(info.isValid("50dp", null, null));
+ assertFalse(info.isValid("50", null, null));
+ assertFalse(info.isValid("test", null, null));
+ }
+
+ public void testResourcesExist() throws Exception {
+ AttributeInfo info = new AttributeInfo("test", Format.REFERENCE_SET);
+ TestResourceRepository projectResources = new TestResourceRepository(false);
+ projectResources.addResource(ResourceType.STRING, "mystring");
+ projectResources.addResource(ResourceType.DIMEN, "mydimen");
+ TestResourceRepository frameworkResources = new TestResourceRepository(true);
+ frameworkResources.addResource(ResourceType.LAYOUT, "mylayout");
+
+ assertTrue(info.isValid("@string/mystring", null, null));
+ assertTrue(info.isValid("@dimen/mydimen", null, null));
+ assertTrue(info.isValid("@android:layout/mylayout", null, null));
+ assertTrue(info.isValid("?android:attr/listPreferredItemHeigh", null, null));
+
+ assertTrue(info.isValid("@string/mystring", projectResources, frameworkResources));
+ assertTrue(info.isValid("@dimen/mydimen", projectResources, frameworkResources));
+ assertTrue(info.isValid("@android:layout/mylayout", projectResources, frameworkResources));
+
+ assertFalse(info.isValid("@android:string/mystring", projectResources,
+ frameworkResources));
+ assertFalse(info.isValid("@android:dimen/mydimen", projectResources, frameworkResources));
+ assertFalse(info.isValid("@layout/mylayout", projectResources, frameworkResources));
+ assertFalse(info.isValid("@layout/foo", projectResources, frameworkResources));
+ assertFalse(info.isValid("@anim/foo", projectResources, frameworkResources));
+ assertFalse(info.isValid("@android:anim/foo", projectResources, frameworkResources));
+ }
+
+ private class TestResourceRepository extends ResourceRepository {
+ private Multimap<ResourceType, String> mResources = ArrayListMultimap.create();
+
+ protected TestResourceRepository(boolean isFrameworkRepository) {
+ super(isFrameworkRepository);
+ }
+
+ void addResource(ResourceType type, String name) {
+ mResources.put(type, name);
+ }
+
+ @Override
+ @NonNull
+ protected ResourceItem createResourceItem(@NonNull String name) {
+ fail("Not used in test");
+ return null;
+ }
+
+ @Override
+ public boolean hasResourceItem(@NonNull ResourceType type, @NonNull String name) {
+ Collection<String> names = mResources.get(type);
+ if (names != null) {
+ return names.contains(name);
+ }
+
+ return false;
+ }
+ };
+
+
+ public void testIsValid() throws Exception {
+ // This test loads the full attrs.xml file and then processes a bunch of platform
+ // resource file and makes sure that they're all considered valid. This helps
+ // make sure that isValid() closely matches what aapt accepts.
+ String sdkPath = System.getenv("ADT_SDK_SOURCE_PATH");
+ assertNotNull("This test requires ADT_SDK_SOURCE_PATH to be set to point to the" +
+ "SDK git repository", sdkPath);
+ File sdk = new File(sdkPath);
+ assertNotNull("$ADT_SDK_SOURCE_PATH (" + sdk.getPath() + ") is not a directory",
+ sdk.isDirectory());
+ File git = sdk.getParentFile();
+ File attrsPath = new File(git, "frameworks" + File.separator + "base"
+ + File.separator + "core" + File.separator + "res" + File.separator + "res"
+ + File.separator + "values" + File.separator + "attrs.xml");
+ assertTrue(attrsPath.getPath(), attrsPath.exists());
+ AttrsXmlParser parser = new AttrsXmlParser(attrsPath.getPath(),
+ new StdLogger(StdLogger.Level.VERBOSE), 1100);
+ parser.preload();
+ Map<String, AttributeInfo> attributeMap = parser.getAttributeMap();
+ assertNotNull(attributeMap);
+ assertNotNull(attributeMap.get("layout_width"));
+ Set<String> seen = Sets.newHashSet();
+
+ checkDir(new File(git, "packages" + File.separator + "apps"), false, attributeMap, seen);
+ }
+
+ private void checkDir(File dir, boolean isResourceDir,
+ Map<String, AttributeInfo> map, Set<String> seen) throws IOException {
+ assertTrue(dir.isDirectory());
+ File[] list = dir.listFiles();
+ if (list != null) {
+ for (File file : list) {
+ if (isResourceDir && file.isFile() && file.getPath().endsWith(DOT_XML)) {
+ checkXmlFile(file, map, seen);
+ } else if (file.isDirectory()) {
+ checkDir(file, isResourceDir || file.getName().equals("res"), map, seen);
+ }
+ }
+ }
+ }
+
+ private void checkXmlFile(File file, Map<String, AttributeInfo> map,
+ Set<String> seen) throws IOException {
+ String xml = Files.toString(file, Charsets.UTF_8);
+ if (xml != null) {
+ //Document doc = DomUtilities.parseStructuredDocument(xml);
+ Document doc = DomUtilities.parseDocument(xml, false);
+ if (doc != null && doc.getDocumentElement() != null) {
+ checkElement(file, doc.getDocumentElement(), map, seen);
+ }
+ }
+ }
+
+ private void checkElement(File file, Element element, Map<String, AttributeInfo> map,
+ Set<String> seen) {
+ NamedNodeMap attributes = element.getAttributes();
+ for (int i = 0, n = attributes.getLength(); i < n; i++) {
+ Attr attribute = (Attr) attributes.item(i);
+
+ String uri = attribute.getNamespaceURI();
+ String name = attribute.getLocalName();
+ String value = attribute.getValue();
+ if (ANDROID_URI.equals(uri)) {
+ AttributeInfo info = map.get(name);
+ if (info == null) {
+ System.out.println("Warning: Unknown attribute '" + name + "' in " + file);
+ return;
+ }
+ if (!info.isValid(value, null, null)) {
+ if (name.equals("duration") || name.equals("exitFadeDuration")) {
+ // Already known
+ return;
+ }
+ String message = "In file " +
+ file.getPath() + ":\nCould not validate value \"" + value
+ + "\" for attribute '"
+ + name + "' where the attribute info has formats " + info.getFormats()
+ + "\n";
+ System.out.println("\n" + message);
+ fail(message);
+ }
+ if ((value.startsWith("@") || value.startsWith("?")) &&
+ !info.getFormats().contains(Format.REFERENCE)) {
+ // Print out errors in attrs.xml
+
+ if (!seen.contains(name)) {
+ seen.add(name);
+ System.out.println("\"" + name + "\" with formats " + info.getFormats()
+ + " was passed a reference (" + value + ") in file " + file);
+ }
+ }
+ }
+ }
+ }
+}
diff --git a/eclipse/plugins/com.android.ide.eclipse.tests/unittests/com/android/ide/common/resources/platform/AttrsXmlParserManifestTest.java b/eclipse/plugins/com.android.ide.eclipse.tests/unittests/com/android/ide/common/resources/platform/AttrsXmlParserManifestTest.java
index cddd63e..82d6aca 100755
--- a/eclipse/plugins/com.android.ide.eclipse.tests/unittests/com/android/ide/common/resources/platform/AttrsXmlParserManifestTest.java
+++ b/eclipse/plugins/com.android.ide.eclipse.tests/unittests/com/android/ide/common/resources/platform/AttrsXmlParserManifestTest.java
@@ -35,8 +35,8 @@ public class AttrsXmlParserManifestTest extends TestCase {
@Override
public void setUp() throws Exception {
- mFilePath = AdtTestData.getInstance().getTestFilePath(MOCK_DATA_PATH); //$NON-NLS-1$
- mParser = new AttrsXmlParser(mFilePath, new TestLogger());
+ mFilePath = AdtTestData.getInstance().getTestFilePath(MOCK_DATA_PATH);
+ mParser = new AttrsXmlParser(mFilePath, new TestLogger(), 100);
}
@Override
diff --git a/eclipse/plugins/com.android.ide.eclipse.tests/unittests/com/android/ide/common/resources/platform/AttrsXmlParserTest.java b/eclipse/plugins/com.android.ide.eclipse.tests/unittests/com/android/ide/common/resources/platform/AttrsXmlParserTest.java
index e57e5cd..883577b 100644
--- a/eclipse/plugins/com.android.ide.eclipse.tests/unittests/com/android/ide/common/resources/platform/AttrsXmlParserTest.java
+++ b/eclipse/plugins/com.android.ide.eclipse.tests/unittests/com/android/ide/common/resources/platform/AttrsXmlParserTest.java
@@ -36,7 +36,7 @@ public class AttrsXmlParserTest extends TestCase {
@Override
public void setUp() throws Exception {
mFilePath = AdtTestData.getInstance().getTestFilePath(MOCK_DATA_PATH);
- mParser = new AttrsXmlParser(mFilePath, new TestLogger());
+ mParser = new AttrsXmlParser(mFilePath, new TestLogger(), 100);
}
@Override
diff --git a/eclipse/plugins/com.android.ide.eclipse.tests/unittests/com/android/ide/eclipse/adt/AdtUtilsTest.java b/eclipse/plugins/com.android.ide.eclipse.tests/unittests/com/android/ide/eclipse/adt/AdtUtilsTest.java
index 0d4e02e..c1e94ac 100644
--- a/eclipse/plugins/com.android.ide.eclipse.tests/unittests/com/android/ide/eclipse/adt/AdtUtilsTest.java
+++ b/eclipse/plugins/com.android.ide.eclipse.tests/unittests/com/android/ide/eclipse/adt/AdtUtilsTest.java
@@ -15,75 +15,12 @@
*/
package com.android.ide.eclipse.adt;
-import java.util.Locale;
-
import junit.framework.TestCase;
+import java.util.Locale;
+
@SuppressWarnings("javadoc")
public class AdtUtilsTest extends TestCase {
- public void testEndsWithIgnoreCase() {
- assertTrue(AdtUtils.endsWithIgnoreCase("foo", "foo"));
- assertTrue(AdtUtils.endsWithIgnoreCase("foo", "Foo"));
- assertTrue(AdtUtils.endsWithIgnoreCase("foo", "foo"));
- assertTrue(AdtUtils.endsWithIgnoreCase("Barfoo", "foo"));
- assertTrue(AdtUtils.endsWithIgnoreCase("BarFoo", "foo"));
- assertTrue(AdtUtils.endsWithIgnoreCase("BarFoo", "foO"));
-
- assertFalse(AdtUtils.endsWithIgnoreCase("foob", "foo"));
- assertFalse(AdtUtils.endsWithIgnoreCase("foo", "fo"));
- }
-
- public void testStartsWithIgnoreCase() {
- assertTrue(AdtUtils.startsWithIgnoreCase("foo", "foo"));
- assertTrue(AdtUtils.startsWithIgnoreCase("foo", "Foo"));
- assertTrue(AdtUtils.startsWithIgnoreCase("foo", "foo"));
- assertTrue(AdtUtils.startsWithIgnoreCase("barfoo", "bar"));
- assertTrue(AdtUtils.startsWithIgnoreCase("BarFoo", "bar"));
- assertTrue(AdtUtils.startsWithIgnoreCase("BarFoo", "bAr"));
-
- assertFalse(AdtUtils.startsWithIgnoreCase("bfoo", "foo"));
- assertFalse(AdtUtils.startsWithIgnoreCase("fo", "foo"));
- }
-
- public void testStartsWith() {
- assertTrue(AdtUtils.startsWith("foo", 0, "foo"));
- assertTrue(AdtUtils.startsWith("foo", 0, "Foo"));
- assertTrue(AdtUtils.startsWith("Foo", 0, "foo"));
- assertTrue(AdtUtils.startsWith("aFoo", 1, "foo"));
-
- assertFalse(AdtUtils.startsWith("aFoo", 0, "foo"));
- assertFalse(AdtUtils.startsWith("aFoo", 2, "foo"));
- }
-
- public void testEndsWith() {
- assertTrue(AdtUtils.endsWith("foo", "foo"));
- assertTrue(AdtUtils.endsWith("foobar", "obar"));
- assertTrue(AdtUtils.endsWith("foobar", "bar"));
- assertTrue(AdtUtils.endsWith("foobar", "ar"));
- assertTrue(AdtUtils.endsWith("foobar", "r"));
- assertTrue(AdtUtils.endsWith("foobar", ""));
-
- assertTrue(AdtUtils.endsWith(new StringBuilder("foobar"), "bar"));
- assertTrue(AdtUtils.endsWith(new StringBuilder("foobar"), new StringBuffer("obar")));
- assertTrue(AdtUtils.endsWith("foobar", new StringBuffer("obar")));
-
- assertFalse(AdtUtils.endsWith("foo", "fo"));
- assertFalse(AdtUtils.endsWith("foobar", "Bar"));
- assertFalse(AdtUtils.endsWith("foobar", "longfoobar"));
- }
-
- public void testEndsWith2() {
- assertTrue(AdtUtils.endsWith("foo", "foo".length(), "foo"));
- assertTrue(AdtUtils.endsWith("foo", "fo".length(), "fo"));
- assertTrue(AdtUtils.endsWith("foo", "f".length(), "f"));
- }
-
- public void testStripWhitespace() {
- assertEquals("foo", AdtUtils.stripWhitespace("foo"));
- assertEquals("foobar", AdtUtils.stripWhitespace("foo bar"));
- assertEquals("foobar", AdtUtils.stripWhitespace(" foo bar \n\t"));
- }
-
public void testExtractClassName() {
assertEquals("Foo", AdtUtils.extractClassName("foo"));
assertEquals("Foobar", AdtUtils.extractClassName("foo bar"));
diff --git a/eclipse/plugins/com.android.ide.eclipse.tests/unittests/com/android/ide/eclipse/adt/internal/resources/manager/IdeScanningContextTest.java b/eclipse/plugins/com.android.ide.eclipse.tests/unittests/com/android/ide/eclipse/adt/internal/resources/manager/IdeScanningContextTest.java
new file mode 100644
index 0000000..3104c85
--- /dev/null
+++ b/eclipse/plugins/com.android.ide.eclipse.tests/unittests/com/android/ide/eclipse/adt/internal/resources/manager/IdeScanningContextTest.java
@@ -0,0 +1,22 @@
+/*
+ * Copyright (C) 2012 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.resources.manager;
+
+import junit.framework.TestCase;
+
+@SuppressWarnings("javadoc")
+public class IdeScanningContextTest extends TestCase {
+}
diff --git a/eclipse/plugins/com.android.ide.eclipse.tests/unittests/com/android/ide/eclipse/adt/internal/sdk/LayoutParamsParserTest.java b/eclipse/plugins/com.android.ide.eclipse.tests/unittests/com/android/ide/eclipse/adt/internal/sdk/LayoutParamsParserTest.java
index 9d87e2f..c89dd06 100644
--- a/eclipse/plugins/com.android.ide.eclipse.tests/unittests/com/android/ide/eclipse/adt/internal/sdk/LayoutParamsParserTest.java
+++ b/eclipse/plugins/com.android.ide.eclipse.tests/unittests/com/android/ide/eclipse/adt/internal/sdk/LayoutParamsParserTest.java
@@ -62,7 +62,7 @@ public class LayoutParamsParserTest extends TestCase {
super(new MockFrameworkClassLoader(),
new AttrsXmlParser(
AdtTestData.getInstance().getTestFilePath(MOCK_DATA_PATH),
- new TestLogger()).preload());
+ new TestLogger(), 100).preload());
mTopViewClass = new ClassWrapper(mock_android.view.View.class);
mTopGroupClass = new ClassWrapper(mock_android.view.ViewGroup.class);
diff --git a/eclipse/plugins/com.android.ide.eclipse.tests/unittests/com/android/ide/eclipse/adt/internal/wizards/templates/FmEscapeXmlAttributeMethodTest.java b/eclipse/plugins/com.android.ide.eclipse.tests/unittests/com/android/ide/eclipse/adt/internal/wizards/templates/FmEscapeXmlAttributeMethodTest.java
new file mode 100644
index 0000000..eb1e949
--- /dev/null
+++ b/eclipse/plugins/com.android.ide.eclipse.tests/unittests/com/android/ide/eclipse/adt/internal/wizards/templates/FmEscapeXmlAttributeMethodTest.java
@@ -0,0 +1,50 @@
+/*
+ * Copyright (C) 2012 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.wizards.templates;
+
+import freemarker.template.SimpleScalar;
+import freemarker.template.TemplateModelException;
+
+import java.util.Collections;
+import java.util.List;
+
+import junit.framework.TestCase;
+
+@SuppressWarnings("javadoc")
+public class FmEscapeXmlAttributeMethodTest extends TestCase {
+ @SuppressWarnings("rawtypes")
+ private void check(String s, String expected) throws TemplateModelException {
+ FmEscapeXmlAttributeMethod method = new FmEscapeXmlAttributeMethod();
+ List list = Collections.singletonList(new SimpleScalar(s));
+ assertEquals(expected, ((SimpleScalar) method.exec(list)).getAsString());
+ }
+
+ public void test1() throws Exception {
+ check("", "");
+ }
+
+ public void test2() throws Exception {
+ check("foo", "foo");
+ }
+
+ public void test3() throws Exception {
+ check("<\"'>&", "&lt;&quot;&apos;>&amp;");
+ }
+
+ public void test4() throws Exception {
+ check("foo>bar", "foo>bar");
+ }
+}
diff --git a/eclipse/plugins/com.android.ide.eclipse.tests/unittests/com/android/ide/eclipse/adt/internal/wizards/templates/FmEscapeXmlStringMethodTest.java b/eclipse/plugins/com.android.ide.eclipse.tests/unittests/com/android/ide/eclipse/adt/internal/wizards/templates/FmEscapeXmlStringMethodTest.java
new file mode 100644
index 0000000..1a4289a
--- /dev/null
+++ b/eclipse/plugins/com.android.ide.eclipse.tests/unittests/com/android/ide/eclipse/adt/internal/wizards/templates/FmEscapeXmlStringMethodTest.java
@@ -0,0 +1,66 @@
+/*
+ * Copyright (C) 2012 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.wizards.templates;
+
+import freemarker.template.SimpleScalar;
+import freemarker.template.TemplateModelException;
+
+import java.util.Collections;
+import java.util.List;
+
+import junit.framework.TestCase;
+
+@SuppressWarnings("javadoc")
+public class FmEscapeXmlStringMethodTest extends TestCase {
+ @SuppressWarnings("rawtypes")
+ private void check(String s, String expected) throws TemplateModelException {
+ FmEscapeXmlStringMethod method = new FmEscapeXmlStringMethod();
+ List list = Collections.singletonList(new SimpleScalar(s));
+ assertEquals(expected, ((SimpleScalar) method.exec(list)).getAsString());
+ }
+
+ public void test1() throws Exception {
+ check("", "");
+ }
+
+ public void test2() throws Exception {
+ check("foo", "foo");
+ }
+
+ public void test3() throws Exception {
+ check(" Foo Bar ", "\" Foo Bar \"");
+ }
+
+ public void test4() throws Exception {
+ check("@foo", "\\@foo");
+ }
+
+ public void test5() throws Exception {
+ check("Hello\nWorld", "Hello\\nWorld");
+ }
+
+ public void test6() throws Exception {
+ check("A & B", "A &amp; B");
+ }
+
+ public void test7() throws Exception {
+ check("Foo's Bar", "Foo\\'s Bar");
+ }
+
+ public void test8() throws Exception {
+ check("'\"\\", "\\'\\\"\\\\");
+ }
+}
diff --git a/eclipse/plugins/com.android.ide.eclipse.tests/unittests/com/android/ide/eclipse/adt/internal/wizards/templates/FmEscapeXmlTextMethodTest.java b/eclipse/plugins/com.android.ide.eclipse.tests/unittests/com/android/ide/eclipse/adt/internal/wizards/templates/FmEscapeXmlTextMethodTest.java
new file mode 100644
index 0000000..c08b834
--- /dev/null
+++ b/eclipse/plugins/com.android.ide.eclipse.tests/unittests/com/android/ide/eclipse/adt/internal/wizards/templates/FmEscapeXmlTextMethodTest.java
@@ -0,0 +1,46 @@
+/*
+ * Copyright (C) 2012 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.wizards.templates;
+
+import freemarker.template.SimpleScalar;
+import freemarker.template.TemplateModelException;
+
+import java.util.Collections;
+import java.util.List;
+
+import junit.framework.TestCase;
+
+@SuppressWarnings("javadoc")
+public class FmEscapeXmlTextMethodTest extends TestCase {
+ @SuppressWarnings("rawtypes")
+ private void check(String s, String expected) throws TemplateModelException {
+ FmEscapeXmlTextMethod method = new FmEscapeXmlTextMethod();
+ List list = Collections.singletonList(new SimpleScalar(s));
+ assertEquals(expected, ((SimpleScalar) method.exec(list)).getAsString());
+ }
+
+ public void test1() throws Exception {
+ check("", "");
+ }
+
+ public void test2() throws Exception {
+ check("foo", "foo");
+ }
+
+ public void test3() throws Exception {
+ check("<\"'>&", "&lt;\"'>&amp;");
+ }
+}
diff --git a/eclipse/plugins/com.android.ide.eclipse.tests/unittests/com/android/ide/eclipse/adt/internal/wizards/templates/FmExtractLettersMethodTest.java b/eclipse/plugins/com.android.ide.eclipse.tests/unittests/com/android/ide/eclipse/adt/internal/wizards/templates/FmExtractLettersMethodTest.java
new file mode 100644
index 0000000..b1d3cee
--- /dev/null
+++ b/eclipse/plugins/com.android.ide.eclipse.tests/unittests/com/android/ide/eclipse/adt/internal/wizards/templates/FmExtractLettersMethodTest.java
@@ -0,0 +1,46 @@
+/*
+ * Copyright (C) 2012 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.wizards.templates;
+
+import freemarker.template.SimpleScalar;
+import freemarker.template.TemplateModelException;
+
+import java.util.Collections;
+import java.util.List;
+
+import junit.framework.TestCase;
+
+@SuppressWarnings("javadoc")
+public class FmExtractLettersMethodTest extends TestCase {
+ @SuppressWarnings("rawtypes")
+ private void check(String s, String expected) throws TemplateModelException {
+ FmExtractLettersMethod method = new FmExtractLettersMethod();
+ List list = Collections.singletonList(new SimpleScalar(s));
+ assertEquals(expected, ((SimpleScalar) method.exec(list)).getAsString());
+ }
+
+ public void test1() throws Exception {
+ check("", "");
+ }
+
+ public void test2() throws Exception {
+ check("foo", "foo");
+ }
+
+ public void test3() throws Exception {
+ check("<\"'>&foo ", "foo");
+ }
+}
diff --git a/files/ant/build.xml b/files/ant/build.xml
index a1e9daa..4861c1d 100644
--- a/files/ant/build.xml
+++ b/files/ant/build.xml
@@ -758,7 +758,7 @@
<jar destfile="${out.library.jar.file}">
<fileset dir="${out.classes.absolute.dir}"
includes="**/*.class"
- excludes="${project.app.package.path}/R.class ${project.app.package.path}/R$*.class ${project.app.package.path}/Manifest.class ${project.app.package.path}/Manifest$*.class ${project.app.package.path}/BuildConfig.class"/>
+ excludes="${project.app.package.path}/R.class ${project.app.package.path}/R$*.class ${project.app.package.path}/BuildConfig.class"/>
<fileset dir="${source.absolute.dir}" excludes="**/*.java ${android.package.excludes}" />
</jar>
</then>
diff --git a/find_java/find_java_exe.cpp b/find_java/find_java_exe.cpp
index c8e0e49..6b1add9 100644
--- a/find_java/find_java_exe.cpp
+++ b/find_java/find_java_exe.cpp
@@ -117,12 +117,15 @@ int main(int argc, char* argv[]) {
_ASSERT(!javaPath.isEmpty());
if (doShortPath) {
+ PVOID oldWow64Value = disableWow64FsRedirection();
if (!javaPath.toShortPath(&javaPath)) {
+ revertWow64FsRedirection(&oldWow64Value);
fprintf(stderr,
"Failed to convert path to a short DOS path: %s\n",
javaPath.cstr());
return 1;
}
+ revertWow64FsRedirection(&oldWow64Value);
}
if (doVersion) {
diff --git a/sdk_common/src/com/android/ide/common/resources/IdResourceParser.java b/sdk_common/src/com/android/ide/common/resources/IdResourceParser.java
index 1de664e..66a72ce 100644
--- a/sdk_common/src/com/android/ide/common/resources/IdResourceParser.java
+++ b/sdk_common/src/com/android/ide/common/resources/IdResourceParser.java
@@ -16,6 +16,7 @@
package com.android.ide.common.resources;
+import com.android.annotations.NonNull;
import com.android.ide.common.rendering.api.ResourceValue;
import com.android.ide.common.resources.ValueResourceParser.IValueResourceRepository;
import com.android.resources.ResourceType;
@@ -48,7 +49,9 @@ public class IdResourceParser {
* as a place to stash errors encountered
* @param isFramework true if scanning a framework resource
*/
- public IdResourceParser(IValueResourceRepository repository, ScanningContext context,
+ public IdResourceParser(
+ @NonNull IValueResourceRepository repository,
+ @NonNull ScanningContext context,
boolean isFramework) {
mRepository = repository;
mContext = context;
@@ -107,7 +110,6 @@ public class IdResourceParser {
private boolean parse(ResourceType type, String path, KXmlParser parser)
throws XmlPullParserException, IOException {
boolean valid = true;
- ResourceRepository resources = mContext.getRepository();
boolean checkForErrors = !mIsFramework && !mContext.needsFullAapt();
while (true) {
@@ -118,33 +120,22 @@ public class IdResourceParser {
String value = parser.getAttributeValue(i);
assert value != null : attribute;
- if (value.startsWith("@")) { //$NON-NLS-1$
- // Gather IDs
- if (value.startsWith("@+")) { //$NON-NLS-1$
- // Strip out the @+id/ or @+android:id/ section
- String id = value.substring(value.indexOf('/') + 1);
- ResourceValue newId = new ResourceValue(ResourceType.ID, id,
- mIsFramework);
- mRepository.addResourceValue(newId);
- } else if (checkForErrors){
- // Validate resource references (unless we're scanning a framework
- // resource or if we've already scheduled a full aapt run)
- boolean exists = resources.hasResourceItem(value);
- if (!exists && !mRepository.hasResourceValue(ResourceType.ID,
- value.substring(value.indexOf('/') + 1))) {
- String error = String.format(
- // Don't localize because the exact pattern matches AAPT's
- // output which has hardcoded regexp matching in
- // AaptParser.
- "%1$s:%2$d: Error: No resource found that matches " + //$NON-NLS-1$
- "the given name (at '%3$s' with value '%4$s')", //$NON-NLS-1$
- path, parser.getLineNumber(),
- attribute, value);
- mContext.addError(error);
- valid = false;
- }
+ if (checkForErrors) {
+ String uri = parser.getAttributeNamespace(i);
+ if (!mContext.checkValue(uri, attribute, value)) {
+ mContext.requestFullAapt();
+ checkForErrors = false;
+ valid = false;
}
}
+
+ if (value.startsWith("@+")) { //$NON-NLS-1$
+ // Strip out the @+id/ or @+android:id/ section
+ String id = value.substring(value.indexOf('/') + 1);
+ ResourceValue newId = new ResourceValue(ResourceType.ID, id,
+ mIsFramework);
+ mRepository.addResourceValue(newId);
+ }
}
} else if (event == XmlPullParser.END_DOCUMENT) {
break;
diff --git a/sdk_common/src/com/android/ide/common/resources/ResourceRepository.java b/sdk_common/src/com/android/ide/common/resources/ResourceRepository.java
index ac0614d..4f50f63 100755
--- a/sdk_common/src/com/android/ide/common/resources/ResourceRepository.java
+++ b/sdk_common/src/com/android/ide/common/resources/ResourceRepository.java
@@ -171,7 +171,7 @@ public abstract class ResourceRepository {
* @return true if the resource is known
*/
public boolean hasResourceItem(@NonNull String url) {
- assert url.startsWith("@") : url;
+ assert url.startsWith("@") || url.startsWith("?") : url;
int typeEnd = url.indexOf('/', 1);
if (typeEnd != -1) {
diff --git a/sdk_common/src/com/android/ide/common/resources/ScanningContext.java b/sdk_common/src/com/android/ide/common/resources/ScanningContext.java
index e4ed275..43561e8 100644
--- a/sdk_common/src/com/android/ide/common/resources/ScanningContext.java
+++ b/sdk_common/src/com/android/ide/common/resources/ScanningContext.java
@@ -15,6 +15,9 @@
*/
package com.android.ide.common.resources;
+import com.android.annotations.NonNull;
+import com.android.annotations.Nullable;
+
import java.util.ArrayList;
import java.util.List;
@@ -24,7 +27,7 @@ import java.util.List;
* so on.
*/
public class ScanningContext {
- private final ResourceRepository mRepository;
+ protected final ResourceRepository mRepository;
private boolean mNeedsFullAapt;
private List<String> mErrors = null;
@@ -33,7 +36,7 @@ public class ScanningContext {
*
* @param repository the associated resource repository
*/
- public ScanningContext(ResourceRepository repository) {
+ public ScanningContext(@NonNull ResourceRepository repository) {
super();
mRepository = repository;
}
@@ -43,6 +46,7 @@ public class ScanningContext {
*
* @return a list of errors encountered during scanning (or null)
*/
+ @Nullable
public List<String> getErrors() {
return mErrors;
}
@@ -55,7 +59,7 @@ public class ScanningContext {
* @param error the error message, including file name and line number at
* the beginning
*/
- public void addError(String error) {
+ public void addError(@NonNull String error) {
if (mErrors == null) {
mErrors = new ArrayList<String>();
}
@@ -67,6 +71,7 @@ public class ScanningContext {
*
* @return the associated repository, never null
*/
+ @NonNull
public ResourceRepository getRepository() {
return mRepository;
}
@@ -89,4 +94,17 @@ public class ScanningContext {
public boolean needsFullAapt() {
return mNeedsFullAapt;
}
+
+ /**
+ * Asks the context to check whether the given attribute name and value is valid
+ * in this context.
+ *
+ * @param uri the XML namespace URI
+ * @param name the attribute local name
+ * @param value the attribute value
+ * @return true if the attribute is valid
+ */
+ public boolean checkValue(@Nullable String uri, @NonNull String name, @NonNull String value) {
+ return true;
+ }
}
diff --git a/sdk_common/src/com/android/ide/common/resources/SingleResourceFile.java b/sdk_common/src/com/android/ide/common/resources/SingleResourceFile.java
index 6b663e9..eb643f6 100644
--- a/sdk_common/src/com/android/ide/common/resources/SingleResourceFile.java
+++ b/sdk_common/src/com/android/ide/common/resources/SingleResourceFile.java
@@ -88,6 +88,10 @@ public class SingleResourceFile extends ResourceFile {
protected void update(ScanningContext context) {
// when this happens, nothing needs to be done since the file only generates
// a single resources that doesn't actually change (its content is the file path)
+
+ // However, we should check for newly introduced errors
+ // Parse the file and look for @+id/ entries
+ validateAttributes(context);
}
@Override
@@ -139,4 +143,26 @@ public class SingleResourceFile extends ResourceFile {
return name;
}
+
+ /**
+ * Validates the associated resource file to make sure the attribute references are valid
+ *
+ * @return true if parsing succeeds and false if it fails
+ */
+ private boolean validateAttributes(ScanningContext context) {
+ // We only need to check if it's a non-framework file
+ if (!isFramework()) {
+ ValidatingResourceParser parser = new ValidatingResourceParser(context, false);
+ try {
+ IAbstractFile file = getFile();
+ return parser.parse(file.getOsLocation(), file.getContents());
+ } catch (Exception e) {
+ context.needsFullAapt();
+ }
+
+ return false;
+ }
+
+ return true;
+ }
}
diff --git a/sdk_common/src/com/android/ide/common/resources/ValidatingResourceParser.java b/sdk_common/src/com/android/ide/common/resources/ValidatingResourceParser.java
new file mode 100644
index 0000000..c1e45a8
--- /dev/null
+++ b/sdk_common/src/com/android/ide/common/resources/ValidatingResourceParser.java
@@ -0,0 +1,136 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.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.apache.org/licenses/LICENSE-2.0
+ *
+ * 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.common.resources;
+
+import com.android.annotations.NonNull;
+
+import org.kxml2.io.KXmlParser;
+import org.xmlpull.v1.XmlPullParser;
+import org.xmlpull.v1.XmlPullParserException;
+
+import java.io.BufferedInputStream;
+import java.io.FileInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+
+/**
+ * Parser for scanning an XML resource file and validating all framework
+ * attribute references in it. If an error is found, the associated context
+ * is marked as needing a full AAPT run.
+ */
+public class ValidatingResourceParser {
+ private final boolean mIsFramework;
+ private ScanningContext mContext;
+
+ /**
+ * Creates a new {@link ValidatingResourceParser}
+ *
+ * @param context a context object with state for the current update, such
+ * as a place to stash errors encountered
+ * @param isFramework true if scanning a framework resource
+ */
+ public ValidatingResourceParser(
+ @NonNull ScanningContext context,
+ boolean isFramework) {
+ mContext = context;
+ mIsFramework = isFramework;
+ }
+
+ /**
+ * Parse the given input and return false if it contains errors, <b>or</b> if
+ * the context is already tagged as needing a full aapt run.
+ *
+ * @param path the full OS path to the file being parsed
+ * @param input the input stream of the XML to be parsed
+ * @return true if parsing succeeds and false if it fails
+ * @throws IOException if reading the contents fails
+ */
+ public boolean parse(final String path, InputStream input)
+ throws IOException {
+ // No need to validate framework files
+ if (mIsFramework) {
+ return true;
+ }
+ if (mContext.needsFullAapt()) {
+ return false;
+ }
+
+ KXmlParser parser = new KXmlParser();
+ try {
+ parser.setFeature(XmlPullParser.FEATURE_PROCESS_NAMESPACES, true);
+
+ if (input instanceof FileInputStream) {
+ input = new BufferedInputStream(input);
+ }
+ parser.setInput(input, "UTF-8"); //$NON-NLS-1$
+
+ return parse(path, parser);
+ } catch (XmlPullParserException e) {
+ String message = e.getMessage();
+
+ // Strip off position description
+ int index = message.indexOf("(position:"); //$NON-NLS-1$ (Hardcoded in KXml)
+ if (index != -1) {
+ message = message.substring(0, index);
+ }
+
+ String error = String.format("%1$s:%2$d: Error: %3$s", //$NON-NLS-1$
+ path, parser.getLineNumber(), message);
+ mContext.addError(error);
+ return false;
+ } catch (RuntimeException e) {
+ // Some exceptions are thrown by the KXmlParser that are not XmlPullParserExceptions,
+ // such as this one:
+ // java.lang.RuntimeException: Undefined Prefix: w in org.kxml2.io.KXmlParser@...
+ // at org.kxml2.io.KXmlParser.adjustNsp(Unknown Source)
+ // at org.kxml2.io.KXmlParser.parseStartTag(Unknown Source)
+ String message = e.getMessage();
+ String error = String.format("%1$s:%2$d: Error: %3$s", //$NON-NLS-1$
+ path, parser.getLineNumber(), message);
+ mContext.addError(error);
+ return false;
+ }
+ }
+
+ private boolean parse(String path, KXmlParser parser)
+ throws XmlPullParserException, IOException {
+ boolean checkForErrors = !mIsFramework && !mContext.needsFullAapt();
+
+ while (true) {
+ int event = parser.next();
+ if (event == XmlPullParser.START_TAG) {
+ for (int i = 0, n = parser.getAttributeCount(); i < n; i++) {
+ String attribute = parser.getAttributeName(i);
+ String value = parser.getAttributeValue(i);
+ assert value != null : attribute;
+
+ if (checkForErrors) {
+ String uri = parser.getAttributeNamespace(i);
+ if (!mContext.checkValue(uri, attribute, value)) {
+ mContext.requestFullAapt();
+ return false;
+ }
+ }
+ }
+ } else if (event == XmlPullParser.END_DOCUMENT) {
+ break;
+ }
+ }
+
+ return true;
+ }
+}
diff --git a/templates/activities/BlankActivity/root/res/values/strings.xml.ftl b/templates/activities/BlankActivity/root/res/values/strings.xml.ftl
index 6c636d6..4ba950a 100644
--- a/templates/activities/BlankActivity/root/res/values/strings.xml.ftl
+++ b/templates/activities/BlankActivity/root/res/values/strings.xml.ftl
@@ -1,6 +1,6 @@
<resources>
<#if !isNewProject>
- <string name="title_${activityToLayout(activityClass)}">${activityTitle}</string>
+ <string name="title_${activityToLayout(activityClass)}">${escapeXmlString(activityTitle)}</string>
</#if>
<string name="menu_settings">Settings</string>
diff --git a/templates/activities/FullscreenActivity/root/res/values/strings.xml.ftl b/templates/activities/FullscreenActivity/root/res/values/strings.xml.ftl
index 53ff7df..5a43acf 100644
--- a/templates/activities/FullscreenActivity/root/res/values/strings.xml.ftl
+++ b/templates/activities/FullscreenActivity/root/res/values/strings.xml.ftl
@@ -1,7 +1,7 @@
<resources>
<#if !isNewProject>
- <string name="title_${simpleName}">${activityTitle}</string>
+ <string name="title_${simpleName}">${escapeXmlString(activityTitle)}</string>
</#if>
<string name="dummy_button1">Button 1</string>
<string name="dummy_button2">Button 2</string>
diff --git a/templates/activities/LoginActivity/root/res/values/strings.xml.ftl b/templates/activities/LoginActivity/root/res/values/strings.xml.ftl
index c2ad046..18bf85f 100644
--- a/templates/activities/LoginActivity/root/res/values/strings.xml.ftl
+++ b/templates/activities/LoginActivity/root/res/values/strings.xml.ftl
@@ -1,6 +1,6 @@
<resources>
<#if !isNewProject>
- <string name="title_${simpleName}">${activityTitle}</string>
+ <string name="title_${simpleName}">${escapeXmlString(activityTitle)}</string>
</#if>
<!-- Strings related to login -->
diff --git a/templates/activities/MasterDetailFlow/globals.xml.ftl b/templates/activities/MasterDetailFlow/globals.xml.ftl
index 519c081..952e278 100644
--- a/templates/activities/MasterDetailFlow/globals.xml.ftl
+++ b/templates/activities/MasterDetailFlow/globals.xml.ftl
@@ -1,8 +1,8 @@
<?xml version="1.0"?>
<globals>
<global id="srcOut" value="src/${slashedPackageName(packageName)}" />
- <global id="CollectionName" value="${objectKind}List" />
- <global id="collection_name" value="${objectKind?lower_case}_list" />
- <global id="DetailName" value="${objectKind}Detail" />
- <global id="detail_name" value="${objectKind?lower_case}_detail" />
+ <global id="CollectionName" value="${extractLetters(objectKind)}List" />
+ <global id="collection_name" value="${extractLetters(objectKind?lower_case)}_list" />
+ <global id="DetailName" value="${extractLetters(objectKind)}Detail" />
+ <global id="detail_name" value="${extractLetters(objectKind?lower_case)}_detail" />
</globals>
diff --git a/templates/activities/MasterDetailFlow/recipe.xml.ftl b/templates/activities/MasterDetailFlow/recipe.xml.ftl
index 2c1f057..8b09c84 100644
--- a/templates/activities/MasterDetailFlow/recipe.xml.ftl
+++ b/templates/activities/MasterDetailFlow/recipe.xml.ftl
@@ -11,7 +11,7 @@
<instantiate from="res/layout/activity_content_list.xml.ftl"
to="res/layout/activity_${collection_name}.xml" />
<instantiate from="res/layout/activity_content_twopane.xml.ftl"
- to="res/layout/activity_${objectKind?lower_case}_twopane.xml" />
+ to="res/layout/activity_${extractLetters(objectKind?lower_case)}_twopane.xml" />
<instantiate from="res/layout/fragment_content_detail.xml.ftl"
to="res/layout/fragment_${detail_name}.xml" />
diff --git a/templates/activities/MasterDetailFlow/root/res/values-large/refs.xml.ftl b/templates/activities/MasterDetailFlow/root/res/values-large/refs.xml.ftl
index 3008e2e..97215c3 100644
--- a/templates/activities/MasterDetailFlow/root/res/values-large/refs.xml.ftl
+++ b/templates/activities/MasterDetailFlow/root/res/values-large/refs.xml.ftl
@@ -6,5 +6,5 @@
For more on layout aliases, see:
http://developer.android.com/training/multiscreen/screensizes.html#TaskUseAliasFilters
-->
- <item type="layout" name="activity_${collection_name}">@layout/activity_${objectKind?lower_case}_twopane</item>
+ <item type="layout" name="activity_${collection_name}">@layout/activity_${extractLetters(objectKind?lower_case)}_twopane</item>
</resources>
diff --git a/templates/activities/MasterDetailFlow/root/res/values-sw600dp/refs.xml.ftl b/templates/activities/MasterDetailFlow/root/res/values-sw600dp/refs.xml.ftl
index c698e6e..d592404 100644
--- a/templates/activities/MasterDetailFlow/root/res/values-sw600dp/refs.xml.ftl
+++ b/templates/activities/MasterDetailFlow/root/res/values-sw600dp/refs.xml.ftl
@@ -7,5 +7,5 @@
For more on layout aliases, see:
http://developer.android.com/training/multiscreen/screensizes.html#TaskUseAliasFilters
-->
- <item type="layout" name="activity_${collection_name}">@layout/activity_${objectKind?lower_case}_twopane</item>
+ <item type="layout" name="activity_${collection_name}">@layout/activity_${extractLetters(objectKind?lower_case)}_twopane</item>
</resources>
diff --git a/templates/activities/MasterDetailFlow/root/res/values/strings.xml.ftl b/templates/activities/MasterDetailFlow/root/res/values/strings.xml.ftl
index 8c555ae..ea882bc 100644
--- a/templates/activities/MasterDetailFlow/root/res/values/strings.xml.ftl
+++ b/templates/activities/MasterDetailFlow/root/res/values/strings.xml.ftl
@@ -1,6 +1,6 @@
<resources>
<#if !isNewProject>
- <string name="title_${collection_name}">${objectKindPlural}</string>
+ <string name="title_${collection_name}">${escapeXmlString(objectKindPlural)}</string>
</#if>
- <string name="title_${detail_name}">${objectKind} Detail</string>
+ <string name="title_${detail_name}">${escapeXmlString(objectKind)} Detail</string>
</resources>
diff --git a/templates/activities/SettingsActivity/root/res/values/strings.xml.ftl b/templates/activities/SettingsActivity/root/res/values/strings.xml.ftl
index bf881a3..8dc52ac 100644
--- a/templates/activities/SettingsActivity/root/res/values/strings.xml.ftl
+++ b/templates/activities/SettingsActivity/root/res/values/strings.xml.ftl
@@ -1,6 +1,6 @@
<resources>
<#if !isNewProject>
- <string name="title_${simpleName}">${activityTitle}</string>
+ <string name="title_${simpleName}">${escapeXmlString(activityTitle)}</string>
</#if>
<!-- Strings related to Settings -->
diff --git a/templates/docs/index.html b/templates/docs/index.html
index 0916157..f8e89eb 100644
--- a/templates/docs/index.html
+++ b/templates/docs/index.html
@@ -471,6 +471,58 @@
<h4>See also</h4>
<p><a href="#toc_underscoretocamelcase"><code>underscoreToCamelCase</code></a></p>
+<h3 data-toctitle="escapeXmlAttribute">string <em>escapeXmlAttribute</em>(string)</h3>
+
+<p>This function escapes a string, such as <code>Android's</code> such that it can be used as an XML attribute value: <code>Android&amp;apos;s</code>. In particular, it will escape ', ", &lt; and &amp;.</p>
+
+<h4>Arguments</h4>
+<dl>
+ <dt><code>str</code></dt>
+ <dd>The string to be escaped.</dd>
+</dl>
+
+<h4>See also</h4>
+<p><a href="#toc_escapexmltext"><code>escapeXmlText</code></a></p>
+<p><a href="#toc_escapexmlstring"><code>escapeXmlString</code></a></p>
+
+<h3 data-toctitle="escapeXmlText">string <em>escapeXmlText</em>(string)</h3>
+
+<p>This function escapes a string, such as <code>A &amp; B's</code> such that it can be used as XML text. This means it will escape &lt; and &gt;, but unlike <a href="#toc_escapexmlattribute"><code>escapeXmlAttribute</code></a> it will <b>not</b> escape ' and ". In the preceeding example, it will escape the string to <code>A &amp;amp; B\s</code>. Note that if you plan to use the XML text as the value for a &lt;string&gt; resource value, you should consider using <a href="#toc_escapexmlstring"><code>escapeXmlString</code></a> instead, since it performs additional escapes necessary for string resources.</p>
+
+<h4>Arguments</h4>
+<dl>
+ <dt><code>str</code></dt>
+ <dd>The string to escape to proper XML text.</dd>
+</dl>
+
+<h4>See also</h4>
+<p><a href="#toc_escapexmlattribute"><code>escapeXmlAttribute</code></a></p>
+<p><a href="#toc_escapexmlstring"><code>escapeXmlString</code></a></p>
+
+<h3 data-toctitle="escapeXmlString">string <em>escapeXmlString</em>(string)</h3>
+
+<p>This function escapes a string, such as <code>A &amp; B's</code> such that it is suitable to be inserted in a string resource file as XML text, such as <code>A &amp;amp; B\s</code>. In addition to escaping XML characters like &lt; and &amp;, it also performs additional Android specific escapes, such as escaping apostrophes with a backslash, and so on.</p>
+
+<h4>Arguments</h4>
+<dl>
+ <dt><code>str</code></dt>
+ <dd>The string, e.g. <code>Activity's Title</code> to escape to a proper resource XML value.</dd>
+</dl>
+
+<h4>See also</h4>
+<p><a href="#toc_escapexmlattribute"><code>escapeXmlAttribute</code></a></p>
+<p><a href="#toc_escapexmltext"><code>escapeXmlText</code></a></p>
+
+<h3 data-toctitle="extractLetters">string <em>extractLetters</em>(string)</h3>
+
+<p>This function extracts all the letters from a string, effectively removing any punctuation and whitespace characters.</p>
+
+<h4>Arguments</h4>
+<dl>
+ <dt><code>str</code></dt>
+ <dd>The string to extract letters from</dd>
+</dl>
+
<h3 data-toctitle="classToResource">string <em>classToResource</em>(string)</h3>
<p>This function converts an Android class name, such as <code>FooActivity</code> or <code>FooFragment</code>, to a corresponding resource-friendly identifier string, such as <code>foo</code>, stripping the 'Activity' or 'Fragment' suffix. Currently stripped suffixes are listed below.</p>
diff --git a/templates/projects/NewAndroidApplication/root/res/values/strings.xml.ftl b/templates/projects/NewAndroidApplication/root/res/values/strings.xml.ftl
index 557e5c2..ee03444 100644
--- a/templates/projects/NewAndroidApplication/root/res/values/strings.xml.ftl
+++ b/templates/projects/NewAndroidApplication/root/res/values/strings.xml.ftl
@@ -1,3 +1,3 @@
<resources>
- <string name="app_name">${appTitle}</string>
+ <string name="app_name">${escapeXmlString(appTitle)}</string>
</resources>