Merge "Add an option to user dict to match more precise locales"
diff --git a/java/res/drawable-hdpi/btn_keyboard_key_dark_active_holo.9.png b/java/res/drawable-hdpi/btn_keyboard_key_dark_active_holo.9.png
new file mode 100644
index 0000000..8bb66b7
--- /dev/null
+++ b/java/res/drawable-hdpi/btn_keyboard_key_dark_active_holo.9.png
Binary files differ
diff --git a/java/res/drawable-mdpi/btn_keyboard_key_dark_active_holo.9.png b/java/res/drawable-mdpi/btn_keyboard_key_dark_active_holo.9.png
new file mode 100644
index 0000000..c8d7ee0
--- /dev/null
+++ b/java/res/drawable-mdpi/btn_keyboard_key_dark_active_holo.9.png
Binary files differ
diff --git a/java/res/drawable-xhdpi/btn_keyboard_key_dark_active_holo.9.png b/java/res/drawable-xhdpi/btn_keyboard_key_dark_active_holo.9.png
new file mode 100644
index 0000000..eba91db
--- /dev/null
+++ b/java/res/drawable-xhdpi/btn_keyboard_key_dark_active_holo.9.png
Binary files differ
diff --git a/java/res/drawable/btn_keyboard_key_ics.xml b/java/res/drawable/btn_keyboard_key_ics.xml
index 7335cc2..e893da1 100644
--- a/java/res/drawable/btn_keyboard_key_ics.xml
+++ b/java/res/drawable/btn_keyboard_key_ics.xml
@@ -23,6 +23,13 @@
     <item android:state_single="true"
           android:drawable="@drawable/btn_keyboard_key_dark_normal_holo" />
 
+    <!-- Action keys. -->
+
+    <item android:state_active="true" android:state_pressed="true"
+          android:drawable="@drawable/btn_keyboard_key_dark_pressed_holo" />
+    <item android:state_active="true"
+          android:drawable="@drawable/btn_keyboard_key_dark_active_holo" />
+
     <!-- Toggle keys. Use checkable/checked state. -->
 
     <item android:state_checkable="true" android:state_checked="true" android:state_pressed="true"
@@ -34,7 +41,7 @@
     <item android:state_checkable="true"
           android:drawable="@drawable/btn_keyboard_key_dark_normal_off_holo" />
 
-    <!-- Normal keys -->
+    <!-- Normal keys. -->
 
     <item android:state_pressed="true"
           android:drawable="@drawable/btn_keyboard_key_light_pressed_holo" />
diff --git a/java/res/values/attrs.xml b/java/res/values/attrs.xml
index 9358c90..0451c9f 100644
--- a/java/res/values/attrs.xml
+++ b/java/res/values/attrs.xml
@@ -194,8 +194,12 @@
         <attr name="moreKeys" format="string" />
         <!-- Maximum column of more keys keyboard -->
         <attr name="maxMoreKeysColumn" format="integer" />
-        <!-- Whether this is a functional key which has different key top than normal key. -->
-        <attr name="isFunctional" format="boolean" />
+        <attr name="backgroundType" format="enum">
+            <!-- This should be aligned with Key.BACKGROUND_TYPE_* -->
+            <enum name="normal" value="0" />
+            <enum name="functional" value="1" />
+            <enum name="action" value="2" />
+        </attr>
         <!-- Whether this is a toggle key. -->
         <attr name="isSticky" format="boolean" />
         <!-- Whether long-pressing on this key will make it repeat. -->
diff --git a/java/res/xml-sw600dp/kbd_key_styles.xml b/java/res/xml-sw600dp/kbd_key_styles.xml
index 2c31e27..dfc7409 100644
--- a/java/res/xml-sw600dp/kbd_key_styles.xml
+++ b/java/res/xml-sw600dp/kbd_key_styles.xml
@@ -24,7 +24,7 @@
     <!-- Base key style for the functional key -->
     <key-style
         latin:styleName="functionalKeyStyle"
-        latin:isFunctional="true" />
+        latin:backgroundType="functional" />
     <!-- Base key style for the key which may have settings key as popup key -->
     <switch>
         <case
diff --git a/java/res/xml-sw768dp/kbd_key_styles.xml b/java/res/xml-sw768dp/kbd_key_styles.xml
index 6570ebc..f7dcc20 100644
--- a/java/res/xml-sw768dp/kbd_key_styles.xml
+++ b/java/res/xml-sw768dp/kbd_key_styles.xml
@@ -24,7 +24,7 @@
     <!-- Functional key styles -->
     <key-style
         latin:styleName="functionalKeyStyle"
