am ec83457d: Merge "Support multiple condition for getDeviceOverrideValue"

* commit 'ec83457d726679e81d0b0ec12c9e7a284464133e':
  Support multiple condition for getDeviceOverrideValue
diff --git a/java/res/values/keyboard-heights.xml b/java/res/values/keyboard-heights.xml
index 1c0277c..c651a89 100644
--- a/java/res/values/keyboard-heights.xml
+++ b/java/res/values/keyboard-heights.xml
@@ -34,6 +34,6 @@
         <!-- Xoom -->
         <item>HARDWARE=stingray,283.1337</item>
     <!-- Default value for unknown device: empty string -->
-        <item>DEFAULT,</item>
+        <item>,</item>
     </string-array>
 </resources>
diff --git a/java/res/values/keypress-vibration-durations.xml b/java/res/values/keypress-vibration-durations.xml
index 45c51e7..53aad72 100644
--- a/java/res/values/keypress-vibration-durations.xml
+++ b/java/res/values/keypress-vibration-durations.xml
@@ -29,6 +29,6 @@
         <!-- Nexus 10 -->
         <item>HARDWARE=manta,16</item>
         <!-- Default value for unknown device -->
-        <item>DEFAULT,20</item>
+        <item>,20</item>
     </string-array>
 </resources>
diff --git a/java/res/values/keypress-volumes.xml b/java/res/values/keypress-volumes.xml
index 7061f13..a096c34 100644
--- a/java/res/values/keypress-volumes.xml
+++ b/java/res/values/keypress-volumes.xml
@@ -27,6 +27,6 @@
         <item>HARDWARE=mako,0.3f</item>
         <item>HARDWARE=manta,0.2f</item>
         <!-- Default value for unknown device -->
-        <item>DEFAULT,0.2f</item>
+        <item>,0.2f</item>
     </string-array>
 </resources>
diff --git a/java/res/values/phantom-sudden-move-event-device-list.xml b/java/res/values/phantom-sudden-move-event-device-list.xml
index d0895b1..53002b3 100644
--- a/java/res/values/phantom-sudden-move-event-device-list.xml
+++ b/java/res/values/phantom-sudden-move-event-device-list.xml
@@ -24,6 +24,6 @@
         <!-- Xoom -->
         <item>HARDWARE=stingray,true</item>
         <!-- Default value for unknown device -->
-        <item>DEFAULT,false</item>
+        <item>,false</item>
     </string-array>
 </resources>
diff --git a/java/res/values/sudden-jumping-touch-event-device-list.xml b/java/res/values/sudden-jumping-touch-event-device-list.xml
index 73e30c1..3a9c379 100644
--- a/java/res/values/sudden-jumping-touch-event-device-list.xml
+++ b/java/res/values/sudden-jumping-touch-event-device-list.xml
@@ -26,6 +26,6 @@
         <!-- Droid -->
         <item>HARDWARE=sholes,true</item>
         <!-- Default value for unknown device -->
-        <item>DEFAULT,false</item>
+        <item>,false</item>
     </string-array>
 </resources>
diff --git a/java/src/com/android/inputmethod/latin/ResourceUtils.java b/java/src/com/android/inputmethod/latin/ResourceUtils.java
index f0bfe75..488a0e3 100644
--- a/java/src/com/android/inputmethod/latin/ResourceUtils.java
+++ b/java/src/com/android/inputmethod/latin/ResourceUtils.java
@@ -19,9 +19,13 @@
 import android.content.res.Resources;
 import android.content.res.TypedArray;
 import android.os.Build;
+import android.text.TextUtils;
 import android.util.Log;
 import android.util.TypedValue;
 
+import com.android.inputmethod.annotations.UsedForTesting;
+
+import java.util.ArrayList;
 import java.util.HashMap;
 
 public final class ResourceUtils {
@@ -35,10 +39,31 @@
         // This utility class is not publicly instantiable.
     }
 
-    private static final String DEFAULT_KEY = "DEFAULT";
     private static final HashMap<String, String> sDeviceOverrideValueMap =
             CollectionUtils.newHashMap();
 
