Merge "Demote skipped characters matched words with respect to length." into honeycomb-mr1
diff --git a/java/proguard.flags b/java/proguard.flags
index 9096855..729f4ad 100644
--- a/java/proguard.flags
+++ b/java/proguard.flags
@@ -14,3 +14,7 @@
   void waitUntilUpdateDBDone();
   void waitForDictionaryLoading();
 }
+
+-keep class com.android.inputmethod.latin.AutoCorrection {
+  java.lang.CharSequence getAutoCorrectionWord();
+}
diff --git a/java/res/values-fr/strings.xml b/java/res/values-fr/strings.xml
index c9c6a45..1c6be41 100644
--- a/java/res/values-fr/strings.xml
+++ b/java/res/values-fr/strings.xml
@@ -40,7 +40,7 @@
     <string name="settings_key_mode_auto_name" msgid="2993460277873684680">"Automatique"</string>
     <string name="settings_key_mode_always_show_name" msgid="3047567041784760575">"Toujours afficher"</string>
     <string name="settings_key_mode_always_hide_name" msgid="7833948046716923994">"Toujours masquer"</string>
-    <string name="auto_correction" msgid="4979925752001319458">"Correction auto."</string>
+    <string name="auto_correction" msgid="4979925752001319458">"Correction auto"</string>
     <string name="auto_correction_summary" msgid="5625751551134658006">"Correction auto mots avec barre espace/signes ponctuation"</string>
     <string name="auto_correction_threshold_mode_off" msgid="8470882665417944026">"Désactiver"</string>
     <string name="auto_correction_threshold_mode_modest" msgid="8788366690620799097">"Simple"</string>
@@ -84,7 +84,7 @@
     <string name="voice_warning_locale_not_supported" msgid="637923019716442333">"La saisie vocale n\'est pas encore prise en charge pour votre langue, mais elle fonctionne en anglais."</string>
     <string name="voice_warning_may_not_understand" msgid="5596289095878251072">"La saisie vocale fait appel à la reconnaissance vocale de Google. Les "<a href="http://m.google.com/privacy">"Règles de confidentialité Google Mobile"</a>" s\'appliquent."</string>
     <string name="voice_warning_how_to_turn_off" msgid="3190378129944934856">"Pour désactiver la saisie vocale, accédez aux paramètres du mode de saisie."</string>
-    <string name="voice_hint_dialog_message" msgid="1420686286820661548">"Pour utiliser la saisie vocale, appuyez sur le bouton représentant un micro."</string>
+    <string name="voice_hint_dialog_message" msgid="1420686286820661548">"Pour utiliser la saisie vocale, appuyez sur la touche du microphone."</string>
     <string name="voice_listening" msgid="467518160751321844">"Parlez maintenant"</string>
     <string name="voice_working" msgid="6666937792815731889">"Traitement en cours"</string>
     <string name="voice_initializing" msgid="661962047129906646"></string>
diff --git a/java/src/com/android/inputmethod/keyboard/Keyboard.java b/java/src/com/android/inputmethod/keyboard/Keyboard.java
index 3005920..06d4468 100644
--- a/java/src/com/android/inputmethod/keyboard/Keyboard.java
+++ b/java/src/com/android/inputmethod/keyboard/Keyboard.java
@@ -174,7 +174,7 @@
         mDefaultHeight = mDefaultWidth;
         mId = id;
         loadKeyboard(context, xmlLayoutResId);
-        mProximityInfo = new ProximityInfo(mDisplayWidth, mDisplayHeight, GRID_WIDTH, GRID_HEIGHT);
+        mProximityInfo = new ProximityInfo(GRID_WIDTH, GRID_HEIGHT);
     }
 
     public int getProximityInfo() {
@@ -378,7 +378,7 @@
                 mGridNeighbors[(y / mCellHeight) * GRID_WIDTH + (x / mCellWidth)] = cell;
             }
         }