-        latin:isFunctional="true" />
+        latin:backgroundType="functional" />
     <key-style
         latin:styleName="shiftKeyStyle"
         latin:code="@integer/key_shift"
diff --git a/java/res/xml/kbd_key_styles.xml b/java/res/xml/kbd_key_styles.xml
index 84b1900..9df6472 100644
--- a/java/res/xml/kbd_key_styles.xml
+++ b/java/res/xml/kbd_key_styles.xml
@@ -24,7 +24,7 @@
     <!-- Base key style for the functional key -->
     <key-style
         latin:styleName="functionalKeyStyle"
-        latin:isFunctional="true" />
+        latin:backgroundType="functional" />
     <!-- Base key style for the key which may have settings or tab key as popup key. -->
     <switch>
         <case
@@ -102,7 +102,7 @@
                 latin:code="@integer/key_return"
                 latin:keyLabel="@string/label_go_key"
                 latin:keyLabelOption="autoXScale"
-                latin:parentStyle="functionalKeyStyle" />
+                latin:backgroundType="action" />
         </case>
         <case
             latin:imeAction="actionNext"
@@ -112,7 +112,7 @@
                 latin:code="@integer/key_return"
                 latin:keyLabel="@string/label_next_key"
                 latin:keyLabelOption="autoXScale"
-                latin:parentStyle="functionalKeyStyle" />
+                latin:backgroundType="action" />
         </case>
         <case
             latin:imeAction="actionDone"
@@ -122,7 +122,7 @@
                 latin:code="@integer/key_return"
                 latin:keyLabel="@string/label_done_key"
                 latin:keyLabelOption="autoXScale"
-                latin:parentStyle="functionalKeyStyle" />
+                latin:backgroundType="action" />
         </case>
         <case
             latin:imeAction="actionSend"
@@ -132,7 +132,7 @@
                 latin:code="@integer/key_return"
                 latin:keyLabel="@string/label_send_key"
                 latin:keyLabelOption="autoXScale"
-                latin:parentStyle="functionalKeyStyle" />
+                latin:backgroundType="action" />
         </case>
         <case
             latin:imeAction="actionSearch"
@@ -141,7 +141,7 @@
                 latin:styleName="returnKeyStyle"
                 latin:code="@integer/key_return"
                 latin:keyIcon="iconSearchKey"
-                latin:parentStyle="functionalKeyStyle" />
+                latin:backgroundType="action" />
         </case>
         <default>
             <key-style
diff --git a/java/src/com/android/inputmethod/keyboard/Key.java b/java/src/com/android/inputmethod/keyboard/Key.java
index 06d248e..9959a78 100644
--- a/java/src/com/android/inputmethod/keyboard/Key.java
+++ b/java/src/com/android/inputmethod/keyboard/Key.java
@@ -102,8 +102,13 @@
      * {@link Keyboard#EDGE_TOP} and {@link Keyboard#EDGE_BOTTOM}.
      */
     private int mEdgeFlags;
-    /** Whether this is a functional key which has different key top than normal key */
-    public final boolean mFunctional;
+
+    /** Background type that represents different key background visual than normal one. */
+    public final int mBackgroundType;
+    public static final int BACKGROUND_TYPE_NORMAL = 0;
+    public static final int BACKGROUND_TYPE_FUNCTIONAL = 1;
+    public static final int BACKGROUND_TYPE_ACTION = 2;
+
     /** Whether this key repeats itself when held down */
     public final boolean mRepeatable;
 
@@ -159,6 +164,17 @@
             android.R.attr.state_pressed
     };
 
+    // action normal state (with properties)
+    private static final int[] KEY_STATE_ACTIVE_NORMAL = {
+            android.R.attr.state_active
+    };
+
+    // action pressed state (with properties)
+    private static final int[] KEY_STATE_ACTIVE_PRESSED = {
+            android.R.attr.state_active,
+            android.R.attr.state_pressed
+    };
+
     // RTL parenthesis character swapping map.
     private static final Map<Integer, Integer> sRtlParenthesisMap = new HashMap<Integer, Integer>();
 