+    private static final String[] BUILD_KEYS_AND_VALUES = {
+        "HARDWARE", Build.HARDWARE,
+        "MODEL", Build.MODEL,
+        "MANUFACTURER", Build.MANUFACTURER
+    };
+    private static final HashMap<String, String> sBuildKeyValues;
+    private static final String sBuildKeyValuesDebugString;
+
+    static {
+        sBuildKeyValues = CollectionUtils.newHashMap();
+        final ArrayList<String> keyValuePairs = CollectionUtils.newArrayList();
+        final int keyCount = BUILD_KEYS_AND_VALUES.length / 2;
+        for (int i = 0; i < keyCount; i++) {
+            final int index = i * 2;
+            final String key = BUILD_KEYS_AND_VALUES[index];
+            final String value = BUILD_KEYS_AND_VALUES[index + 1];
+            sBuildKeyValues.put(key, value);
+            keyValuePairs.add(key + '=' + value);
+        }
+        sBuildKeyValuesDebugString = "[" + TextUtils.join(" ", keyValuePairs) + "]";
+    }
+
     public static String getDeviceOverrideValue(final Resources res, final int overrideResId) {
         final int orientation = res.getConfiguration().orientation;
         final String key = overrideResId + "-" + orientation;
@@ -47,34 +72,115 @@
         }
 
         final String[] overrideArray = res.getStringArray(overrideResId);
-        final String hardwareKey = "HARDWARE=" + Build.HARDWARE;
-        final String overrideValue = StringUtils.findValueOfKey(hardwareKey, overrideArray);
+        final String overrideValue = findConstantForKeyValuePairs(sBuildKeyValues, overrideArray);
         // The overrideValue might be an empty string.
         if (overrideValue != null) {
             if (DEBUG) {
                 Log.d(TAG, "Find override value:"
                         + " resource="+ res.getResourceEntryName(overrideResId)
-                        + " " + hardwareKey + " override=" + overrideValue);
+                        + " build=" + sBuildKeyValuesDebugString
+                        + " override=" + overrideValue);
             }
             sDeviceOverrideValueMap.put(key, overrideValue);
             return overrideValue;
         }
 
-        final String defaultValue = StringUtils.findValueOfKey(DEFAULT_KEY, overrideArray);
+        final String defaultValue = findDefaultConstant(overrideArray);
         // The defaultValue might be an empty string.
         if (defaultValue == null) {
             Log.w(TAG, "Couldn't find override value nor default value:"
                     + " resource="+ res.getResourceEntryName(overrideResId)
-                    + " " + hardwareKey);
+                    + " build=" + sBuildKeyValuesDebugString);
         } else if (DEBUG) {
             Log.d(TAG, "Found default value:"
                 + " resource="+ res.getResourceEntryName(overrideResId)
-                + " " + hardwareKey + " " + DEFAULT_KEY + "=" + defaultValue);
+                + " build=" + sBuildKeyValuesDebugString + " default=" + defaultValue);
         }
         sDeviceOverrideValueMap.put(key, defaultValue);
         return defaultValue;
     }
 
+    /**
+     * Find the condition that fulfills specified key value pairs from an array of
+     * "condition,constant", and return the corresponding string constant. A condition is
+     * "pattern1[:pattern2...] (or an empty string for the default). A pattern is "key=value"
+     * string. The condition matches only if all patterns of the condition are true for the
+     * specified key value pairs.
+     *
+     * For example, "condition,constant" has the following format.
+     * (See {@link ResourceUtilsTests#testFindConstantForKeyValuePairsCombined()})
+     *  - HARDWARE=mako,constantForNexus4
+     *  - MODEL=Nexus 4:MANUFACTURER=LGE,constantForNexus4
+     *  - ,defaultConstant
+     *
+     * @param keyValuePairs attributes to be used to look for a matched condition.
+     * @param conditionConstantArray an array of "condition,constant" elements to be searched.
+     * @return the constant part of the matched "condition,constant" element. Returns null if no
+     * condition matches.
+     */
+    @UsedForTesting
+    static String findConstantForKeyValuePairs(final HashMap<String, String> keyValuePairs,
+            final String[] conditionConstantArray) {
+        if (conditionConstantArray == null || keyValuePairs == null) {
+            return null;
+        }
+        for (final String conditionConstant : conditionConstantArray) {
+            final int posComma = conditionConstant.indexOf(',');
+            if (posComma < 0) {
+                throw new RuntimeException("Array element has no comma: " + conditionConstant);
+            }
+            final String condition = conditionConstant.substring(0, posComma);
+            if (condition.isEmpty()) {
+                // Default condition. The default condition should be searched by
+                // {@link #findConstantForDefault(String[])}.
+                continue;
+            }
+            if (fulfillsCondition(keyValuePairs, condition)) {
+                return conditionConstant.substring(posComma + 1);
+            }
+        }
+        return null;
+    }
+
+    private static boolean fulfillsCondition(final HashMap<String,String> keyValuePairs,
+            final String condition) {
+        final String[] patterns = condition.split(":");
+        // Check all patterns in a condition are true
+        for (final String pattern : patterns) {
+            final int posEqual = pattern.indexOf('=');
+            if (posEqual < 0) {
+                throw new RuntimeException("Pattern has no '=': " + condition);
+            }
+            final String key = pattern.substring(0, posEqual);
+            final String value = keyValuePairs.get(key);
+            if (value == null) {
+                throw new RuntimeException("Found unknown key: " + condition);
+            }
+            final String patternValue = pattern.substring(posEqual + 1);
+            if (!value.equals(patternValue)) {
+                return false;
+            }
+        }
+        return true;
+    }
+
+    @UsedForTesting
+    static String findDefaultConstant(final String[] conditionConstantArray) {
+        if (conditionConstantArray == null) {
+            return null;
+        }
+        for (final String condition : conditionConstantArray) {
+            final int posComma = condition.indexOf(',');
+            if (posComma < 0) {
+                throw new RuntimeException("Array element has no comma: " + condition);
+            }
+            if (posComma == 0) { // condition is empty.
+                return condition.substring(posComma + 1);
+            }
+        }
+        return null;
+    }
+
     public static boolean isValidFraction(final float fraction) {
         return fraction >= 0.0f;
     }
