Merge commit 'goog/master'

Conflicts:
	java/src/com/android/inputmethod/latin/LatinIME.java
diff --git a/java/res/values-ko/strings.xml b/java/res/values-ko/strings.xml
index fbfdd4c..414a149 100644
--- a/java/res/values-ko/strings.xml
+++ b/java/res/values-ko/strings.xml
@@ -23,7 +23,7 @@
     <string name="english_ime_name" msgid="7252517407088836577">"Android 키보드 2"</string>
     <string name="english_ime_settings" msgid="6661589557206947774">"Android 키보드 2 설정"</string>
     <string name="vibrate_on_keypress" msgid="5258079494276955460">"키를 누를 때 진동 발생"</string>
-    <string name="sound_on_keypress" msgid="6093592297198243644">"버튼을 누를 때 소리 발생"</string>
+    <string name="sound_on_keypress" msgid="6093592297198243644">"키를 누를 때 소리 발생"</string>
     <string name="hit_correction" msgid="4855351009261318389">"입력 오류 수정"</string>
     <string name="hit_correction_summary" msgid="8761701873008070796">"입력 오류 수정 사용"</string>
     <string name="hit_correction_land" msgid="2567691684825205448">"가로 입력 오류"</string>
@@ -87,7 +87,7 @@
     <string name="voice_warning_may_not_understand" msgid="4611518823070986445">"음성 입력은 Google의 네트워크화된 음성 인식을 사용하는 실험적 기능입니다."</string>
     <string name="voice_warning_how_to_turn_off" msgid="5652369578498701761">"음성 입력을 사용하지 않으려면 키보드 설정으로 이동하세요."</string>
     <string name="voice_hint_dialog_message" msgid="6892342981545727994">"음성 입력을 사용하려면 마이크 버튼을 누르거나 터치 키보드 위로 손가락을 미끄러지듯 움직이세요."</string>
-    <string name="voice_listening" msgid="467518160751321844">"지금 시작하세요."</string>
+    <string name="voice_listening" msgid="467518160751321844">"지금 말하세요."</string>
     <string name="voice_working" msgid="6666937792815731889">"인식 중"</string>
     <!-- no translation found for voice_initializing (661962047129906646) -->
     <skip />
diff --git a/java/res/xml-ru/kbd_qwerty.xml b/java/res/xml-ru/kbd_qwerty.xml
index cbb518f..9773a30 100755
--- a/java/res/xml-ru/kbd_qwerty.xml
+++ b/java/res/xml-ru/kbd_qwerty.xml
@@ -70,7 +70,9 @@
         <Key android:keyLabel="м"/>
         <Key android:keyLabel="и"/>
         <Key android:keyLabel="т"/>
-        <Key android:keyLabel="ь"/>
+        <Key android:keyLabel="ь"
+                android:popupKeyboard="@xml/kbd_popup_template"
+                android:popupCharacters="ъ" />
         <Key android:keyLabel="б"/>
         <Key android:keyLabel="ю"/>
         <Key android:codes="-5" android:keyIcon="@drawable/sym_keyboard_delete"
diff --git a/java/src/com/android/inputmethod/voice/EditingUtil.java b/java/src/com/android/inputmethod/latin/EditingUtil.java
similarity index 78%
rename from java/src/com/android/inputmethod/voice/EditingUtil.java
rename to java/src/com/android/inputmethod/latin/EditingUtil.java
index 6316d8c..7571f1d 100644
--- a/java/src/com/android/inputmethod/voice/EditingUtil.java
+++ b/java/src/com/android/inputmethod/latin/EditingUtil.java
@@ -14,7 +14,7 @@
  * the License.
  */
 
-package com.android.inputmethod.voice;
+package com.android.inputmethod.latin;
 
 import android.view.inputmethod.ExtractedText;
 import android.view.inputmethod.ExtractedTextRequest;
