am f6cf387e: Merge "Recursively resolve @string/resource reference in key key spec parsing"

* commit 'f6cf387edc5e70dd578790c2625c50ed00a5dbf0':
  Recursively resolve @string/resource reference in key key spec parsing
diff --git a/java/src/com/android/inputmethod/keyboard/internal/KeySpecParser.java b/java/src/com/android/inputmethod/keyboard/internal/KeySpecParser.java
index adb5f47..e3c5da4 100644
--- a/java/src/com/android/inputmethod/keyboard/internal/KeySpecParser.java
+++ b/java/src/com/android/inputmethod/keyboard/internal/KeySpecParser.java
@@ -43,6 +43,8 @@
 public class KeySpecParser {
     private static final boolean DEBUG = LatinImeLogger.sDBG;
 
+    private static final int MAX_STRING_REFERENCE_INDIRECTION = 10;
+
     // Constants for parsing.
     private static int COMMA = ',';
     private static final char ESCAPE_CHAR = '\\';
@@ -274,35 +276,51 @@
         return resId;
     }
 
-    private static String resolveStringResource(String text, Resources res, int packageNameResId) {
-        final int size = text.length();
-        if (size < PREFIX_STRING.length()) {
-            return text;
-        }
-
-        StringBuilder sb = null;
-        for (int pos = 0; pos < size; pos++) {
-            final char c = text.charAt(pos);
-            if (c == PREFIX_AT && text.startsWith(PREFIX_STRING, pos)) {
-                if (sb == null) {
-                    sb = new StringBuilder(text.substring(0, pos));
-                }
-                final int end = searchResourceNameEnd(text, pos + PREFIX_STRING.length());
-                final String resName = text.substring(pos + 1, end);
-                final int resId = getResourceId(res, resName, packageNameResId);
-                sb.append(res.getString(resId));
-                pos = end - 1;
-            } else if (c == ESCAPE_CHAR) {
-                if (sb != null) {
-                    // Append both escape character and escaped character.
-                    sb.append(text.substring(pos, Math.min(pos + 2, size)));
-                }
-                pos++;
-            } else if (sb != null) {
-                sb.append(c);
+    private static String resolveStringResource(String rawText, Resources res,
+            int packageNameResId) {
+        int level = 0;
+        String text = rawText;
+        StringBuilder sb;
+        do {
+            level++;
+            if (level >= MAX_STRING_REFERENCE_INDIRECTION) {
+                throw new RuntimeException("too many @string/resource indirection: " + text);
             }
-        }
-        return (sb == null) ? text : sb.toString();
+
+            final int size = text.length();
+            if (size < PREFIX_STRING.length()) {
+                return text;
+            }
+
+            sb = null;
+            for (int pos = 0; pos < size; pos++) {
+                final char c = text.charAt(pos);
+                if (c == PREFIX_AT && text.startsWith(PREFIX_STRING, pos)) {
+                    if (sb == null) {
+                        sb = new StringBuilder(text.substring(0, pos));
+                    }
+                    final int end = searchResourceNameEnd(text, pos + PREFIX_STRING.length());
+                    final String resName = text.substring(pos + 1, end);
+                    final int resId = getResourceId(res, resName, packageNameResId);
+                    sb.append(res.getString(resId));
+                    pos = end - 1;
+                } else if (c == ESCAPE_CHAR) {
+                    if (sb != null) {
+                        // Append both escape character and escaped character.
+                        sb.append(text.substring(pos, Math.min(pos + 2, size)));
+                    }
+                    pos++;
+                } else if (sb != null) {
+                    sb.append(c);
+                }
+            }
+
+            if (sb != null) {
+                text = sb.toString();
+            }
+        } while (sb != null);
+
+        return text;
     }
 
     private static int searchResourceNameEnd(String text, int start) {
diff --git a/tests/res/values/donottranslate.xml b/tests/res/values/donottranslate.xml
index d0cde71..1ca4451 100644
--- a/tests/res/values/donottranslate.xml
+++ b/tests/res/values/donottranslate.xml
@@ -50,4 +50,7 @@
     <string name="multiple_labels_with_escape_surrounded_by_spaces">" \\abc , d\\ef , gh\\i "</string>
     <string name="multiple_labels_with_comma_and_escape">"ab\\\\,d\\\\\\,,g\\,i"</string>
     <string name="multiple_labels_with_comma_and_escape_surrounded_by_spaces">" ab\\\\ , d\\\\\\, , g\\,i "</string>
+    <string name="indirect_string">@string/multiple_chars</string>
+    <string name="indirect_string_with_literal">x,@string/multiple_chars,y</string>
+    <string name="infinite_indirection">infinite,@string/infinite_indirection,loop</string>
 </resources>
diff --git a/tests/src/com/android/inputmethod/keyboard/internal/KeySpecParserCsvTests.java b/tests/src/com/android/inputmethod/keyboard/internal/KeySpecParserCsvTests.java
index a0ce86d..e090031 100644
--- a/tests/src/com/android/inputmethod/keyboard/internal/KeySpecParserCsvTests.java
+++ b/tests/src/com/android/inputmethod/keyboard/internal/KeySpecParserCsvTests.java
@@ -288,4 +288,16 @@
                 "abc@string/multiple_labels",
                 "abcabc", "def", "ghi");
     }
+
+    public void testParseIndirectReference() {
+        assertTextArray("Indirect",
+                "@string/indirect_string", "a", "b", "c");
+        assertTextArray("Indirect with literal",
+                "1,@string/indirect_string_with_literal,2", "1", "x", "a", "b", "c", "y", "2");
+    }
+
+    public void testParseInfiniteIndirectReference() {
+        assertError("Infinite indirection",
+                "1,@string/infinite_indirection,2", "1", "infinite", "<infinite>", "loop", "2");
+    }
 }