diff --git a/java/src/com/android/inputmethod/latin/StringUtils.java b/java/src/com/android/inputmethod/latin/StringUtils.java
index 5ff101f..ab050d7 100644
--- a/java/src/com/android/inputmethod/latin/StringUtils.java
+++ b/java/src/com/android/inputmethod/latin/StringUtils.java
@@ -65,30 +65,6 @@
     }
 
     /**
-     * Find a value that has a specified key from an array of key-comma-value.
-     *
-     * @param key a key string to find.
-     * @param array an array of key-comma-value string to be searched.
-     * @return the value part of the first string that has a specified key.
-     * Returns null if it couldn't be found.
-     */
-    public static String findValueOfKey(final String key, final String[] array) {
-        if (array == null) {
-            return null;
-        }
-        for (final String element : array) {
-            final int posComma = element.indexOf(',');
-            if (posComma < 0) {
-                throw new RuntimeException("Element has no comma: " + element);
-            }
-            if (element.substring(0, posComma).equals(key)) {
-                return element.substring(posComma + 1);
-            }
-        }
-        return null;
-    }
-
-    /**
      * Remove duplicates from an array of strings.
      *
      * This method will always keep the first occurrence of all strings at their position
diff --git a/tests/src/com/android/inputmethod/latin/ResourceUtilsTests.java b/tests/src/com/android/inputmethod/latin/ResourceUtilsTests.java
new file mode 100644
index 0000000..fa6df70
--- /dev/null
+++ b/tests/src/com/android/inputmethod/latin/ResourceUtilsTests.java
@@ -0,0 +1,140 @@
+/*
+ * Copyright (C) 2013 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.inputmethod.latin;
+
+import android.test.AndroidTestCase;
+import android.test.suitebuilder.annotation.SmallTest;
+
+import java.util.HashMap;
+
+@SmallTest
+public class ResourceUtilsTests extends AndroidTestCase {
+    public void testFindDefaultConstant() {
+        final String[] nullArray = null;
+        assertNull(ResourceUtils.findDefaultConstant(nullArray));
+
+        final String[] emptyArray = {};
+        assertNull(ResourceUtils.findDefaultConstant(emptyArray));
+
+        final String[] array = {
+            "HARDWARE=grouper,0.3",
+            "HARDWARE=mako,0.4",
+            ",defaultValue1",
+            "HARDWARE=manta,0.2",
+            ",defaultValue2",
+        };
+        assertEquals(ResourceUtils.findDefaultConstant(array), "defaultValue1");
+    }
+
+    public void testFindConstantForKeyValuePairsSimple() {
+        final HashMap<String,String> anyKeyValue = CollectionUtils.newHashMap();
+        anyKeyValue.put("anyKey", "anyValue");
+        final HashMap<String,String> nullKeyValue = null;
+        final HashMap<String,String> emptyKeyValue = CollectionUtils.newHashMap();
+
+        final String[] nullArray = null;
+        assertNull(ResourceUtils.findConstantForKeyValuePairs(anyKeyValue, nullArray));
+        assertNull(ResourceUtils.findConstantForKeyValuePairs(emptyKeyValue, nullArray));
+        assertNull(ResourceUtils.findConstantForKeyValuePairs(nullKeyValue, nullArray));
+
+        final String[] emptyArray = {};
+        assertNull(ResourceUtils.findConstantForKeyValuePairs(anyKeyValue, emptyArray));
+        assertNull(ResourceUtils.findConstantForKeyValuePairs(emptyKeyValue, emptyArray));
+        assertNull(ResourceUtils.findConstantForKeyValuePairs(nullKeyValue, emptyArray));
+
+        final String HARDWARE_KEY = "HARDWARE";
+        final String[] array = {
+            ",defaultValue",
+            "HARDWARE=grouper,0.3",
+            "HARDWARE=mako,0.4",
+            "HARDWARE=manta,0.2",
+            "HARDWARE=mako,0.5",
+        };
+
+        final HashMap<String,String> keyValues = CollectionUtils.newHashMap();
+        keyValues.put(HARDWARE_KEY, "grouper");
+        assertEquals(ResourceUtils.findConstantForKeyValuePairs(keyValues, array), "0.3");
+        keyValues.put(HARDWARE_KEY, "mako");
+        assertEquals(ResourceUtils.findConstantForKeyValuePairs(keyValues, array), "0.4");
+        keyValues.put(HARDWARE_KEY, "manta");
+        assertEquals(ResourceUtils.findConstantForKeyValuePairs(keyValues, array), "0.2");
+
+        try {
+            keyValues.clear();
+            keyValues.put("hardware", "grouper");
+            final String constant = ResourceUtils.findConstantForKeyValuePairs(keyValues, array);
+            fail("condition without HARDWARE must fail: constant=" + constant);
+        } catch (final RuntimeException e) {
+            assertEquals(e.getMessage(), "Found unknown key: HARDWARE=grouper");
+        }
+        keyValues.clear();
+        keyValues.put(HARDWARE_KEY, "MAKO");
+        assertNull(ResourceUtils.findConstantForKeyValuePairs(keyValues, array));
+        keyValues.put(HARDWARE_KEY, "mantaray");
+        assertNull(ResourceUtils.findConstantForKeyValuePairs(keyValues, array));
+
+        try {
+            final String constant = ResourceUtils.findConstantForKeyValuePairs(
+                    emptyKeyValue, array);
+            fail("emptyCondition shouldn't match: constant=" + constant);
+        } catch (final RuntimeException e) {
+            assertEquals(e.getMessage(), "Found unknown key: HARDWARE=grouper");
+        }
+    }
+
+    public void testFindConstantForKeyValuePairsCombined() {
+        final String HARDWARE_KEY = "HARDWARE";
+        final String MODEL_KEY = "MODEL";
+        final String MANUFACTURER_KEY = "MANUFACTURER";
+        final String[] array = {
+            ",defaultValue",
+            "HARDWARE=grouper:MANUFACTURER=asus,0.3",
+            "HARDWARE=mako:MODEL=Nexus 4,0.4",
+            "HARDWARE=manta:MODEL=Nexus 10:MANUFACTURER=samsung,0.2"
+        };
+        final String[] failArray = {
+            ",defaultValue",
+            "HARDWARE=grouper:MANUFACTURER=ASUS,0.3",
+            "HARDWARE=mako:MODEL=Nexus_4,0.4",
+            "HARDWARE=mantaray:MODEL=Nexus 10:MANUFACTURER=samsung,0.2"
+        };
+
+        final HashMap<String,String> keyValues = CollectionUtils.newHashMap();
+        keyValues.put(HARDWARE_KEY, "grouper");
+        keyValues.put(MODEL_KEY, "Nexus 7");
+        keyValues.put(MANUFACTURER_KEY, "asus");
+        assertEquals(ResourceUtils.findConstantForKeyValuePairs(keyValues, array), "0.3");
+        assertNull(ResourceUtils.findConstantForKeyValuePairs(keyValues, failArray));
+
+        keyValues.clear();
+        keyValues.put(HARDWARE_KEY, "mako");
+        keyValues.put(MODEL_KEY, "Nexus 4");
+        keyValues.put(MANUFACTURER_KEY, "LGE");
+        assertEquals(ResourceUtils.findConstantForKeyValuePairs(keyValues, array), "0.4");
+        assertNull(ResourceUtils.findConstantForKeyValuePairs(keyValues, failArray));
+
+        keyValues.clear();
+        keyValues.put(HARDWARE_KEY, "manta");
+        keyValues.put(MODEL_KEY, "Nexus 10");
+        keyValues.put(MANUFACTURER_KEY, "samsung");
+        assertEquals(ResourceUtils.findConstantForKeyValuePairs(keyValues, array), "0.2");
+        assertNull(ResourceUtils.findConstantForKeyValuePairs(keyValues, failArray));
+        keyValues.put(HARDWARE_KEY, "mantaray");
+        assertNull(ResourceUtils.findConstantForKeyValuePairs(keyValues, array));
+        assertEquals(ResourceUtils.findConstantForKeyValuePairs(keyValues, failArray), "0.2");
+    }
+}
diff --git a/tests/src/com/android/inputmethod/latin/StringUtilsTests.java b/tests/src/com/android/inputmethod/latin/StringUtilsTests.java
index b6a17a3..29e790a 100644
--- a/tests/src/com/android/inputmethod/latin/StringUtilsTests.java
+++ b/tests/src/com/android/inputmethod/latin/StringUtilsTests.java
@@ -178,7 +178,7 @@
         assertTrue(StringUtils.isIdenticalAfterDowncase(""));
     }
 
-    private void checkCapitalize(final String src, final String dst, final String separators,
+    private static void checkCapitalize(final String src, final String dst, final String separators,
             final Locale locale) {
         assertEquals(dst, StringUtils.capitalizeEachWord(src, separators, locale));
         assert(src.equals(dst)
@@ -237,63 +237,4 @@
         // code for now True is acceptable.
         assertTrue(StringUtils.lastPartLooksLikeURL(".abc/def"));
     }
-
-    public void testFindValueOfKey() {
-        final String nullKey = null;
-        final String emptyKey = "";
-
-        final String[] nullArray = null;
-        assertNull(StringUtils.findValueOfKey("anyKey", nullArray));
-        assertNull(StringUtils.findValueOfKey(emptyKey, nullArray));
-        assertNull(StringUtils.findValueOfKey(nullKey, nullArray));
-
-        final String[] emptyArray = {};
-        assertNull(StringUtils.findValueOfKey("anyKey", emptyArray));
-        assertNull(StringUtils.findValueOfKey(emptyKey, emptyArray));
-        assertNull(StringUtils.findValueOfKey(nullKey, emptyArray));
-
-        final String[] array = {
-            "DEFAULT,defaultValue",
-            "HARDWARE=grouper,0.3",
-            "HARDWARE=mako,0.4",
-            "HARDWARE=manta,0.2"
-        };
-        assertEquals(StringUtils.findValueOfKey("HARDWARE=grouper", array), "0.3");
-        assertEquals(StringUtils.findValueOfKey("HARDWARE=mako", array), "0.4");
-        assertEquals(StringUtils.findValueOfKey("HARDWARE=manta", array), "0.2");
-        assertEquals(StringUtils.findValueOfKey("DEFAULT", array), "defaultValue");
-
-        assertNull(StringUtils.findValueOfKey("hardware=grouper", array));
-        assertNull(StringUtils.findValueOfKey("HARDWARE=MAKO", array));
-        assertNull(StringUtils.findValueOfKey("HARDWARE=mantaray", array));
-        assertNull(StringUtils.findValueOfKey(emptyKey, array));
-        assertNull(StringUtils.findValueOfKey(nullKey, array));
-
-        final String[] containsNullKey = {
-            "DEFAULT,defaultValue",
-            ",emptyValue"
-        };
-        assertEquals(StringUtils.findValueOfKey(emptyKey, containsNullKey), "emptyValue");
-
-        final String[] containsMultipleSameKeys = {
-            "key1,value1",
-            "key2,value2",
-            "key3,value3",
-            "key2,value4"
-        };
-        assertEquals(StringUtils.findValueOfKey("key2", containsMultipleSameKeys), "value2");
-
-        final String[] containNoCommaElement = {
-            "key1,value1",
-            "key2-and-value2",
-            "key3,value3"
-        };
-        assertEquals(StringUtils.findValueOfKey("key1", containNoCommaElement), "value1");
-        try {
-            final String valueOfKey3 = StringUtils.findValueOfKey("key3", containNoCommaElement);
-            fail("finding valueOfKey3=" + valueOfKey3 + " must fail");
-        } catch (final RuntimeException e) {
-            assertEquals(e.getMessage(), "Element has no comma: key2-and-value2");
-        }
-    }
 }