@@ -75,9 +75,21 @@
      *   represents the cursor, then "hello " will be returned.
      */
     public static String getWordAtCursor(
-        InputConnection connection, String separators) {
-        Range range = getWordRangeAtCursor(connection, separators);
-        return (range == null) ? null : range.word;
+            InputConnection connection, String separators) {
+        return getWordAtCursor(connection, separators, null);
+    }
+
+    /**
+     * @param connection connection to the current text field.
+     * @param sep characters which may separate words
+     * @return the word that surrounds the cursor, including up to one trailing
+     *   separator. For example, if the field contains "he|llo world", where |
+     *   represents the cursor, then "hello " will be returned.
+     */
+    public static String getWordAtCursor(
+        InputConnection connection, String separators, Range range) {
+        Range r = getWordRangeAtCursor(connection, separators, range);
+        return (r == null) ? null : r.word;
     }
 
     /**
@@ -87,7 +99,7 @@
     public static void deleteWordAtCursor(
         InputConnection connection, String separators) {
 
-        Range range = getWordRangeAtCursor(connection, separators);
+        Range range = getWordRangeAtCursor(connection, separators, null);
         if (range == null) return;
 
         connection.finishComposingText();
@@ -101,18 +113,20 @@
     /**
      * Represents a range of text, relative to the current cursor position.
      */