@@ -225,7 +241,7 @@
         mEdgeFlags = edgeFlags;
         mHintLabel = hintLabel;
         mLabelOption = 0;
-        mFunctional = false;
+        mBackgroundType = BACKGROUND_TYPE_NORMAL;
         mSticky = false;
         mRepeatable = false;
         mMoreKeys = null;
@@ -325,8 +341,9 @@
         mMaxMoreKeysColumn = style.getInt(keyboardAttr, R.styleable.Keyboard_Key_maxMoreKeysColumn,
                 params.mMaxMiniKeyboardColumn);
 
+        mBackgroundType = style.getInt(
+                keyAttr, R.styleable.Keyboard_Key_backgroundType, BACKGROUND_TYPE_NORMAL);
         mRepeatable = style.getBoolean(keyAttr, R.styleable.Keyboard_Key_isRepeatable, false);
-        mFunctional = style.getBoolean(keyAttr, R.styleable.Keyboard_Key_isFunctional, false);
         mSticky = style.getBoolean(keyAttr, R.styleable.Keyboard_Key_isSticky, false);
         mEnabled = style.getBoolean(keyAttr, R.styleable.Keyboard_Key_enabled, true);
         mEdgeFlags = 0;
@@ -540,36 +557,24 @@
      */
     public int[] getCurrentDrawableState() {
         final boolean pressed = mPressed;
-        if (!mSticky && mFunctional) {
-            if (pressed) {
-                return KEY_STATE_FUNCTIONAL_PRESSED;
+
+        // TODO: "Sticky" should be one of backgroundType.
+        if (mSticky) {
+            if (mHighlightOn) {
+                return pressed ? KEY_STATE_PRESSED_ON : KEY_STATE_NORMAL_ON;
             } else {
-                return KEY_STATE_FUNCTIONAL_NORMAL;
+                return pressed ? KEY_STATE_PRESSED_OFF : KEY_STATE_NORMAL_OFF;
             }
         }
 
-        int[] states = KEY_STATE_NORMAL;
-
-        if (mHighlightOn) {
-            if (pressed) {
-                states = KEY_STATE_PRESSED_ON;
-            } else {
-                states = KEY_STATE_NORMAL_ON;
-            }
-        } else {
-            if (mSticky) {
-                if (pressed) {
-                    states = KEY_STATE_PRESSED_OFF;
-                } else {
-                    states = KEY_STATE_NORMAL_OFF;
-                }
-            } else {
-                if (pressed) {
-                    states = KEY_STATE_PRESSED;
-                }
-            }
+        switch (mBackgroundType) {
+        case BACKGROUND_TYPE_FUNCTIONAL:
+            return pressed ? KEY_STATE_FUNCTIONAL_PRESSED : KEY_STATE_FUNCTIONAL_NORMAL;
+        case BACKGROUND_TYPE_ACTION:
+            return pressed ? KEY_STATE_ACTIVE_PRESSED : KEY_STATE_ACTIVE_NORMAL;
+        default: /* BACKGROUND_TYPE_NORMAL */
+            return pressed ? KEY_STATE_PRESSED : KEY_STATE_NORMAL;
         }
-        return states;
     }
 
     public static class Spacer extends Key {
diff --git a/java/src/com/android/inputmethod/keyboard/internal/KeyStyles.java b/java/src/com/android/inputmethod/keyboard/internal/KeyStyles.java
index 6d78e85..9800f24 100644
--- a/java/src/com/android/inputmethod/keyboard/internal/KeyStyles.java
+++ b/java/src/com/android/inputmethod/keyboard/internal/KeyStyles.java
@@ -172,7 +172,7 @@
             readInt(keyAttr, R.styleable.Keyboard_Key_keyIconPreview);
             readInt(keyAttr, R.styleable.Keyboard_Key_keyIconShifted);
             readInt(keyAttr, R.styleable.Keyboard_Key_maxMoreKeysColumn);
-            readBoolean(keyAttr, R.styleable.Keyboard_Key_isFunctional);
+            readInt(keyAttr, R.styleable.Keyboard_Key_backgroundType);
             readBoolean(keyAttr, R.styleable.Keyboard_Key_isSticky);
             readBoolean(keyAttr, R.styleable.Keyboard_Key_isRepeatable);
             readBoolean(keyAttr, R.styleable.Keyboard_Key_enabled);
diff --git a/java/src/com/android/inputmethod/latin/Suggest.java b/java/src/com/android/inputmethod/latin/Suggest.java
index 93933f1..16dccf8 100644
--- a/java/src/com/android/inputmethod/latin/Suggest.java
+++ b/java/src/com/android/inputmethod/latin/Suggest.java
@@ -306,12 +306,10 @@
         Arrays.fill(mScores, 0);
 
         // Save a lowercase version of the original word
-        CharSequence typedWord = wordComposer.getTypedWord();
+        String typedWord = wordComposer.getTypedWord();
         if (typedWord != null) {
-            final String typedWordString = typedWord.toString();
-            typedWord = typedWordString;
             // Treating USER_TYPED as UNIGRAM suggestion for logging now.
-            LatinImeLogger.onAddSuggestedWord(typedWordString, Suggest.DIC_USER_TYPED,
+            LatinImeLogger.onAddSuggestedWord(typedWord, Suggest.DIC_USER_TYPED,
                     Dictionary.DataType.UNIGRAM);
         }
         mTypedWord = typedWord;
diff --git a/java/src/com/android/inputmethod/latin/WordComposer.java b/java/src/com/android/inputmethod/latin/WordComposer.java
index 24519ad..a79e6dc 100644
--- a/java/src/com/android/inputmethod/latin/WordComposer.java
+++ b/java/src/com/android/inputmethod/latin/WordComposer.java
@@ -164,11 +164,11 @@
      * Returns the word as it was typed, without any correction applied.
      * @return the word that was typed so far
      */
-    public CharSequence getTypedWord() {
+    public String getTypedWord() {
         if (size() == 0) {
             return null;
         }
-        return mTypedWord;
+        return mTypedWord.toString();
     }
 
     /**
diff --git a/java/src/com/android/inputmethod/latin/spellcheck/AndroidSpellCheckerService.java b/java/src/com/android/inputmethod/latin/spellcheck/AndroidSpellCheckerService.java
index 0864526..1e5b877 100644
--- a/java/src/com/android/inputmethod/latin/spellcheck/AndroidSpellCheckerService.java
+++ b/java/src/com/android/inputmethod/latin/spellcheck/AndroidSpellCheckerService.java
@@ -327,67 +327,78 @@
         @Override
         public SuggestionsInfo onGetSuggestions(final TextInfo textInfo,
                 final int suggestionsLimit) {
-            final String text = textInfo.getText();
-
-            if (shouldFilterOut(text)) return EMPTY_SUGGESTIONS_INFO;
-
-            final SuggestionsGatherer suggestionsGatherer =
-                    new SuggestionsGatherer(suggestionsLimit);
-            final WordComposer composer = new WordComposer();
-            final int length = text.length();
-            for (int i = 0; i < length; ++i) {
-                final int character = text.codePointAt(i);
-                final int proximityIndex = SpellCheckerProximityInfo.getIndexOf(character);
-                final int[] proximities;
-                if (-1 == proximityIndex) {
-                    proximities = new int[] { character };
-                } else {
-                    proximities = Arrays.copyOfRange(SpellCheckerProximityInfo.PROXIMITY,
-                            proximityIndex, proximityIndex + SpellCheckerProximityInfo.ROW_SIZE);
-                }
-                composer.add(character, proximities,
-                        WordComposer.NOT_A_COORDINATE, WordComposer.NOT_A_COORDINATE);
-            }
-
-            final int capitalizeType = getCapitalizationType(text);
-            boolean isInDict = true;
             try {
-                final DictAndProximity dictInfo = mDictionaryPool.take();
-                dictInfo.mDictionary.getWords(composer, suggestionsGatherer,
-                        dictInfo.mProximityInfo);
-                isInDict = dictInfo.mDictionary.isValidWord(text);
-                if (!isInDict && CAPITALIZE_NONE != capitalizeType) {
-                    // We want to test the word again if it's all caps or first caps only.
-                    // If it's fully down, we already tested it, if it's mixed case, we don't
-                    // want to test a lowercase version of it.
-                    isInDict = dictInfo.mDictionary.isValidWord(text.toLowerCase(mLocale));
-                }
-                if (!mDictionaryPool.offer(dictInfo)) {
-                    Log.e(TAG, "Can't re-insert a dictionary into its pool");
-                }
-            } catch (InterruptedException e) {
-                // I don't think this can happen.
-                return EMPTY_SUGGESTIONS_INFO;
-            }
+                final String text = textInfo.getText();
 
-            final SuggestionsGatherer.Result result = suggestionsGatherer.getResults(text,
-                    mService.mTypoThreshold, capitalizeType, mLocale);
+                if (shouldFilterOut(text)) return EMPTY_SUGGESTIONS_INFO;
 
-            if (DBG) {
-                Log.i(TAG, "Spell checking results for " + text + " with suggestion limit "
-                        + suggestionsLimit);
-                Log.i(TAG, "IsInDict = " + result.mLooksLikeTypo);
-                Log.i(TAG, "LooksLikeTypo = " + result.mLooksLikeTypo);
-                for (String suggestion : result.mSuggestions) {
-                    Log.i(TAG, suggestion);
+                final SuggestionsGatherer suggestionsGatherer =
+                        new SuggestionsGatherer(suggestionsLimit);
+                final WordComposer composer = new WordComposer();
+                final int length = text.length();
+                for (int i = 0; i < length; ++i) {
+                    final int character = text.codePointAt(i);
+                    final int proximityIndex = SpellCheckerProximityInfo.getIndexOf(character);
+                    final int[] proximities;
+                    if (-1 == proximityIndex) {
+                        proximities = new int[] { character };
+                    } else {
+                        proximities = Arrays.copyOfRange(SpellCheckerProximityInfo.PROXIMITY,
+                                proximityIndex,
+                                proximityIndex + SpellCheckerProximityInfo.ROW_SIZE);
+                    }
+                    composer.add(character, proximities,
+                            WordComposer.NOT_A_COORDINATE, WordComposer.NOT_A_COORDINATE);
+                }
+
+                final int capitalizeType = getCapitalizationType(text);
+                boolean isInDict = true;
+                try {
+                    final DictAndProximity dictInfo = mDictionaryPool.take();
+                    dictInfo.mDictionary.getWords(composer, suggestionsGatherer,
+                            dictInfo.mProximityInfo);
+                    isInDict = dictInfo.mDictionary.isValidWord(text);
+                    if (!isInDict && CAPITALIZE_NONE != capitalizeType) {
+                        // We want to test the word again if it's all caps or first caps only.
+                        // If it's fully down, we already tested it, if it's mixed case, we don't
+                        // want to test a lowercase version of it.
+                        isInDict = dictInfo.mDictionary.isValidWord(text.toLowerCase(mLocale));
+                    }
+                    if (!mDictionaryPool.offer(dictInfo)) {
+                        Log.e(TAG, "Can't re-insert a dictionary into its pool");
+                    }
+                } catch (InterruptedException e) {
+                    // I don't think this can happen.
+                    return EMPTY_SUGGESTIONS_INFO;
+                }
+
+                final SuggestionsGatherer.Result result = suggestionsGatherer.getResults(text,
+                        mService.mTypoThreshold, capitalizeType, mLocale);
+
+                if (DBG) {
+                    Log.i(TAG, "Spell checking results for " + text + " with suggestion limit "
+                            + suggestionsLimit);
+                    Log.i(TAG, "IsInDict = " + result.mLooksLikeTypo);
+                    Log.i(TAG, "LooksLikeTypo = " + result.mLooksLikeTypo);
+                    for (String suggestion : result.mSuggestions) {
+                        Log.i(TAG, suggestion);
+                    }
+                }
+
+                final int flags =
+                        (isInDict ? SuggestionsInfo.RESULT_ATTR_IN_THE_DICTIONARY : 0)
+                                | (result.mLooksLikeTypo
+                                        ? SuggestionsInfo.RESULT_ATTR_LOOKS_LIKE_TYPO : 0);
+                return new SuggestionsInfo(flags, result.mSuggestions);
+            } catch (RuntimeException e) {
+                // Don't kill the keyboard if there is a bug in the spell checker
+                if (DBG) {
+                    throw e;
+                } else {
+                    Log.e(TAG, "Exception while spellcheking: " + e);
+                    return EMPTY_SUGGESTIONS_INFO;
                 }
             }
-
-            final int flags =
-                    (isInDict ? SuggestionsInfo.RESULT_ATTR_IN_THE_DICTIONARY : 0)
-                            | (result.mLooksLikeTypo
-                                    ? SuggestionsInfo.RESULT_ATTR_LOOKS_LIKE_TYPO : 0);
-            return new SuggestionsInfo(flags, result.mSuggestions);
         }
     }
 }