-        mProximityInfo.setProximityInfo(mGridNeighbors);
+        mProximityInfo.setProximityInfo(mGridNeighbors, getMinWidth(), getHeight(), mKeys);
     }
 
     public boolean isInside(Key key, int x, int y) {
diff --git a/java/src/com/android/inputmethod/keyboard/ProximityInfo.java b/java/src/com/android/inputmethod/keyboard/ProximityInfo.java
index fcada37..80d6de9 100644
--- a/java/src/com/android/inputmethod/keyboard/ProximityInfo.java
+++ b/java/src/com/android/inputmethod/keyboard/ProximityInfo.java
@@ -18,18 +18,17 @@
 
 import com.android.inputmethod.latin.Utils;
 
+import java.util.Arrays;
+import java.util.List;
+
 public class ProximityInfo {
     public static final int MAX_PROXIMITY_CHARS_SIZE = 16;
 
-    private final int mDisplayWidth;
-    private final int mDisplayHeight;
     private final int mGridWidth;
     private final int mGridHeight;
     private final int mGridSize;
 
-    ProximityInfo(int displayWidth, int displayHeight, int gridWidth, int gridHeight) {
-        mDisplayWidth = displayWidth;
-        mDisplayHeight = displayHeight;
+    ProximityInfo(int gridWidth, int gridHeight) {
         mGridWidth = gridWidth;
         mGridHeight = gridHeight;
         mGridSize = mGridWidth * mGridHeight;
@@ -43,20 +42,19 @@
             int displayHeight, int gridWidth, int gridHeight, int[] proximityCharsArray);
     private native void releaseProximityInfoNative(int nativeProximityInfo);
 
-    public final void setProximityInfo(int[][] gridNeighbors) {
+    public final void setProximityInfo(int[][] gridNeighborKeyIndexes, int keyboardWidth,
+            int keyboardHeight, List<Key> keys) {
         int[] proximityCharsArray = new int[mGridSize * MAX_PROXIMITY_CHARS_SIZE];
+        Arrays.fill(proximityCharsArray, KeyDetector.NOT_A_CODE);
         for (int i = 0; i < mGridSize; ++i) {
-            final int proximityCharsLength = gridNeighbors[i].length;
-            for (int j = 0; j < MAX_PROXIMITY_CHARS_SIZE; ++j) {
-                int charCode = KeyDetector.NOT_A_KEY;
-                if (j < proximityCharsLength) {
-                    charCode = gridNeighbors[i][j];
-                }
-                proximityCharsArray[i * MAX_PROXIMITY_CHARS_SIZE + j] = charCode;
+            final int proximityCharsLength = gridNeighborKeyIndexes[i].length;
+            for (int j = 0; j < proximityCharsLength; ++j) {
+                proximityCharsArray[i * MAX_PROXIMITY_CHARS_SIZE + j] =
+                        keys.get(gridNeighborKeyIndexes[i][j]).mCode;
             }
         }
         mNativeProximityInfo = setProximityInfoNative(MAX_PROXIMITY_CHARS_SIZE,
-                mDisplayWidth, mDisplayHeight, mGridWidth, mGridHeight, proximityCharsArray);
+                keyboardWidth, keyboardHeight, mGridWidth, mGridHeight, proximityCharsArray);
     }
 
     // TODO: Get rid of this function's input (keyboard).
diff --git a/java/src/com/android/inputmethod/latin/AutoCorrection.java b/java/src/com/android/inputmethod/latin/AutoCorrection.java
index d1154d9..f177f6a 100644
--- a/java/src/com/android/inputmethod/latin/AutoCorrection.java
+++ b/java/src/com/android/inputmethod/latin/AutoCorrection.java
@@ -19,6 +19,7 @@
 import android.util.Log;
 
 import java.util.ArrayList;
+import java.util.Collection;
 
 public class AutoCorrection {
     private static final boolean DBG = LatinImeLogger.sDBG;
@@ -45,12 +46,12 @@
         return mNormalizedScore;
     }
 
-    public void updateAutoCorrectionStatus(Suggest suggest,
+    public void updateAutoCorrectionStatus(Collection<Dictionary> dictionaries,
             WordComposer wordComposer, ArrayList<CharSequence> suggestions, int[] priorities,
             CharSequence typedWord, double autoCorrectionThreshold, int correctionMode,
             CharSequence quickFixedWord) {
         if (hasAutoCorrectionForTypedWord(
-                suggest, wordComposer, suggestions, typedWord, correctionMode)) {
+                dictionaries, wordComposer, suggestions, typedWord, correctionMode)) {
             mHasAutoCorrection = true;
             mAutoCorrectionWord = typedWord;
         } else if (hasAutoCorrectForBinaryDictionary(wordComposer, suggestions, correctionMode,
@@ -63,9 +64,17 @@
         }
     }
 
-    private boolean hasAutoCorrectionForTypedWord(Suggest suggest, WordComposer wordComposer,
-            ArrayList<CharSequence> suggestions, CharSequence typedWord, int correctionMode) {
-        return wordComposer.size() > 1 && suggestions.size() > 0 && suggest.isValidWord(typedWord)
+    private boolean hasAutoCorrectionForTypedWord(Collection<Dictionary> dictionaries,
+            WordComposer wordComposer, ArrayList<CharSequence> suggestions, CharSequence typedWord,
+            int correctionMode) {
+        boolean isValidWord = false;
+        for (final Dictionary dictionary : dictionaries) {
+            if (dictionary.isValidWord(typedWord)) {
+                isValidWord = true;
+                break;
+            }
+        }
+        return wordComposer.size() > 1 && suggestions.size() > 0 && isValidWord
                 && (correctionMode == Suggest.CORRECTION_FULL
                 || correctionMode == Suggest.CORRECTION_FULL_BIGRAM);
     }
diff --git a/java/src/com/android/inputmethod/latin/ExpandableDictionary.java b/java/src/com/android/inputmethod/latin/ExpandableDictionary.java
index f2305f1..0318175 100644
--- a/java/src/com/android/inputmethod/latin/ExpandableDictionary.java
+++ b/java/src/com/android/inputmethod/latin/ExpandableDictionary.java
@@ -225,7 +225,7 @@
     /**
      * Returns the word's frequency or -1 if not found
      */
-    public int getWordFrequency(CharSequence word) {
+    protected int getWordFrequency(CharSequence word) {
         Node node = searchNode(mRoots, word, 0, word.length());
         return (node == null) ? -1 : node.mFrequency;
     }
diff --git a/java/src/com/android/inputmethod/latin/LatinIME.java b/java/src/com/android/inputmethod/latin/LatinIME.java
index 646de66..c66cb5f 100644
--- a/java/src/com/android/inputmethod/latin/LatinIME.java
+++ b/java/src/com/android/inputmethod/latin/LatinIME.java
@@ -1427,7 +1427,6 @@
 
     private void saveWordInHistory(CharSequence result) {
         if (mWord.size() <= 1) {
-            mWord.reset();
             return;
         }
         // Skip if result is null. It happens in some edge case.
diff --git a/java/src/com/android/inputmethod/latin/Suggest.java b/java/src/com/android/inputmethod/latin/Suggest.java
index 95a2d63..ec86a39 100644
--- a/java/src/com/android/inputmethod/latin/Suggest.java
+++ b/java/src/com/android/inputmethod/latin/Suggest.java
@@ -25,6 +25,10 @@
 import java.io.File;
 import java.util.ArrayList;
 import java.util.Arrays;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Map;
+import java.util.Set;
 
 /**
  * This class loads a dictionary and provides a list of suggestions for a given sequence of
@@ -70,14 +74,13 @@
     private AutoCorrection mAutoCorrection;
 
     private BinaryDictionary mMainDict;
-
-    private Dictionary mUserDictionary;
-
-    private Dictionary mAutoDictionary;
-
-    private Dictionary mContactsDictionary;
-
-    private Dictionary mUserBigramDictionary;
+    private static final String DICT_KEY_MAIN = "main";
+    private static final String DICT_KEY_CONTACTS = "contacts";
+    private static final String DICT_KEY_AUTO = "auto";
+    private static final String DICT_KEY_USER = "user";
+    private static final String DICT_KEY_USER_BIGRAM = "user_bigram";
+    private final Map<String, Dictionary> mUnigramDictionaries = new HashMap<String, Dictionary>();
+    private final Map<String, Dictionary> mBigramDictionaries = new HashMap<String, Dictionary>();
 
     private int mPrefMaxSuggestions = 12;
 
@@ -101,16 +104,19 @@
     private int mCorrectionMode = CORRECTION_BASIC;
 
     public Suggest(Context context, int dictionaryResId) {
-        mMainDict = BinaryDictionary.initDictionary(context, dictionaryResId, DIC_MAIN);
-        init();
+        init(BinaryDictionary.initDictionary(context, dictionaryResId, DIC_MAIN));
     }
 
     /* package for test */ Suggest(File dictionary, long startOffset, long length) {
-        mMainDict = BinaryDictionary.initDictionary(dictionary, startOffset, length, DIC_MAIN);
-        init();
+        init(BinaryDictionary.initDictionary(dictionary, startOffset, length, DIC_MAIN));
     }
 
-    private void init() {
+    private void init(BinaryDictionary mainDict) {
+        if (mainDict != null) {
+            mMainDict = mainDict;
+            mUnigramDictionaries.put(DICT_KEY_MAIN, mainDict);
+            mBigramDictionaries.put(DICT_KEY_MAIN, mainDict);
+        }
         mAutoCorrection = new AutoCorrection();
         initPool();
     }
@@ -147,22 +153,28 @@
      * before the main dictionary, if set.
      */
     public void setUserDictionary(Dictionary userDictionary) {
-        mUserDictionary = userDictionary;
+        if (userDictionary != null)
+            mUnigramDictionaries.put(DICT_KEY_USER, userDictionary);
     }
 
     /**
      * Sets an optional contacts dictionary resource to be loaded.
      */
-    public void setContactsDictionary(Dictionary userDictionary) {
-        mContactsDictionary = userDictionary;
+    public void setContactsDictionary(Dictionary contactsDictionary) {
+        if (contactsDictionary != null) {
+            mUnigramDictionaries.put(DICT_KEY_CONTACTS, contactsDictionary);
+            mBigramDictionaries.put(DICT_KEY_CONTACTS, contactsDictionary);
+        }
     }
 
     public void setAutoDictionary(Dictionary autoDictionary) {
-        mAutoDictionary = autoDictionary;
+        if (autoDictionary != null)
+            mUnigramDictionaries.put(DICT_KEY_AUTO, autoDictionary);
     }
 
     public void setUserBigramDictionary(Dictionary userBigramDictionary) {
-        mUserBigramDictionary = userBigramDictionary;
+        if (userBigramDictionary != null)
+            mBigramDictionaries.put(DICT_KEY_USER_BIGRAM, userBigramDictionary);
     }
 
     public void setAutoCorrectionThreshold(double threshold) {
@@ -240,14 +252,8 @@
                 if (mMainDict != null && mMainDict.isValidWord(lowerPrevWord)) {
                     prevWordForBigram = lowerPrevWord;
                 }
-                if (mUserBigramDictionary != null) {
-                    mUserBigramDictionary.getBigrams(wordComposer, prevWordForBigram, this);
-                }
-                if (mContactsDictionary != null) {
-                    mContactsDictionary.getBigrams(wordComposer, prevWordForBigram, this);
-                }
-                if (mMainDict != null) {
-                    mMainDict.getBigrams(wordComposer, prevWordForBigram, this);
+                for (final Dictionary dictionary : mBigramDictionaries.values()) {
+                    dictionary.getBigrams(wordComposer, prevWordForBigram, this);
                 }
                 char currentChar = wordComposer.getTypedWord().charAt(0);
                 char currentCharUpper = Character.toUpperCase(currentChar);
@@ -270,15 +276,13 @@
 
         } else if (wordComposer.size() > 1) {
             // At second character typed, search the unigrams (scores being affected by bigrams)
-            if (mUserDictionary != null || mContactsDictionary != null) {
-                if (mUserDictionary != null) {
-                    mUserDictionary.getWords(wordComposer, this);
-                }
-                if (mContactsDictionary != null) {
-                    mContactsDictionary.getWords(wordComposer, this);
-                }
+            for (final String key : mUnigramDictionaries.keySet()) {
+                // Skip AutoDictionary to lookup
+                if (key.equals(DICT_KEY_AUTO))
+                    continue;
+                final Dictionary dictionary = mUnigramDictionaries.get(key);
+                dictionary.getWords(wordComposer, this);
             }
-            if (mMainDict != null) mMainDict.getWords(wordComposer, this);
         }
         CharSequence autoText = null;
         final String typedWordString = typedWord == null ? null : typedWord.toString();
@@ -324,8 +328,9 @@
             }
         }
 
-        mAutoCorrection.updateAutoCorrectionStatus(this, wordComposer, mSuggestions, mPriorities,
-                typedWord, mAutoCorrectionThreshold, mCorrectionMode, autoText);
+        mAutoCorrection.updateAutoCorrectionStatus(mUnigramDictionaries.values(), wordComposer,
+                mSuggestions, mPriorities, typedWord, mAutoCorrectionThreshold, mCorrectionMode,
+                autoText);
 
         if (autoText != null) {
             mSuggestions.add(0, autoText);
@@ -515,10 +520,11 @@
         if (word == null || word.length() == 0 || mMainDict == null) {
             return false;
         }
-        return mMainDict.isValidWord(word)
-                || (mUserDictionary != null && mUserDictionary.isValidWord(word))
-                || (mAutoDictionary != null && mAutoDictionary.isValidWord(word))
-                || (mContactsDictionary != null && mContactsDictionary.isValidWord(word));
+        for (final Dictionary dictionary : mUnigramDictionaries.values()) {
+            if (dictionary.isValidWord(word))
+                return true;
+        }
+        return false;
     }
 
     private void collectGarbage(ArrayList<CharSequence> suggestions, int prefMaxSuggestions) {
@@ -539,25 +545,12 @@
     }
 
     public void close() {
-        if (mMainDict != null) {
-            mMainDict.close();
-            mMainDict = null;
+        final Set<Dictionary> dictionaries = new HashSet<Dictionary>();
+        dictionaries.addAll(mUnigramDictionaries.values());
+        dictionaries.addAll(mBigramDictionaries.values());
+        for (final Dictionary dictionary : dictionaries) {
+            dictionary.close();
         }
-        if (mUserDictionary != null) {
-            mUserDictionary.close();
-            mUserDictionary = null;
-        }
-        if (mUserBigramDictionary != null) {
-            mUserBigramDictionary.close();
-            mUserBigramDictionary = null;
-        }
-        if (mContactsDictionary != null) {
-            mContactsDictionary.close();
-            mContactsDictionary = null;
-        }
-        if (mAutoDictionary != null) {
-            mAutoDictionary.close();
-            mAutoDictionary = null;
-        }
+        mMainDict = null;
     }
 }
diff --git a/native/src/defines.h b/native/src/defines.h
index 0d1f037..16927e5 100644
--- a/native/src/defines.h
+++ b/native/src/defines.h
@@ -129,10 +129,13 @@
 #define DICTIONARY_HEADER_SIZE 2
 #define NOT_VALID_WORD -99
 
+#define KEYCODE_SPACE ' '
+
 #define SUGGEST_WORDS_WITH_MISSING_CHARACTER true
 #define SUGGEST_WORDS_WITH_MISSING_SPACE_CHARACTER true
 #define SUGGEST_WORDS_WITH_EXCESSIVE_CHARACTER true
 #define SUGGEST_WORDS_WITH_TRANSPOSED_CHARACTERS true
+#define SUGGEST_WORDS_WITH_SPACE_PROXIMITY true
 
 // The following "rate"s are used as a multiplier before dividing by 100, so they are in percent.
 #define WORDS_WITH_MISSING_CHARACTER_DEMOTION_RATE 100
@@ -148,6 +151,10 @@
 
 #define MAX_DEPTH_MULTIPLIER 3
 
+// TODO: Reduce this constant if possible; check the maximum number of umlauts in the same German
+// word in the dictionary
+#define DEFAULT_MAX_UMLAUT_SEARCH_DEPTH 5
+
 // Minimum suggest depth for one word for all cases except for missing space suggestions.
 #define MIN_SUGGEST_DEPTH 1
 #define MIN_USER_TYPED_LENGTH_FOR_MISSING_SPACE_SUGGESTION 3
diff --git a/native/src/proximity_info.cpp b/native/src/proximity_info.cpp
index d0cba3e..5f2d09f 100644
--- a/native/src/proximity_info.cpp
+++ b/native/src/proximity_info.cpp
@@ -17,18 +17,48 @@
 #include <stdio.h>
 #include <string.h>
 
+#define LOG_TAG "LatinIME: proximity_info.cpp"
+
 #include "proximity_info.h"
 
 namespace latinime {
-ProximityInfo::ProximityInfo(int maxProximityCharsSize, int displayWidth, int displayHeight,
-        int gridWidth, int gridHeight, uint32_t const *proximityCharsArray)
-        : MAX_PROXIMITY_CHARS_SIZE(maxProximityCharsSize), DISPLAY_WIDTH(displayWidth),
-          DISPLAY_HEIGHT(displayHeight), GRID_WIDTH(gridWidth), GRID_HEIGHT(gridHeight) {
-    mProximityCharsArray = new uint32_t[GRID_WIDTH * GRID_HEIGHT * MAX_PROXIMITY_CHARS_SIZE];
-    memcpy(mProximityCharsArray, proximityCharsArray, sizeof(mProximityCharsArray));
+ProximityInfo::ProximityInfo(const int maxProximityCharsSize, const int keyboardWidth,
+        const int keyboardHeight, const int gridWidth, const int gridHeight,
+        const uint32_t *proximityCharsArray)
+        : MAX_PROXIMITY_CHARS_SIZE(maxProximityCharsSize), KEYBOARD_WIDTH(keyboardWidth),
+          KEYBOARD_HEIGHT(keyboardHeight), GRID_WIDTH(gridWidth), GRID_HEIGHT(gridHeight),
+          CELL_WIDTH((keyboardWidth + gridWidth - 1) / gridWidth),
+          CELL_HEIGHT((keyboardHeight + gridHeight - 1) / gridHeight) {
+    const int len = GRID_WIDTH * GRID_HEIGHT * MAX_PROXIMITY_CHARS_SIZE;
+    mProximityCharsArray = new uint32_t[len];
+    if (DEBUG_PROXIMITY_INFO) {
+        LOGI("Create proximity info array %d", len);
+    }
+    memcpy(mProximityCharsArray, proximityCharsArray, len * sizeof(mProximityCharsArray[0]));
 }
 
 ProximityInfo::~ProximityInfo() {
     delete[] mProximityCharsArray;
 }
+
+inline int ProximityInfo::getStartIndexFromCoordinates(const int x, const int y) const {
+    return (y / CELL_HEIGHT) * GRID_WIDTH + (x / CELL_WIDTH)
+            * MAX_PROXIMITY_CHARS_SIZE;
 }
+
+bool ProximityInfo::hasSpaceProximity(const int x, const int y) const {
+    const int startIndex = getStartIndexFromCoordinates(x, y);
+    if (DEBUG_PROXIMITY_INFO) {
+        LOGI("hasSpaceProximity: index %d", startIndex);
+    }
+    for (int i = 0; i < MAX_PROXIMITY_CHARS_SIZE; ++i) {
+        if (DEBUG_PROXIMITY_INFO) {
+            LOGI("Index: %d", mProximityCharsArray[startIndex + i]);
+        }
+        if (mProximityCharsArray[startIndex + i] == KEYCODE_SPACE) {
+            return true;
+        }
+    }
+    return false;
+}
+}  // namespace latinime
diff --git a/native/src/proximity_info.h b/native/src/proximity_info.h
index 54e9950..0f12018 100644
--- a/native/src/proximity_info.h
+++ b/native/src/proximity_info.h
@@ -25,15 +25,20 @@
 
 class ProximityInfo {
 public:
-    ProximityInfo(int maxProximityCharsSize, int displayWidth, int displayHeight, int gridWidth,
-            int gridHeight, uint32_t const *proximityCharsArray);
+    ProximityInfo(const int maxProximityCharsSize, const int keyboardWidth,
+            const int keybaordHeight, const int gridWidth, const int gridHeight,
+            const uint32_t *proximityCharsArray);
     ~ProximityInfo();
+    bool hasSpaceProximity(const int x, const int y) const;
 private:
-    const int MAX_PROXIMITY_CHARS_SIZE;
-    const int DISPLAY_WIDTH;
-    const int DISPLAY_HEIGHT;
+    int getStartIndexFromCoordinates(const int x, const int y) const;
+    const int CELL_WIDTH;
+    const int CELL_HEIGHT;
+    const int KEYBOARD_WIDTH;
+    const int KEYBOARD_HEIGHT;
     const int GRID_WIDTH;
     const int GRID_HEIGHT;
+    const int MAX_PROXIMITY_CHARS_SIZE;
     uint32_t *mProximityCharsArray;
 };
 }; // namespace latinime
diff --git a/native/src/unigram_dictionary.cpp b/native/src/unigram_dictionary.cpp
index 17a87a7..3487d4f 100644
--- a/native/src/unigram_dictionary.cpp
+++ b/native/src/unigram_dictionary.cpp
@@ -41,7 +41,8 @@
     MAX_PROXIMITY_CHARS(maxProximityChars), IS_LATEST_DICT_VERSION(isLatestDictVersion),
     TYPED_LETTER_MULTIPLIER(typedLetterMultiplier), FULL_WORD_MULTIPLIER(fullWordMultiplier),
     ROOT_POS(isLatestDictVersion ? DICTIONARY_HEADER_SIZE : 0),
-    BYTES_IN_ONE_CHAR(MAX_PROXIMITY_CHARS * sizeof(*mInputCodes)) {
+    BYTES_IN_ONE_CHAR(MAX_PROXIMITY_CHARS * sizeof(*mInputCodes)),
+    MAX_UMLAUT_SEARCH_DEPTH(DEFAULT_MAX_UMLAUT_SEARCH_DEPTH) {
     if (DEBUG_DICT) LOGI("UnigramDictionary - constructor");
 }
 
@@ -80,30 +81,37 @@
 void UnigramDictionary::getWordWithDigraphSuggestionsRec(const ProximityInfo *proximityInfo,
         const int *xcoordinates, const int* ycoordinates, const int *codesBuffer,
         const int codesBufferSize, const int flags, const int* codesSrc, const int codesRemain,
-        int* codesDest, unsigned short* outWords, int* frequencies) {
+        int currentDepth, int* codesDest, unsigned short* outWords, int* frequencies) {
 
-    for (int i = 0; i < codesRemain; ++i) {
-        if (isDigraph(codesSrc, i, codesRemain)) {
-            // Found a digraph. We will try both spellings. eg. the word is "pruefen"
+    if (currentDepth < MAX_UMLAUT_SEARCH_DEPTH) {
+        for (int i = 0; i < codesRemain; ++i) {
+            if (isDigraph(codesSrc, i, codesRemain)) {
+                // Found a digraph. We will try both spellings. eg. the word is "pruefen"
 
-            // Copy the word up to the first char of the digraph, then continue processing
-            // on the remaining part of the word, skipping the second char of the digraph.
-            // In our example, copy "pru" and continue running on "fen"
-            memcpy(codesDest, codesSrc, i * BYTES_IN_ONE_CHAR);
-            getWordWithDigraphSuggestionsRec(proximityInfo, xcoordinates, ycoordinates, codesBuffer,
-                    codesBufferSize, flags, codesSrc + (i + 1) * MAX_PROXIMITY_CHARS,
-                    codesRemain - i - 1, codesDest + i * MAX_PROXIMITY_CHARS,
-                    outWords, frequencies);
+                // Copy the word up to the first char of the digraph, then continue processing
+                // on the remaining part of the word, skipping the second char of the digraph.
+                // In our example, copy "pru" and continue running on "fen"
+                // Make i the index of the second char of the digraph for simplicity. Forgetting
+                // to do that results in an infinite recursion so take care!
+                ++i;
+                memcpy(codesDest, codesSrc, i * BYTES_IN_ONE_CHAR);
+                getWordWithDigraphSuggestionsRec(proximityInfo, xcoordinates, ycoordinates,
+                        codesBuffer, codesBufferSize, flags,
+                        codesSrc + (i + 1) * MAX_PROXIMITY_CHARS, codesRemain - i - 1,
+                        currentDepth + 1, codesDest + i * MAX_PROXIMITY_CHARS, outWords,
+                        frequencies);
 
-            // Copy the second char of the digraph in place, then continue processing on
-            // the remaining part of the word.
-            // In our example, after "pru" in the buffer copy the "e", and continue running on "fen"
-            memcpy(codesDest + i * MAX_PROXIMITY_CHARS, codesSrc + i * MAX_PROXIMITY_CHARS,
-                    BYTES_IN_ONE_CHAR);
-            getWordWithDigraphSuggestionsRec(proximityInfo, xcoordinates, ycoordinates, codesBuffer,
-                    codesBufferSize, flags, codesSrc + i * MAX_PROXIMITY_CHARS, codesRemain - i,
-                    codesDest + i * MAX_PROXIMITY_CHARS, outWords, frequencies);
-            return;
+                // Copy the second char of the digraph in place, then continue processing on
+                // the remaining part of the word.
+                // In our example, after "pru" in the buffer copy the "e", and continue on "fen"
+                memcpy(codesDest + i * MAX_PROXIMITY_CHARS, codesSrc + i * MAX_PROXIMITY_CHARS,
+                        BYTES_IN_ONE_CHAR);
+                getWordWithDigraphSuggestionsRec(proximityInfo, xcoordinates, ycoordinates,
+                        codesBuffer, codesBufferSize, flags, codesSrc + i * MAX_PROXIMITY_CHARS,
+                        codesRemain - i, currentDepth + 1, codesDest + i * MAX_PROXIMITY_CHARS,
+                        outWords, frequencies);
+                return;
+            }
         }
     }
 
@@ -128,13 +136,13 @@
     { // Incrementally tune the word and try all possibilities
         int codesBuffer[getCodesBufferSize(codes, codesSize, MAX_PROXIMITY_CHARS)];
         getWordWithDigraphSuggestionsRec(proximityInfo, xcoordinates, ycoordinates, codesBuffer,
-                codesSize, flags, codes, codesSize, codesBuffer, outWords, frequencies);
+                codesSize, flags, codes, codesSize, 0, codesBuffer, outWords, frequencies);
     } else { // Normal processing
         getWordSuggestions(proximityInfo, xcoordinates, ycoordinates, codes, codesSize,
                 outWords, frequencies);
     }
 
-    PROF_START(6);
+    PROF_START(20);
     // Get the word count
     int suggestedWordsCount = 0;
     while (suggestedWordsCount < MAX_WORDS && mFrequencies[suggestedWordsCount] > 0) {
@@ -150,7 +158,7 @@
             }
         }
     }
-    PROF_END(6);
+    PROF_END(20);
     PROF_CLOSE;
     return suggestedWordsCount;
 }
@@ -164,12 +172,6 @@
     initSuggestions(codes, codesSize, outWords, frequencies);
     if (DEBUG_DICT) assert(codesSize == mInputLength);
 
-    if (DEBUG_PROXIMITY_INFO) {
-        for (int i = 0; i < codesSize; ++i) {
-            LOGI("Input[%d] x = %d, y = %d", i, xcoordinates[i], ycoordinates[i]);
-        }
-    }
-
     const int MAX_DEPTH = min(mInputLength * MAX_DEPTH_MULTIPLIER, MAX_WORD_LENGTH);
     PROF_END(0);
 
@@ -219,6 +221,25 @@
         }
     }
     PROF_END(5);
+
+    PROF_START(6);
+    if (SUGGEST_WORDS_WITH_SPACE_PROXIMITY) {
+        // The first and last "mistyped spaces" are taken care of by excessive character handling
+        for (int i = 1; i < codesSize - 1; ++i) {
+            if (DEBUG_DICT) LOGI("--- Suggest words with proximity space %d", i);
+            const int x = xcoordinates[i];
+            const int y = ycoordinates[i];
+            if (DEBUG_PROXIMITY_INFO)
+                LOGI("Input[%d] x = %d, y = %d, has space proximity = %d",
+                        i, x, y, proximityInfo->hasSpaceProximity(x, y));
+
+            if (proximityInfo->hasSpaceProximity(x, y)) {
+                getMistypedSpaceWords(mInputLength, i);
+            }
+
+        }
+    }
+    PROF_END(6);
 }
 
 void UnigramDictionary::initSuggestions(const int *codes, const int codesSize,
@@ -379,27 +400,31 @@
     }
 }
 
-bool UnigramDictionary::getMissingSpaceWords(const int inputLength, const int missingSpacePos) {
-    if (missingSpacePos <= 0 || missingSpacePos >= inputLength
-            || inputLength >= MAX_WORD_LENGTH) return false;
-    const int newWordLength = inputLength + 1;
+bool UnigramDictionary::getSplitTwoWordsSuggestion(const int inputLength,
+        const int firstWordStartPos, const int firstWordLength, const int secondWordStartPos,
+        const int secondWordLength) {
+    if (inputLength >= MAX_WORD_LENGTH) return false;
+    if (0 >= firstWordLength || 0 >= secondWordLength || firstWordStartPos >= secondWordStartPos
+            || firstWordStartPos < 0 || secondWordStartPos >= inputLength)
+        return false;
+    const int newWordLength = firstWordLength + secondWordLength + 1;
     // Allocating variable length array on stack
     unsigned short word[newWordLength];
-    const int firstFreq = getBestWordFreq(0, missingSpacePos, mWord);
+    const int firstFreq = getBestWordFreq(firstWordStartPos, firstWordLength, mWord);
     if (DEBUG_DICT) LOGI("First freq: %d", firstFreq);
     if (firstFreq <= 0) return false;
 
-    for (int i = 0; i < missingSpacePos; ++i) {
+    for (int i = 0; i < firstWordLength; ++i) {
         word[i] = mWord[i];
     }
 
-    const int secondFreq = getBestWordFreq(missingSpacePos, inputLength - missingSpacePos, mWord);
+    const int secondFreq = getBestWordFreq(secondWordStartPos, secondWordLength, mWord);
     if (DEBUG_DICT) LOGI("Second  freq:  %d", secondFreq);
     if (secondFreq <= 0) return false;
 
-    word[missingSpacePos] = SPACE;
-    for (int i = (missingSpacePos + 1); i < newWordLength; ++i) {
-        word[i] = mWord[i - missingSpacePos - 1];
+    word[firstWordLength] = SPACE;
+    for (int i = (firstWordLength + 1); i < newWordLength; ++i) {
+        word[i] = mWord[i - firstWordLength - 1];
     }
 
     int pairFreq = ((firstFreq + secondFreq) / 2);
@@ -409,6 +434,17 @@
     return true;
 }
 
+bool UnigramDictionary::getMissingSpaceWords(const int inputLength, const int missingSpacePos) {
+    return getSplitTwoWordsSuggestion(
+            inputLength, 0, missingSpacePos, missingSpacePos, inputLength - missingSpacePos);
+}
+
+bool UnigramDictionary::getMistypedSpaceWords(const int inputLength, const int spaceProximityPos) {
+    return getSplitTwoWordsSuggestion(
+            inputLength, 0, spaceProximityPos, spaceProximityPos + 1,
+            inputLength - spaceProximityPos - 1);
+}
+
 // Keep this for comparing spec to new getWords
 void UnigramDictionary::getWordsOld(const int initialPos, const int inputLength, const int skipPos,
         const int excessivePos, const int transposedPos,int *nextLetters,
diff --git a/native/src/unigram_dictionary.h b/native/src/unigram_dictionary.h
index 2912cb0..ef820cb 100644
--- a/native/src/unigram_dictionary.h
+++ b/native/src/unigram_dictionary.h
@@ -46,7 +46,7 @@
     void getWordWithDigraphSuggestionsRec(const ProximityInfo *proximityInfo,
         const int *xcoordinates, const int* ycoordinates, const int *codesBuffer,
         const int codesBufferSize, const int flags, const int* codesSrc, const int codesRemain,
-        int* codesDest, unsigned short* outWords, int* frequencies);
+        int currentDepth, int* codesDest, unsigned short* outWords, int* frequencies);
     void initSuggestions(const int *codes, const int codesSize, unsigned short *outWords,
             int *frequencies);
     void getSuggestionCandidates(const int skipPos, const int excessivePos,
@@ -64,7 +64,11 @@
             const bool traverseAllNodes, const int snr, const int inputIndex, const int diffs,
             const int skipPos, const int excessivePos, const int transposedPos, int *nextLetters,
             const int nextLettersSize);
+    bool getSplitTwoWordsSuggestion(const int inputLength,
+            const int firstWordStartPos, const int firstWordLength,
+            const int secondWordStartPos, const int secondWordLength);
     bool getMissingSpaceWords(const int inputLength, const int missingSpacePos);
+    bool getMistypedSpaceWords(const int inputLength, const int spaceProximityPos);
     // Keep getWordsOld for comparing performance between getWords and getWordsOld
     void getWordsOld(const int initialPos, const int inputLength, const int skipPos,
             const int excessivePos, const int transposedPos, int *nextLetters,
@@ -109,6 +113,7 @@
     const int FULL_WORD_MULTIPLIER;
     const int ROOT_POS;
     const unsigned int BYTES_IN_ONE_CHAR;
+    const unsigned int MAX_UMLAUT_SEARCH_DEPTH;
 
     // Flags for special processing
     // Those *must* match the flags in BinaryDictionary.Flags.ALL_FLAGS in BinaryDictionary.java
diff --git a/tests/src/com/android/inputmethod/latin/SuggestHelper.java b/tests/src/com/android/inputmethod/latin/SuggestHelper.java
index a845acb..ed01a75 100644
--- a/tests/src/com/android/inputmethod/latin/SuggestHelper.java
+++ b/tests/src/com/android/inputmethod/latin/SuggestHelper.java
@@ -73,6 +73,7 @@
                 final int[] codes = mKeyDetector.newCodeArray();
                 mKeyDetector.getKeyIndexAndNearbyCodes(x, y, codes);
                 word.add(c, codes, x, y);
+                return;
             }
         }
         word.add(c, new int[] { c }, WordComposer.NOT_A_COORDINATE, WordComposer.NOT_A_COORDINATE);