-    private static class Range {
+    public static class Range {
         /** Characters before selection start */
-        int charsBefore;
+        public int charsBefore;
 
         /**
          * Characters after selection start, including one trailing word
          * separator.
          */
-        int charsAfter;
+        public int charsAfter;
 
         /** The actual characters that make up a word */
-        String word;
+        public String word;
+
+        public Range() {}
 
         public Range(int charsBefore, int charsAfter, String word) {
             if (charsBefore < 0 || charsAfter < 0) {
@@ -125,7 +139,7 @@
     }
 
     private static Range getWordRangeAtCursor(
-        InputConnection connection, String sep) {
+            InputConnection connection, String sep, Range range) {
         if (connection == null || sep == null) {
             return null;
         }
@@ -137,20 +151,22 @@
 
         // Find first word separator before the cursor
         int start = before.length();
-        while (--start > 0 && !isWhitespace(before.charAt(start - 1), sep));
+        while (start > 0 && !isWhitespace(before.charAt(start - 1), sep)) start--;
 
         // Find last word separator after the cursor
         int end = -1;
         while (++end < after.length() && !isWhitespace(after.charAt(end), sep));
-        if (end < after.length() - 1) {
-            end++; // Include trailing space, if it exists, in word
-        }
 
         int cursor = getCursorPosition(connection);
         if (start >= 0 && cursor + end <= after.length() + before.length()) {
             String word = before.toString().substring(start, before.length())
-                + after.toString().substring(0, end);
-            return new Range(before.length() - start, end, word);
+                    + after.toString().substring(0, end);
+
+            Range returnRange = range != null? range : new Range();
+            returnRange.charsBefore = before.length() - start;
+            returnRange.charsAfter = end;
+            returnRange.word = word;
+            return returnRange;
         }
 
         return null;
diff --git a/java/src/com/android/inputmethod/latin/LatinIME.java b/java/src/com/android/inputmethod/latin/LatinIME.java
index 6af8c4f..756cd23 100644
--- a/java/src/com/android/inputmethod/latin/LatinIME.java
+++ b/java/src/com/android/inputmethod/latin/LatinIME.java
@@ -16,7 +16,6 @@
 
 package com.android.inputmethod.latin;
 
-import com.android.inputmethod.voice.EditingUtil;
 import com.android.inputmethod.voice.FieldContext;
 import com.android.inputmethod.voice.SettingsUtil;
 import com.android.inputmethod.voice.VoiceInput;
@@ -127,6 +126,7 @@
     private static final int MSG_UPDATE_SHIFT_STATE = 2;
     private static final int MSG_VOICE_RESULTS = 3;
     private static final int MSG_START_LISTENING_AFTER_SWIPE = 4;
+    private static final int MSG_UPDATE_OLD_SUGGESTIONS = 5;
 
     // If we detect a swipe gesture within N ms of typing, then swipe is
     // ignored, since it may in fact be two key presses in quick succession.
@@ -204,6 +204,12 @@
     private boolean mVoiceOnPrimary;
     private int     mOrientation;
     private List<CharSequence> mSuggestPuncList;
+    // Keep track of the last selection range to decide if we need to show word alternatives
+    private int     mLastSelectionStart;
+    private int     mLastSelectionEnd;
+
+    // Input type is such that we should not auto-correct
+    private boolean mInputTypeNoAutoCorrect;
 
     // Indicates whether the suggestion strip is to be on in landscape
     private boolean mJustAccepted;
@@ -228,17 +234,66 @@
 
     // Keeps track of most recently inserted text (multi-character key) for reverting
     private CharSequence mEnteredText;
+    private boolean mRefreshKeyboardRequired;
 
     // For each word, a list of potential replacements, usually from voice.
     private Map<String, List<CharSequence>> mWordToSuggestions =
             new HashMap<String, List<CharSequence>>();
 
+    private ArrayList<WordAlternatives> mWordHistory = new ArrayList<WordAlternatives>();
+
     private class VoiceResults {
         List<String> candidates;
         Map<String, List<CharSequence>> alternatives;
     }
+    
+    public abstract static class WordAlternatives {
+        protected CharSequence mChosenWord;
 
-    private boolean mRefreshKeyboardRequired;
+        public WordAlternatives() {
+            // Nothing
+        }
+
+        public WordAlternatives(CharSequence chosenWord) {
+            mChosenWord = chosenWord;
+        }
+
+        @Override
+        public int hashCode() {
+            return mChosenWord.hashCode();
+        }
+
+        public abstract CharSequence getOriginalWord();
+
+        public CharSequence getChosenWord() {
+            return mChosenWord;
+        }
+
+        public abstract List<CharSequence> getAlternatives();
+    }
+
+    public class TypedWordAlternatives extends WordAlternatives {
+        private WordComposer word;
+
+        public TypedWordAlternatives() {
+            // Nothing
+        }
+
+        public TypedWordAlternatives(CharSequence chosenWord, WordComposer wordComposer) {
+            super(chosenWord);
+            word = wordComposer;
+        }
+
+        @Override
+        public CharSequence getOriginalWord() {
+            return word.getTypedWord();
+        }
+
+        @Override
+        public List<CharSequence> getAlternatives() {
+            return getTypedSuggestions(word);
+        }
+    }
 
     Handler mHandler = new Handler() {
         @Override
@@ -247,6 +302,9 @@
                 case MSG_UPDATE_SUGGESTIONS:
                     updateSuggestions();
                     break;
+                case MSG_UPDATE_OLD_SUGGESTIONS:
+                    setOldSuggestions();
+                    break;
                 case MSG_START_TUTORIAL:
                     if (mTutorial == null) {
                         if (mKeyboardSwitcher.getInputView().isShown()) {
@@ -467,7 +525,6 @@
         mShowingVoiceSuggestions = false;
         mImmediatelyAfterVoiceSuggestions = false;
         mVoiceInputHighlighted = false;
-        mWordToSuggestions.clear();
         mInputTypeNoAutoCorrect = false;
         mPredictionOn = false;
         mCompletionOn = false;
@@ -625,10 +682,11 @@
         // clear whatever candidate text we have.
         if ((((mComposing.length() > 0 && mPredicting) || mVoiceInputHighlighted)
                 && (newSelStart != candidatesEnd
-                    || newSelEnd != candidatesEnd))) {
+                    || newSelEnd != candidatesEnd)
+                && mLastSelectionStart != newSelStart)) {
             mComposing.setLength(0);
             mPredicting = false;
-            updateSuggestions();
+            postUpdateSuggestions();
             TextEntryState.reset();
             InputConnection ic = getCurrentInputConnection();
             if (ic != null) {
@@ -648,26 +706,18 @@
         mJustAccepted = false;
         postUpdateShiftKeyState();
 
-        if (VOICE_INSTALLED) {
-            if (mShowingVoiceSuggestions) {
-                if (mImmediatelyAfterVoiceSuggestions) {
-                    mImmediatelyAfterVoiceSuggestions = false;
-                } else {
-                    updateSuggestions();
-                    mShowingVoiceSuggestions = false;
-                }
-            }
-            if (VoiceInput.ENABLE_WORD_CORRECTIONS) {
-                // If we have alternatives for the current word, then show them.
-                String word = EditingUtil.getWordAtCursor(
-                        getCurrentInputConnection(), getWordSeparators());
-                if (word != null && mWordToSuggestions.containsKey(word.trim())) {
-                    mSuggestionShouldReplaceCurrentWord = true;
-                    final List<CharSequence> suggestions = mWordToSuggestions.get(word.trim());
+        // Make a note of the cursor position
+        mLastSelectionStart = newSelStart;
+        mLastSelectionEnd = newSelEnd;
 
-                    setSuggestions(suggestions, false, true, true);
-                    setCandidatesViewShown(true);
-                }
+
+        // If a word is selected
+        if ((candidatesStart == candidatesEnd || newSelStart != oldSelStart)
+                && (newSelStart < newSelEnd - 1 || (!mPredicting))
+                && !mVoiceInputHighlighted) {
+            abortCorrection(false);
+            if (isCursorTouchingWord() || mLastSelectionStart < mLastSelectionEnd) {
+                postUpdateOldSuggestions();
             }
         }
     }
@@ -692,6 +742,8 @@
                 mVoiceInput.cancel();
             }
         }
+        mWordToSuggestions.clear();
+        mWordHistory.clear();
         super.hideWindow();
         TextEntryState.endSession();
     }
@@ -1035,6 +1087,7 @@
         }
         InputConnection ic = getCurrentInputConnection();
         if (ic == null) return;
+        abortCorrection(false);
         ic.beginBatchEdit();
         if (mPredicting) {
             commitTyped(ic);
@@ -1115,6 +1168,13 @@
         }
     }
 
+    private void abortCorrection(boolean force) {
+        if (force || TextEntryState.isCorrecting()) {
+            getCurrentInputConnection().finishComposingText();
+            setSuggestions(null, false, false, false);
+        }
+    }
+
     private void handleCharacter(int primaryCode, int[] keyCodes) {
         if (VOICE_INSTALLED && mVoiceInputHighlighted) {
             commitVoiceInput();
@@ -1124,11 +1184,13 @@
             // Assume input length is 1. This assumption fails for smiley face insertions.
             mVoiceInput.incrementTextModificationInsertCount(1);
         }
+        abortCorrection(false);
 
         if (isAlphabet(primaryCode) && isPredictionOn() && !isCursorTouchingWord()) {
             if (!mPredicting) {
                 mPredicting = true;
                 mComposing.setLength(0);
+                saveWordInHistory(mBestWord);
                 mWord.reset();
             }
         }
@@ -1184,6 +1246,7 @@
         InputConnection ic = getCurrentInputConnection();
         if (ic != null) {
             ic.beginBatchEdit();
+            abortCorrection(false);
         }
         if (mPredicting) {
             // In certain languages where single quote is a separator, it's better
@@ -1222,7 +1285,6 @@
                 && primaryCode != KEYCODE_ENTER) {
             swapPunctuationAndSpace();
         } else if (isPredictionOn() && primaryCode == KEYCODE_SPACE) {
-        //else if (TextEntryState.STATE_SPACE_AFTER_ACCEPTED) {
             doubleSpace();
         }
         if (pickedDefault) {
@@ -1244,6 +1306,17 @@
         TextEntryState.endSession();
     }
 
+    private void saveWordInHistory(CharSequence result) {
+        if (mWord.size() <= 1) {
+            mWord.reset();
+            return;
+        }
+        TypedWordAlternatives entry = new TypedWordAlternatives(result, mWord);
+        // Create a new WordComposer as the old one is being saved for later use
+        mWord = new WordComposer(mWord);
+        mWordHistory.add(entry);
+    }
+
     private void checkToggleCapsLock() {
         if (mKeyboardSwitcher.getInputView().getKeyboard().isShifted()) {
             toggleCapsLock();
@@ -1263,6 +1336,11 @@
         mHandler.sendMessageDelayed(mHandler.obtainMessage(MSG_UPDATE_SUGGESTIONS), 100);
     }
 
+    private void postUpdateOldSuggestions() {
+        mHandler.removeMessages(MSG_UPDATE_OLD_SUGGESTIONS);
+        mHandler.sendMessageDelayed(mHandler.obtainMessage(MSG_UPDATE_OLD_SUGGESTIONS), 300);
+    }
+
     private boolean isPredictionOn() {
         boolean predictionOn = mPredictionOn;
         return predictionOn;
@@ -1447,9 +1525,6 @@
         // Show N-Best alternates, if there is more than one choice.
         if (nBest.size() > 1) {
             mImmediatelyAfterVoiceSuggestions = true;
-            mShowingVoiceSuggestions = true;
-            setSuggestions(nBest.subList(1, nBest.size()), false, true, true);
-            setCandidatesViewShown(true);
         }
         mVoiceInputHighlighted = true;
         mWordToSuggestions.putAll(mVoiceResults.alternatives);
@@ -1489,17 +1564,32 @@
             setNextSuggestions();
             return;
         }
+        showSuggestions(mWord);
+    }
 
-        List<CharSequence> stringList = mSuggest.getSuggestions(inputView,
-                mWord, false);
+    private List<CharSequence> getTypedSuggestions(WordComposer word) {
+        List<CharSequence> stringList = mSuggest.getSuggestions(
+                mKeyboardSwitcher.getInputView(), word, false);
+        return stringList;
+    }
+
+    private void showCorrections(WordAlternatives alternatives) {
+        List<CharSequence> stringList = alternatives.getAlternatives();
+        ((LatinKeyboard) mKeyboardSwitcher.getInputView().getKeyboard()).setPreferredLetters(null);
+        showSuggestions(stringList, alternatives.getOriginalWord(), false, false);
+    }
+
+    private void showSuggestions(WordComposer word) {
+        List<CharSequence> stringList = mSuggest.getSuggestions(
+                mKeyboardSwitcher.getInputView(), word, false);
         int[] nextLettersFrequencies = mSuggest.getNextLettersFrequencies();
 
-        ((LatinKeyboard) inputView.getKeyboard()).setPreferredLetters(
+        ((LatinKeyboard) mKeyboardSwitcher.getInputView().getKeyboard()).setPreferredLetters(
                 nextLettersFrequencies);
 
         boolean correctionAvailable = !mInputTypeNoAutoCorrect && mSuggest.hasMinimalCorrection();
         //|| mCorrectionMode == mSuggest.CORRECTION_FULL;
-        CharSequence typedWord = mWord.getTypedWord();
+        CharSequence typedWord = word.getTypedWord();
         // If we're in basic correct
         boolean typedWordValid = mSuggest.isValidWord(typedWord) ||
                 (preferCapitalization()
@@ -1508,8 +1598,14 @@
             correctionAvailable |= typedWordValid;
         }
         // Don't auto-correct words with multiple capital letter
-        correctionAvailable &= !mWord.isMostlyCaps();
+        correctionAvailable &= !word.isMostlyCaps();
+        correctionAvailable &= !TextEntryState.isCorrecting();
 
+        showSuggestions(stringList, typedWord, typedWordValid, correctionAvailable);
+    }
+
+    private void showSuggestions(List<CharSequence> stringList, CharSequence typedWord,
+            boolean typedWordValid, boolean correctionAvailable) {
         setSuggestions(stringList, false, typedWordValid, correctionAvailable);
         if (stringList.size() > 0) {
             if (correctionAvailable && !typedWordValid && stringList.size() > 1) {
@@ -1550,6 +1646,7 @@
             mVoiceInput.logTextModifiedByChooseSuggestion(suggestion.length());
         }
 
+        final boolean correcting = TextEntryState.isCorrecting();
         InputConnection ic = getCurrentInputConnection();
         if (ic != null) {
             ic.beginBatchEdit();
@@ -1594,10 +1691,11 @@
                 index, suggestions);
         TextEntryState.acceptedSuggestion(mComposing.toString(), suggestion);
         // Follow it with a space
-        if (mAutoSpace) {
+        if (mAutoSpace && !correcting) {
             sendSpace();
             mJustAddedAutoSpace = true;
         }
+
         // Fool the state watcher so that a subsequent backspace will not do a revert
         TextEntryState.typedCharacter((char) KEYCODE_SPACE, true);
         if (index == 0 && mCorrectionMode > 0 && !mSuggest.isValidWord(suggestion)) {
@@ -1608,6 +1706,29 @@
         }
     }
 
+    private void rememberReplacedWord(CharSequence suggestion) {
+        if (mShowingVoiceSuggestions) {
+            // Retain the replaced word in the alternatives array.
+            InputConnection ic = getCurrentInputConnection();
+            EditingUtil.Range range = new EditingUtil.Range();
+            String wordToBeReplaced = EditingUtil.getWordAtCursor(getCurrentInputConnection(),
+                                                                  mWordSeparators, range).trim();
+            if (!mWordToSuggestions.containsKey(wordToBeReplaced)) {
+              wordToBeReplaced = wordToBeReplaced.toLowerCase();
+            }
+            if (mWordToSuggestions.containsKey(wordToBeReplaced)) {
+                List<CharSequence> suggestions = mWordToSuggestions.get(wordToBeReplaced);
+                if (suggestions.contains(suggestion)) {
+                    suggestions.remove(suggestion);
+                }
+                suggestions.add(wordToBeReplaced);
+                mWordToSuggestions.remove(wordToBeReplaced);
+                mWordToSuggestions.put(suggestion.toString(), suggestions);
+            }
+        }
+        // TODO: implement rememberReplacedWord for typed words
+    }
+
     private void pickSuggestion(CharSequence suggestion) {
         LatinKeyboardView inputView = mKeyboardSwitcher.getInputView();
         if (mCapsLock) {
@@ -1620,6 +1741,7 @@
         }
         InputConnection ic = getCurrentInputConnection();
         if (ic != null) {
+            rememberReplacedWord(suggestion);
             if (mSuggestionShouldReplaceCurrentWord) {
                 EditingUtil.deleteWordAtCursor(ic, getWordSeparators());
             }
@@ -1627,6 +1749,7 @@
                 ic.commitText(suggestion, 1);
             }
         }
+        saveWordInHistory(suggestion);
         mPredicting = false;
         mCommittedLength = suggestion.length();
         ((LatinKeyboard) inputView.getKeyboard()).setPreferredLetters(null);
@@ -1634,16 +1757,128 @@
         updateShiftKeyState(getCurrentInputEditorInfo());
     }
 
+    private void setOldSuggestions() {
+        // TODO: Inefficient to check if touching word and then get the touching word. Do it
+        // in one go.
+        mShowingVoiceSuggestions = false;
+        InputConnection ic = getCurrentInputConnection();
+        if (ic == null) return;
+        ic.beginBatchEdit();
+        // If there is a selection, then undo the selection first. Unfortunately this causes
+        // a flicker. TODO: Add getSelectionText() to InputConnection API.
+        if (mLastSelectionStart < mLastSelectionEnd) {
+            ic.setSelection(mLastSelectionStart, mLastSelectionStart);
+        }
+        if (!mPredicting && isCursorTouchingWord()) {
+            EditingUtil.Range range = new EditingUtil.Range();
+            CharSequence touching =
+                EditingUtil.getWordAtCursor(getCurrentInputConnection(), mWordSeparators,
+                        range);
+            if (touching != null && touching.length() > 1) {
+                if (mWordSeparators.indexOf(touching.charAt(touching.length() - 1)) > 0) {
+                    touching = touching.toString().substring(0, touching.length() - 1);
+                }
+
+                // Search for result in spoken word alternatives
+                // TODO: possibly combine the spoken suggestions with the typed suggestions.
+                String selectedWord = touching.toString().trim();
+                if (!mWordToSuggestions.containsKey(selectedWord)){
+                    selectedWord = selectedWord.toLowerCase();
+                }
+                if (mWordToSuggestions.containsKey(selectedWord)){
+                    mShowingVoiceSuggestions = true;
+                    mSuggestionShouldReplaceCurrentWord = true;
+                    underlineWord(touching, range.charsBefore, range.charsAfter);
+                    List<CharSequence> suggestions = mWordToSuggestions.get(selectedWord);
+                    // If the first letter of touching is capitalized, make all the suggestions
+                    // start with a capital letter.
+                    if (Character.isUpperCase((char) touching.charAt(0))) {
+                        for (int i=0; i< suggestions.size(); i++) {
+                            String origSugg = (String) suggestions.get(i);
+                            String capsSugg = origSugg.toUpperCase().charAt(0)
+                                + origSugg.subSequence(1, origSugg.length()).toString();
+                            suggestions.set(i,capsSugg);
+                        }
+                    }
+                    setSuggestions(suggestions, false, true, true);
+                    setCandidatesViewShown(true);
+                    TextEntryState.selectedForCorrection();
+                    ic.endBatchEdit();
+                    return;
+                }
+                // If we didn't find a match, search for result in word history
+                WordComposer foundWord = null;
+                WordAlternatives alternatives = null;
+                for (WordAlternatives entry : mWordHistory) {
+                    if (TextUtils.equals(entry.getChosenWord(), touching)) {
+                        if (entry instanceof TypedWordAlternatives) {
+                            foundWord = ((TypedWordAlternatives)entry).word;
+                        }
+                        alternatives = entry;
+                        break;
+                    }
+                }
+                // If we didn't find a match, at least suggest completions
+                if (foundWord == null && mSuggest.isValidWord(touching)) {
+                    foundWord = new WordComposer();
+                    for (int i = 0; i < touching.length(); i++) {
+                        foundWord.add(touching.charAt(i), new int[] { touching.charAt(i) });
+                    }
+                }
+                // Found a match, show suggestions
+                if (foundWord != null || alternatives != null) {
+                    mSuggestionShouldReplaceCurrentWord = true;
+                    underlineWord(touching, range.charsBefore, range.charsAfter);
+                    TextEntryState.selectedForCorrection();
+                    if (alternatives == null) alternatives = new TypedWordAlternatives(touching,
+                            foundWord);
+                    showCorrections(alternatives);
+                    if (foundWord != null) {
+                        mWord = foundWord;
+                    } else {
+                        mWord.reset();
+                    }
+                    // Revert the selection
+                    if (mLastSelectionStart < mLastSelectionEnd) {
+                        ic.setSelection(mLastSelectionStart, mLastSelectionEnd);
+                    }
+                    ic.endBatchEdit();
+                    return;
+                }
+                abortCorrection(true);
+            } else {
+                abortCorrection(true);
+                setNextSuggestions();
+            }
+        } else {
+            abortCorrection(true);
+        }
+        // Revert the selection
+        if (mLastSelectionStart < mLastSelectionEnd) {
+            ic.setSelection(mLastSelectionStart, mLastSelectionEnd);
+        }
+        ic.endBatchEdit();
+    }
+
     private void setNextSuggestions() {
         setSuggestions(mSuggestPuncList, false, false, false);
     }
 
+    private void underlineWord(CharSequence word, int left, int right) {
+        InputConnection ic = getCurrentInputConnection();
+        if (ic == null) return;
+        ic.deleteSurroundingText(left, right);
+        ic.setComposingText(word, 1);
+        ic.setSelection(mLastSelectionStart, mLastSelectionStart);
+    }
+
     private void checkAddToDictionary(CharSequence suggestion, int frequencyDelta) {
+        if (suggestion == null || suggestion.length() < 1) return;
         // Only auto-add to dictionary if auto-correct is ON. Otherwise we'll be
         // adding words in situations where the user or application really didn't
         // want corrections enabled or learned.
         if (!(mCorrectionMode == Suggest.CORRECTION_FULL)) return;
-        if (mAutoDictionary.isValidWord(suggestion)
+        if (suggestion != null && mAutoDictionary.isValidWord(suggestion)
                 || (!mSuggest.isValidWord(suggestion.toString())
                     && !mSuggest.isValidWord(suggestion.toString().toLowerCase()))) {
             mAutoDictionary.addWord(suggestion.toString(), frequencyDelta);
@@ -2065,7 +2300,6 @@
     private static final int CPS_BUFFER_SIZE = 16;
     private long[] mCpsIntervals = new long[CPS_BUFFER_SIZE];
     private int mCpsIndex;
-    private boolean mInputTypeNoAutoCorrect;
 
     private void measureCps() {
         long now = System.currentTimeMillis();
diff --git a/java/src/com/android/inputmethod/latin/TextEntryState.java b/java/src/com/android/inputmethod/latin/TextEntryState.java
index e2f949e..1a3bb87 100644
--- a/java/src/com/android/inputmethod/latin/TextEntryState.java
+++ b/java/src/com/android/inputmethod/latin/TextEntryState.java
@@ -69,9 +69,11 @@
     public static final int STATE_SPACE_AFTER_ACCEPTED = 7;
     public static final int STATE_SPACE_AFTER_PICKED = 8;
     public static final int STATE_UNDO_COMMIT = 9;
-    
+    public static final int STATE_CORRECTING = 10;
+    public static final int STATE_PICKED_CORRECTION = 11;
+
     private static int sState = STATE_UNKNOWN;
-    
+
     private static FileOutputStream sKeyLocationFile;
     private static FileOutputStream sUserActionFile;
     
@@ -153,12 +155,17 @@
 
     public static void acceptedSuggestion(CharSequence typedWord, CharSequence actualWord) {
         sManualSuggestCount++;
+        int oldState = sState;
         if (typedWord.equals(actualWord)) {
             acceptedTyped(typedWord);
         }
-        sState = STATE_PICKED_SUGGESTION;
+        sState = oldState == STATE_CORRECTING ? STATE_PICKED_CORRECTION : STATE_PICKED_SUGGESTION;
     }
-    
+
+    public static void selectedForCorrection() {
+        sState = STATE_CORRECTING;
+    }
+
     public static void typedCharacter(char c, boolean isSeparator) {
         boolean isSpace = c == ' ';
         switch (sState) {
@@ -180,6 +187,7 @@
                 }
                 break;
             case STATE_PICKED_SUGGESTION:
+            case STATE_PICKED_CORRECTION:
                 if (isSpace) {
                     sState = STATE_SPACE_AFTER_PICKED;
                 } else if (isSeparator) {
@@ -206,6 +214,10 @@
                 } else {
                     sState = STATE_IN_WORD;
                 }
+                break;
+            case STATE_CORRECTING:
+                sState = STATE_START;
+                break;
         }
     }
     
@@ -227,7 +239,11 @@
     public static int getState() {
         return sState;
     }
-    
+
+    public static boolean isCorrecting() {
+        return sState == STATE_CORRECTING || sState == STATE_PICKED_CORRECTION;
+    }
+
     public static void keyPressedAt(Key key, int x, int y) {
         if (LOGGING && sKeyLocationFile != null && key.codes[0] >= 32) {
             String out = 
diff --git a/java/src/com/android/inputmethod/latin/WordComposer.java b/java/src/com/android/inputmethod/latin/WordComposer.java
index 2547aa1..e2573a0 100644
--- a/java/src/com/android/inputmethod/latin/WordComposer.java
+++ b/java/src/com/android/inputmethod/latin/WordComposer.java
@@ -49,6 +49,13 @@
         mTypedWord = new StringBuilder(20);
     }
 
+    WordComposer(WordComposer copy) {
+        mCodes = (ArrayList<int[]>) copy.mCodes.clone();
+        mPreferredWord = copy.mPreferredWord;
+        mTypedWord = new StringBuilder(copy.mTypedWord);
+        mCapsCount = copy.mCapsCount;
+        mAutoCapitalized = copy.mAutoCapitalized;
+    }
     /**
      * Clear out the keys registered so far.
      */
diff --git a/java/src/com/android/inputmethod/voice/VoiceInput.java b/java/src/com/android/inputmethod/voice/VoiceInput.java
index ac06ab5..3546709 100644
--- a/java/src/com/android/inputmethod/voice/VoiceInput.java
+++ b/java/src/com/android/inputmethod/voice/VoiceInput.java
@@ -63,7 +63,7 @@
 
     // WARNING! Before enabling this, fix the problem with calling getExtractedText() in
     // landscape view. It causes Extracted text updates to be rejected due to a token mismatch
-    public static boolean ENABLE_WORD_CORRECTIONS = false;
+    public static boolean ENABLE_WORD_CORRECTIONS = true;
 
     // Dummy word suggestion which means "delete current word"
     public static final String DELETE_SYMBOL = " \u00D7 ";  // times symbol
@@ -308,7 +308,7 @@
                 SettingsUtil.getSettingsInt(
                         mContext.getContentResolver(),
                         SettingsUtil.LATIN_IME_MAX_VOICE_RESULTS,
-                        1));
+                        10));
 
         // Get endpointer params from Gservices.
         // TODO: Consider caching these values for improved performance on slower devices.