Merge "Avoid returning an object that's still used internally"
diff --git a/java/res/values/attrs.xml b/java/res/values/attrs.xml
index 9358c90..3275cd0 100644
--- a/java/res/values/attrs.xml
+++ b/java/res/values/attrs.xml
@@ -194,8 +194,11 @@
         <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" />
+        </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..e1f68d4 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
diff --git a/java/src/com/android/inputmethod/keyboard/Key.java b/java/src/com/android/inputmethod/keyboard/Key.java
index 06d248e..c4d5e85 100644
--- a/java/src/com/android/inputmethod/keyboard/Key.java
+++ b/java/src/com/android/inputmethod/keyboard/Key.java
@@ -102,8 +102,12 @@
      * {@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;
+
     /** Whether this key repeats itself when held down */
     public final boolean mRepeatable;
 
@@ -225,7 +229,7 @@
         mEdgeFlags = edgeFlags;
         mHintLabel = hintLabel;
         mLabelOption = 0;
-        mFunctional = false;
+        mBackgroundType = BACKGROUND_TYPE_NORMAL;
         mSticky = false;
         mRepeatable = false;
         mMoreKeys = null;
@@ -325,8 +329,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,7 +545,7 @@
      */
     public int[] getCurrentDrawableState() {
         final boolean pressed = mPressed;
-        if (!mSticky && mFunctional) {
+        if (!mSticky && mBackgroundType == BACKGROUND_TYPE_FUNCTIONAL) {
             if (pressed) {
                 return KEY_STATE_FUNCTIONAL_PRESSED;
             } else {
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/spellcheck/AndroidSpellCheckerService.java b/java/src/com/android/inputmethod/latin/spellcheck/AndroidSpellCheckerService.java
index dfa0abf..bf2e90d 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);
         }
     }
 }