am d36f3f83: (-s ours) Merge "Import revised translations.  DO NOT MERGE" into ics-mr1

* commit 'd36f3f8393704b2d5a82d52f8df3a1db398c2853':
  Import revised translations.  DO NOT MERGE
diff --git a/java/res/values-cs/strings.xml b/java/res/values-cs/strings.xml
index d57ccaa..8df065e 100644
--- a/java/res/values-cs/strings.xml
+++ b/java/res/values-cs/strings.xml
@@ -23,7 +23,7 @@
     <string name="english_ime_name" msgid="7252517407088836577">"Klávesnice Android"</string>
     <string name="english_ime_settings" msgid="6661589557206947774">"Nastavení klávesnice Android"</string>
     <string name="english_ime_input_options" msgid="3909945612939668554">"Možnosti zadávání textu a dat"</string>
-    <string name="spell_checker_service_name" msgid="2003013122022285508">"Opravy Android"</string>
+    <string name="spell_checker_service_name" msgid="2003013122022285508">"Opravy pravopisu Android"</string>
     <string name="android_spell_checker_settings" msgid="5822324635435443689">"Nastavení kontroly pravopisu"</string>
     <string name="use_proximity_option_title" msgid="7469233942295924620">"Použít údaje o blízkosti"</string>
     <string name="use_proximity_option_summary" msgid="2857708859847261945">"Při kontrole pravopisu uvažovat blízkost písmen na klávesnici"</string>
diff --git a/java/res/values-fa/strings.xml b/java/res/values-fa/strings.xml
index 7d8c1e9..0168880 100644
--- a/java/res/values-fa/strings.xml
+++ b/java/res/values-fa/strings.xml
@@ -29,7 +29,7 @@
     <string name="use_proximity_option_summary" msgid="2857708859847261945">"استفاده از یک الگوریتم مجاورت مشابه صفحه کلید برای غلط گیری املایی"</string>
     <string name="vibrate_on_keypress" msgid="5258079494276955460">"لرزش با فشار کلید"</string>
     <string name="sound_on_keypress" msgid="6093592297198243644">"صدا با فشار کلید"</string>
-    <string name="popup_on_keypress" msgid="123894815723512944">"بازشدن با فشار کلید"</string>
+    <string name="popup_on_keypress" msgid="123894815723512944">"بازشو با فشار کلید"</string>
     <string name="general_category" msgid="1859088467017573195">"کلی"</string>
     <string name="correction_category" msgid="2236750915056607613">"تصحیح متن"</string>
     <string name="misc_category" msgid="6894192814868233453">"سایر گزینه ها"</string>
diff --git a/java/res/values-iw/strings.xml b/java/res/values-iw/strings.xml
index 1ea5a69..4659cdf 100644
--- a/java/res/values-iw/strings.xml
+++ b/java/res/values-iw/strings.xml
@@ -139,7 +139,7 @@
     <string name="configure_input_method" msgid="373356270290742459">"הגדרת שיטות קלט"</string>
     <string name="language_selection_title" msgid="1651299598555326750">"שפות קלט"</string>
     <string name="select_language" msgid="3693815588777926848">"שפות קלט"</string>
-    <string name="hint_add_to_dictionary" msgid="9006292060636342317">"← גע פעם נוספת לשמירה"</string>
+    <string name="hint_add_to_dictionary" msgid="9006292060636342317">"??? גע פעם נוספת לשמירה"</string>
     <string name="has_dictionary" msgid="6071847973466625007">"מילון זמין"</string>
     <string name="prefs_enable_log" msgid="6620424505072963557">"הפוך משוב ממשתמשים לפעיל"</string>
     <string name="prefs_description_log" msgid="5827825607258246003">"עזור לשפר את עורך שיטת הקלט על ידי שליחה אוטומטית של סטטיסטיקת שימוש ודוחות קריסת מחשב ל-Google."</string>
diff --git a/java/res/values-ja/strings.xml b/java/res/values-ja/strings.xml
index 540bb46..57b3895 100644
--- a/java/res/values-ja/strings.xml
+++ b/java/res/values-ja/strings.xml
@@ -43,7 +43,7 @@
     <string name="enable_span_insert" msgid="7204653105667167620">"再修正を有効にする"</string>
     <string name="enable_span_insert_summary" msgid="2947317657871394467">"再修正の候補を挿入する"</string>
     <string name="auto_cap" msgid="1719746674854628252">"自動大文字変換"</string>
-    <string name="configure_dictionaries_title" msgid="4238652338556902049">"アドオン辞書"</string>
+    <string name="configure_dictionaries_title" msgid="4238652338556902049">"辞書を追加"</string>
     <string name="main_dictionary" msgid="4798763781818361168">"メイン辞書"</string>
     <string name="prefs_show_suggestions" msgid="8026799663445531637">"修正候補を表示する"</string>
     <string name="prefs_show_suggestions_summary" msgid="1583132279498502825">"入力中に入力候補を表示する"</string>
diff --git a/java/res/values-th/strings.xml b/java/res/values-th/strings.xml
index 53f5660..cd4a558 100644
--- a/java/res/values-th/strings.xml
+++ b/java/res/values-th/strings.xml
@@ -35,7 +35,7 @@
     <string name="misc_category" msgid="6894192814868233453">"ตัวเลือกอื่นๆ"</string>
     <string name="advanced_settings" msgid="362895144495591463">"การตั้งค่าขั้นสูง"</string>
     <string name="advanced_settings_summary" msgid="5193513161106637254">"ตัวเลือกสำหรับผู้ใช้ที่มีความเชี่ยวชาญ"</string>
-    <string name="key_preview_popup_dismiss_delay" msgid="6213164897443068248">"หน่วงเวลาก่อนปิดป๊อปอัพหลัก"</string>
+    <string name="key_preview_popup_dismiss_delay" msgid="6213164897443068248">"การหน่วงเวลาก่อนปิดป๊อปอัพหลัก"</string>
     <string name="key_preview_popup_dismiss_no_delay" msgid="2096123151571458064">"ไม่มีการหน่วงเวลา"</string>
     <string name="key_preview_popup_dismiss_default_delay" msgid="2166964333903906734">"ค่าเริ่มต้น"</string>
     <string name="use_contacts_dict" msgid="4435317977804180815">"แนะนำชื่อผู้ติดต่อ"</string>
@@ -56,7 +56,7 @@
     <string name="auto_correction_threshold_mode_off" msgid="8470882665417944026">"ปิด"</string>
     <string name="auto_correction_threshold_mode_modest" msgid="8788366690620799097">"ปานกลาง"</string>
     <string name="auto_correction_threshold_mode_aggeressive" msgid="3524029103734923819">"เข้มงวด"</string>
-    <string name="auto_correction_threshold_mode_very_aggeressive" msgid="3386782235540547678">"เข้มงวดมาก"</string>
+    <string name="auto_correction_threshold_mode_very_aggeressive" msgid="3386782235540547678">"สูง"</string>
     <string name="bigram_suggestion" msgid="2636414079905220518">"คำแนะนำ Bigram"</string>
     <string name="bigram_suggestion_summary" msgid="4383845146070101531">"ใช้คำก่อนหน้านี้เพื่อปรับปรุงคำแนะนำ"</string>
     <string name="bigram_prediction" msgid="8914273444762259739">"การคาดคะเน Bigram"</string>
diff --git a/java/src/com/android/inputmethod/keyboard/Keyboard.java b/java/src/com/android/inputmethod/keyboard/Keyboard.java
index a57b9d1..8d40e7a 100644
--- a/java/src/com/android/inputmethod/keyboard/Keyboard.java
+++ b/java/src/com/android/inputmethod/keyboard/Keyboard.java
@@ -75,7 +75,6 @@
     public static final int CODE_DELETE = -5;
     public static final int CODE_SETTINGS = -6;
     public static final int CODE_SHORTCUT = -7;
-    public static final int CODE_HAPTIC_AND_AUDIO_FEEDBACK_ONLY = -98;
     // Code value representing the code is not specified.
     public static final int CODE_UNSPECIFIED = -99;
 
diff --git a/java/src/com/android/inputmethod/keyboard/LatinKeyboardView.java b/java/src/com/android/inputmethod/keyboard/LatinKeyboardView.java
index a24195e..34f1c62 100644
--- a/java/src/com/android/inputmethod/keyboard/LatinKeyboardView.java
+++ b/java/src/com/android/inputmethod/keyboard/LatinKeyboardView.java
@@ -21,7 +21,6 @@
 import android.content.res.Resources;
 import android.graphics.Canvas;
 import android.os.Message;
-import android.os.SystemClock;
 import android.util.AttributeSet;
 import android.util.Log;
 import android.view.GestureDetector;
@@ -349,9 +348,11 @@
         // When shift key is double tapped, the first tap is correctly processed as usual tap. And
         // the second tap is treated as this double tap event, so that we need not mark tracker
         // calling setAlreadyProcessed() nor remove the tracker from mPointerQueue.
-        final int primaryCode = ignore ? Keyboard.CODE_HAPTIC_AND_AUDIO_FEEDBACK_ONLY
-                : Keyboard.CODE_CAPSLOCK;
-        mKeyboardActionListener.onCodeInput(primaryCode, null, 0, 0);
+        if (ignore) {
+            mKeyboardActionListener.onCustomRequest(LatinIME.CODE_HAPTIC_AND_AUDIO_FEEDBACK);
+        } else {
+            mKeyboardActionListener.onCodeInput(Keyboard.CODE_CAPSLOCK, null, 0, 0);
+        }
     }
 
     // This default implementation returns a more keys panel.
@@ -452,8 +453,7 @@
                 this, this, pointX, pointY, mMoreKeysWindow, getKeyboardActionListener());
         final int translatedX = moreKeysPanel.translateX(tracker.getLastX());
         final int translatedY = moreKeysPanel.translateY(tracker.getLastY());
-        tracker.onShowMoreKeysPanel(
-                translatedX, translatedY, SystemClock.uptimeMillis(), moreKeysPanel);
+        tracker.onShowMoreKeysPanel(translatedX, translatedY, moreKeysPanel);
         dimEntireKeyboard(true);
         return true;
     }
diff --git a/java/src/com/android/inputmethod/keyboard/PointerTracker.java b/java/src/com/android/inputmethod/keyboard/PointerTracker.java
index 38c419d..d5986aa 100644
--- a/java/src/com/android/inputmethod/keyboard/PointerTracker.java
+++ b/java/src/com/android/inputmethod/keyboard/PointerTracker.java
@@ -18,6 +18,7 @@
 
 import android.content.Context;
 import android.content.res.Resources;
+import android.os.SystemClock;
 import android.util.Log;
 import android.view.MotionEvent;
 import android.widget.TextView;
@@ -615,9 +616,9 @@
         }
     }
 
-    public void onShowMoreKeysPanel(int x, int y, long eventTime, KeyEventHandler handler) {
+    public void onShowMoreKeysPanel(int x, int y, KeyEventHandler handler) {
         onLongPressed();
-        onDownEvent(x, y, eventTime, handler);
+        onDownEvent(x, y, SystemClock.uptimeMillis(), handler);
         mIsShowingMoreKeysPanel = true;
     }
 
diff --git a/java/src/com/android/inputmethod/latin/LatinIME.java b/java/src/com/android/inputmethod/latin/LatinIME.java
index 567537d..1666fc1 100644
--- a/java/src/com/android/inputmethod/latin/LatinIME.java
+++ b/java/src/com/android/inputmethod/latin/LatinIME.java
@@ -157,6 +157,22 @@
         SUGGESTION_VISIBILILTY_HIDE_VALUE
     };
 
+    // Magic space: a space that should disappear on space/apostrophe insertion, move after the
+    // punctuation on punctuation insertion, and become a real space on alpha char insertion.
+    // Weak space: a space that be swapped only by suggestion strip punctuation.
+    // Double space: the state where the user pressed space twice quickly, which LatinIME
+    // resolved as period-space. Undoing this converts the period to a space.
+    // Swap punctuation: the state where a (weak or magic) space and a punctuation from the
+    // suggestion strip have just been swapped. Undoing this swaps them back.
+    private static final int SPACE_STATE_NONE = 0;
+    private static final int SPACE_STATE_DOUBLE = 1;
+    private static final int SPACE_STATE_SWAP_PUNCTUATION = 2;
+    private static final int SPACE_STATE_MAGIC = 3;
+    private static final int SPACE_STATE_WEAK = 4;
+
+    // Current space state of the input method. This can be any of the above constants.
+    private int mSpaceState;
+
     private Settings.Values mSettingsValues;
 
     private View mExtractArea;
@@ -190,12 +206,6 @@
     private WordComposer mWordComposer = new WordComposer();
     private CharSequence mBestWord;
     private boolean mHasUncommittedTypedChars;
-    // Magic space: a space that should disappear on space/apostrophe insertion, move after the
-    // punctuation on punctuation insertion, and become a real space on alpha char insertion.
-    private boolean mJustAddedMagicSpace; // This indicates whether the last char is a magic space.
-    // This indicates whether the last keypress resulted in processing of double space replacement
-    // with period-space.
-    private boolean mJustReplacedDoubleSpace;
 
     private int mCorrectionMode;
     private int mCommittedLength;
@@ -727,8 +737,7 @@
         mComposingStringBuilder.setLength(0);
         mHasUncommittedTypedChars = false;
         mDeleteCount = 0;
-        mJustAddedMagicSpace = false;
-        mJustReplacedDoubleSpace = false;
+        mSpaceState = SPACE_STATE_NONE;
 
         loadSettings();
         updateCorrectionMode();
@@ -889,6 +898,13 @@
                 || newSelEnd != candidatesEnd) && mLastSelectionStart != newSelStart;
         final boolean candidatesCleared = candidatesStart == -1 && candidatesEnd == -1;
         if (!mExpectingUpdateSelection) {
+            if (SPACE_STATE_WEAK == mSpaceState) {
+                // Test for no WEAK_SPACE action because there is a race condition that may end up
+                // in coming here on a normal key press. We set this to NONE because after
+                // a cursor move, we don't want the suggestion strip to swap the space with the
+                // newly inserted punctuation.
+                mSpaceState = SPACE_STATE_NONE;
+            }
             if (((mComposingStringBuilder.length() > 0 && mHasUncommittedTypedChars)
                     || mVoiceProxy.isVoiceInputHighlighted())
                     && (selectionChanged || candidatesCleared)) {
@@ -906,8 +922,6 @@
                 TextEntryState.reset();
                 updateSuggestions();
             }
-            mJustAddedMagicSpace = false; // The user moved the cursor.
-            mJustReplacedDoubleSpace = false;
         }
         mExpectingUpdateSelection = false;
         mHandler.postUpdateShiftKeyState();
@@ -1132,25 +1146,22 @@
         return false;
     }
 
-    private void swapSwapperAndSpace() {
-        final InputConnection ic = getCurrentInputConnection();
-        if (ic == null) return;
+    // "ic" may be null
+    private void swapSwapperAndSpaceWhileInBatchEdit(final InputConnection ic) {
+        if (null == ic) return;
         CharSequence lastTwo = ic.getTextBeforeCursor(2, 0);
         // It is guaranteed lastTwo.charAt(1) is a swapper - else this method is not called.
         if (lastTwo != null && lastTwo.length() == 2
                 && lastTwo.charAt(0) == Keyboard.CODE_SPACE) {
-            ic.beginBatchEdit();
             ic.deleteSurroundingText(2, 0);
             ic.commitText(lastTwo.charAt(1) + " ", 1);
-            ic.endBatchEdit();
             mKeyboardSwitcher.updateShiftState();
         }
     }
 
-    private void maybeDoubleSpace() {
-        if (mCorrectionMode == Suggest.CORRECTION_NONE) return;
-        final InputConnection ic = getCurrentInputConnection();
-        if (ic == null) return;
+    private boolean maybeDoubleSpaceWhileInBatchEdit(final InputConnection ic) {
+        if (mCorrectionMode == Suggest.CORRECTION_NONE) return false;
+        if (ic == null) return false;
         final CharSequence lastThree = ic.getTextBeforeCursor(3, 0);
         if (lastThree != null && lastThree.length() == 3
                 && Utils.canBeFollowedByPeriod(lastThree.charAt(0))
@@ -1158,22 +1169,19 @@
                 && lastThree.charAt(2) == Keyboard.CODE_SPACE
                 && mHandler.isAcceptingDoubleSpaces()) {
             mHandler.cancelDoubleSpacesTimer();
-            ic.beginBatchEdit();
             ic.deleteSurroundingText(2, 0);
             ic.commitText(". ", 1);
-            ic.endBatchEdit();
             mKeyboardSwitcher.updateShiftState();
-            mJustReplacedDoubleSpace = true;
-        } else {
-            mHandler.startDoubleSpacesTimer();
+            return true;
         }
+        return false;
     }
 
-    // "ic" must not null
+    // "ic" must not be null
     private void maybeRemovePreviousPeriod(final InputConnection ic, CharSequence text) {
         // When the text's first character is '.', remove the previous period
         // if there is one.
-        CharSequence lastOne = ic.getTextBeforeCursor(1, 0);
+        final CharSequence lastOne = ic.getTextBeforeCursor(1, 0);
         if (lastOne != null && lastOne.length() == 1
                 && lastOne.charAt(0) == Keyboard.CODE_PERIOD
                 && text.charAt(0) == Keyboard.CODE_PERIOD) {
@@ -1181,11 +1189,10 @@
         }
     }
 
-    private void removeTrailingSpace() {
-        final InputConnection ic = getCurrentInputConnection();
+    // "ic" may be null
+    private void removeTrailingSpaceWhileInBatchEdit(final InputConnection ic) {
         if (ic == null) return;
-
-        CharSequence lastOne = ic.getTextBeforeCursor(1, 0);
+        final CharSequence lastOne = ic.getTextBeforeCursor(1, 0);
         if (lastOne != null && lastOne.length() == 1
                 && lastOne.charAt(0) == Keyboard.CODE_SPACE) {
             ic.deleteSurroundingText(1, 0);
@@ -1222,6 +1229,7 @@
 
     // Virtual codes representing custom requests.  These are used in onCustomRequest() below.
     public static final int CODE_SHOW_INPUT_METHOD_PICKER = 1;
+    public static final int CODE_HAPTIC_AND_AUDIO_FEEDBACK = 2;
 
     @Override
     public boolean onCustomRequest(int requestCode) {
@@ -1233,6 +1241,9 @@
                 return true;
             }
             return false;
+        case CODE_HAPTIC_AND_AUDIO_FEEDBACK:
+            hapticAndAudioFeedback(Keyboard.CODE_UNSPECIFIED);
+            return true;
         }
         return false;
     }
@@ -1241,6 +1252,28 @@
         return mOptionsDialog != null && mOptionsDialog.isShowing();
     }
 
+    private void insertPunctuationFromSuggestionStrip(final InputConnection ic, final int code) {
+        final CharSequence beforeText = ic != null ? ic.getTextBeforeCursor(1, 0) : null;
+        final int toLeft = TextUtils.isEmpty(beforeText) ? 0 : beforeText.charAt(0);
+        final boolean shouldRegisterSwapPunctuation;
+        // If we have a space left of the cursor and it's a weak or a magic space, then we should
+        // swap it, and override the space state with SPACESTATE_SWAP_PUNCTUATION.
+        // To swap it, we fool handleSeparator to think the previous space state was a
+        // magic space.
+        if (Keyboard.CODE_SPACE == toLeft && mSpaceState == SPACE_STATE_WEAK) {
+            mSpaceState = SPACE_STATE_MAGIC;
+            shouldRegisterSwapPunctuation = true;
+        } else {
+            shouldRegisterSwapPunctuation = false;
+        }
+        onCodeInput(code, new int[] { code },
+                KeyboardActionListener.NOT_A_TOUCH_COORDINATE,
+                KeyboardActionListener.NOT_A_TOUCH_COORDINATE);
+        if (shouldRegisterSwapPunctuation) {
+            mSpaceState = SPACE_STATE_SWAP_PUNCTUATION;
+        }
+    }
+
     // Implementation of {@link KeyboardActionListener}.
     @Override
     public void onCodeInput(int primaryCode, int[] keyCodes, int x, int y) {
@@ -1251,11 +1284,16 @@
         mLastKeyTime = when;
         KeyboardSwitcher switcher = mKeyboardSwitcher;
         final boolean distinctMultiTouch = switcher.hasDistinctMultitouch();
-        final boolean lastStateOfJustReplacedDoubleSpace = mJustReplacedDoubleSpace;
-        mJustReplacedDoubleSpace = false;
+        // The space state depends only on the last character pressed and its own previous
+        // state. Here, we revert the space state to neutral if the key is actually modifying
+        // the input contents (any non-shift key), which is what we should do for
+        // all inputs that do not result in a special state. Each character handling is then
+        // free to override the state as they see fit.
+        final int spaceState = mSpaceState;
         switch (primaryCode) {
         case Keyboard.CODE_DELETE:
-            handleBackspace(lastStateOfJustReplacedDoubleSpace);
+            mSpaceState = SPACE_STATE_NONE;
+            handleBackspace(spaceState);
             mDeleteCount++;
             mExpectingUpdateSelection = true;
             LatinImeLogger.logOnDelete();
@@ -1280,11 +1318,7 @@
             break;
         case Keyboard.CODE_CAPSLOCK:
             switcher.toggleCapsLock();
-            //$FALL-THROUGH$
-        case Keyboard.CODE_HAPTIC_AND_AUDIO_FEEDBACK_ONLY:
-            // Dummy code for haptic and audio feedbacks.
-            vibrate();
-            playKeyClick(primaryCode);
+            hapticAndAudioFeedback(primaryCode);
             break;
         case Keyboard.CODE_SHORTCUT:
             mSubtypeSwitcher.switchToShortcutIME();
@@ -1301,10 +1335,11 @@
             // To sum it up: do not update mExpectingUpdateSelection here.
             break;
         default:
+            mSpaceState = SPACE_STATE_NONE;
             if (mSettingsValues.isWordSeparator(primaryCode)) {
-                handleSeparator(primaryCode, x, y);
+                handleSeparator(primaryCode, x, y, spaceState);
             } else {
-                handleCharacter(primaryCode, keyCodes, x, y);
+                handleCharacter(primaryCode, keyCodes, x, y, spaceState);
             }
             mExpectingUpdateSelection = true;
             break;
@@ -1326,7 +1361,7 @@
         ic.endBatchEdit();
         mKeyboardSwitcher.updateShiftState();
         mKeyboardSwitcher.onKey(Keyboard.CODE_DUMMY);
-        mJustAddedMagicSpace = false;
+        mSpaceState = SPACE_STATE_NONE;
         mEnteredText = text;
     }
 
@@ -1336,7 +1371,7 @@
         mKeyboardSwitcher.onCancelInput();
     }
 
-    private void handleBackspace(boolean justReplacedDoubleSpace) {
+    private void handleBackspace(final int spaceState) {
         if (mVoiceProxy.logAndRevertVoiceInput()) return;
 
         final InputConnection ic = getCurrentInputConnection();
@@ -1374,15 +1409,24 @@
         }
         mHandler.postUpdateShiftKeyState();
 
+        // TODO: Merge space state with TextEntryState
         TextEntryState.backspace();
         if (TextEntryState.isUndoCommit()) {
             revertLastWord(ic);
             ic.endBatchEdit();
             return;
         }
-        if (justReplacedDoubleSpace) {
+        if (SPACE_STATE_DOUBLE == spaceState) {
             if (revertDoubleSpace(ic)) {
                 ic.endBatchEdit();
+                // No need to reset mSpaceState, it has already be done (that's why we
+                // receive it as a parameter)
+                return;
+            }
+        } else if (SPACE_STATE_SWAP_PUNCTUATION == spaceState) {
+            if (revertSwapPunctuation(ic)) {
+                ic.endBatchEdit();
+                // Likewise
                 return;
             }
         }
@@ -1432,11 +1476,15 @@
         }
     }
 
-    private void handleCharacter(int primaryCode, int[] keyCodes, int x, int y) {
+    private void handleCharacter(final int primaryCode, final int[] keyCodes, final int x,
+            final int y, final int spaceState) {
         mVoiceProxy.handleCharacter();
 
-        if (mJustAddedMagicSpace && mSettingsValues.isMagicSpaceStripper(primaryCode)) {
-            removeTrailingSpace();
+        final InputConnection ic = getCurrentInputConnection();
+        if (ic != null) ic.beginBatchEdit();
+        if (SPACE_STATE_MAGIC == spaceState
+                && mSettingsValues.isMagicSpaceStripper(primaryCode)) {
+            removeTrailingSpaceWhileInBatchEdit(ic);
         }
 
         int code = primaryCode;
@@ -1454,6 +1502,7 @@
         if (switcher.isShiftedOrShiftLocked()) {
             if (keyCodes == null || keyCodes[0] < Character.MIN_CODE_POINT
                     || keyCodes[0] > Character.MAX_CODE_POINT) {
+                if (null != ic) ic.endBatchEdit();
                 return;
             }
             code = keyCodes[0];
@@ -1467,6 +1516,7 @@
                 } else {
                     // Some keys, such as [eszett], have upper case as multi-characters.
                     onTextInput(upperCaseString);
+                    if (null != ic) ic.endBatchEdit();
                     return;
                 }
             }
@@ -1474,7 +1524,6 @@
         if (mHasUncommittedTypedChars) {
             mComposingStringBuilder.append((char) code);
             mWordComposer.add(code, keyCodes, x, y);
-            final InputConnection ic = getCurrentInputConnection();
             if (ic != null) {
                 // If it's the first letter, make note of auto-caps state
                 if (mWordComposer.size() == 1) {
@@ -1492,18 +1541,19 @@
         } else {
             sendKeyChar((char)code);
         }
-        if (mJustAddedMagicSpace && mSettingsValues.isMagicSpaceSwapper(primaryCode)) {
-            swapSwapperAndSpace();
-        } else {
-            mJustAddedMagicSpace = false;
+        if (SPACE_STATE_MAGIC == spaceState
+                && mSettingsValues.isMagicSpaceSwapper(primaryCode)) {
+            if (null != ic) swapSwapperAndSpaceWhileInBatchEdit(ic);
         }
 
         switcher.updateShiftState();
         if (LatinIME.PERF_DEBUG) measureCps();
         TextEntryState.typedCharacter((char) code, mSettingsValues.isWordSeparator(code), x, y);
+        if (null != ic) ic.endBatchEdit();
     }
 
-    private void handleSeparator(int primaryCode, int x, int y) {
+    private void handleSeparator(final int primaryCode, final int x, final int y,
+            final int spaceState) {
         mVoiceProxy.handleSeparator();
         mComposingStateManager.onFinishComposingText();
 
@@ -1533,21 +1583,45 @@
             }
         }
 
-        if (mJustAddedMagicSpace) {
+        final boolean swapMagicSpace;
+        if (SPACE_STATE_MAGIC == spaceState) {
             if (mSettingsValues.isMagicSpaceSwapper(primaryCode)) {
-                sendKeyChar((char)primaryCode);
-                swapSwapperAndSpace();
+                swapMagicSpace = true;
             } else {
-                if (mSettingsValues.isMagicSpaceStripper(primaryCode)) removeTrailingSpace();
-                sendKeyChar((char)primaryCode);
-                mJustAddedMagicSpace = false;
+                swapMagicSpace = false;
+                if (mSettingsValues.isMagicSpaceStripper(primaryCode)) {
+                    removeTrailingSpaceWhileInBatchEdit(ic);
+                }
             }
         } else {
-            sendKeyChar((char)primaryCode);
+            swapMagicSpace = false;
         }
 
-        if (isSuggestionsRequested() && primaryCode == Keyboard.CODE_SPACE) {
-            maybeDoubleSpace();
+        sendKeyChar((char)primaryCode);
+
+        if (Keyboard.CODE_SPACE == primaryCode) {
+            if (isSuggestionsRequested()) {
+                if (maybeDoubleSpaceWhileInBatchEdit(ic)) {
+                    mSpaceState = SPACE_STATE_DOUBLE;
+                } else if (!isShowingPunctuationList()) {
+                    mSpaceState = SPACE_STATE_WEAK;
+                }
+            }
+
+            mHandler.startDoubleSpacesTimer();
+            if (!isCursorTouchingWord()) {
+                mHandler.cancelUpdateSuggestions();
+                mHandler.postUpdateBigramPredictions();
+            }
+        } else {
+            if (swapMagicSpace) {
+                swapSwapperAndSpaceWhileInBatchEdit(ic);
+                mSpaceState = SPACE_STATE_MAGIC;
+            }
+
+            // Set punctuation right away. onUpdateSelection will fire but tests whether it is
+            // already displayed or not, so it's okay.
+            setPunctuationSuggestions();
         }
 
         TextEntryState.typedCharacter((char) primaryCode, true, x, y);
@@ -1560,16 +1634,6 @@
                         ic, mLastSelectionEnd - typedWord.length(), typedWord, mBestWord);
             }
         }
-        if (Keyboard.CODE_SPACE == primaryCode) {
-            if (!isCursorTouchingWord()) {
-                mHandler.cancelUpdateSuggestions();
-                mHandler.postUpdateBigramPredictions();
-            }
-        } else {
-            // Set punctuation right away. onUpdateSelection will fire but tests whether it is
-            // already displayed or not, so it's okay.
-            setPunctuationSuggestions();
-        }
         mKeyboardSwitcher.updateShiftState();
         if (ic != null) {
             ic.endBatchEdit();
@@ -1604,7 +1668,7 @@
     public boolean isSuggestionsStripVisible() {
         if (mSuggestionsView == null)
             return false;
-        if (mSuggestionsView.isShowingAddToDictionaryHint() || TextEntryState.isRecorrecting())
+        if (mSuggestionsView.isShowingAddToDictionaryHint())
             return true;
         if (!isShowingSuggestionsStrip())
             return false;
@@ -1709,7 +1773,6 @@
         }
         // Don't auto-correct words with multiple capital letter
         autoCorrectionAvailable &= !wordComposer.isMostlyCaps();
-        autoCorrectionAvailable &= !TextEntryState.isRecorrecting();
 
         // Basically, we update the suggestion strip only when suggestion count > 1.  However,
         // there is an exception: We update the suggestion strip whenever typed word's length
@@ -1782,7 +1845,6 @@
         mVoiceProxy.flushAndLogAllTextModificationCounters(index, suggestion,
                 mSettingsValues.mWordSeparators);
 
-        final boolean recorrecting = TextEntryState.isRecorrecting();
         final InputConnection ic = getCurrentInputConnection();
         if (ic != null) {
             ic.beginBatchEdit();
@@ -1812,8 +1874,8 @@
             LatinImeLogger.logOnManualSuggestion(
                     "", suggestion.toString(), index, suggestions.mWords);
             // Find out whether the previous character is a space. If it is, as a special case
-            // for punctuation entered through the suggestion strip, it should be considered
-            // a magic space even if it was a normal space. This is meant to help in case the user
+            // for punctuation entered through the suggestion strip, it should be swapped
+            // if it was a magic or a weak space. This is meant to help in case the user
             // pressed space on purpose of displaying the suggestion strip punctuation.
             final int rawPrimaryCode = suggestion.charAt(0);
             // Maybe apply the "bidi mirrored" conversions for parentheses
@@ -1821,15 +1883,8 @@
             final boolean isRtl = keyboard != null && keyboard.mIsRtlKeyboard;
             final int primaryCode = Key.getRtlParenthesisCode(rawPrimaryCode, isRtl);
 
-            final CharSequence beforeText = ic != null ? ic.getTextBeforeCursor(1, 0) : "";
-            final int toLeft = (ic == null || TextUtils.isEmpty(beforeText))
-                    ? 0 : beforeText.charAt(0);
-            final boolean oldMagicSpace = mJustAddedMagicSpace;
-            if (Keyboard.CODE_SPACE == toLeft) mJustAddedMagicSpace = true;
-            onCodeInput(primaryCode, new int[] { primaryCode },
-                    KeyboardActionListener.NOT_A_TOUCH_COORDINATE,
-                    KeyboardActionListener.NOT_A_TOUCH_COORDINATE);
-            mJustAddedMagicSpace = oldMagicSpace;
+            insertPunctuationFromSuggestionStrip(ic, primaryCode);
+            // TODO: the following endBatchEdit seems useless, check
             if (ic != null) {
                 ic.endBatchEdit();
             }
@@ -1853,7 +1908,7 @@
                 suggestion.toString(), index, suggestions.mWords);
         TextEntryState.acceptedSuggestion(mComposingStringBuilder.toString(), suggestion);
         // Follow it with a space
-        if (mInsertSpaceOnPickSuggestionManually && !recorrecting) {
+        if (mInsertSpaceOnPickSuggestionManually) {
             sendMagicSpace();
         }
 
@@ -1873,13 +1928,11 @@
                         || !AutoCorrection.isValidWord(
                                 mSuggest.getUnigramDictionaries(), suggestion, true));
 
-        if (!recorrecting) {
-            // Fool the state watcher so that a subsequent backspace will not do a revert, unless
-            // we just did a correction, in which case we need to stay in
-            // TextEntryState.State.PICKED_SUGGESTION state.
-            TextEntryState.typedCharacter((char) Keyboard.CODE_SPACE, true,
-                    WordComposer.NOT_A_COORDINATE, WordComposer.NOT_A_COORDINATE);
-        }
+        // Fool the state watcher so that a subsequent backspace will not do a revert, unless
+        // we just did a correction, in which case we need to stay in
+        // TextEntryState.State.PICKED_SUGGESTION state.
+        TextEntryState.typedCharacter((char) Keyboard.CODE_SPACE, true,
+                WordComposer.NOT_A_COORDINATE, WordComposer.NOT_A_COORDINATE);
         if (!showingAddToDictionaryHint) {
             // If we're not showing the "Touch again to save", then show corrections again.
             // In case the cursor position doesn't change, make sure we show the suggestions again.
@@ -2021,13 +2074,13 @@
         return false;
     }
 
-    // "ic" must not null
+    // "ic" must not be null
     private boolean sameAsTextBeforeCursor(final InputConnection ic, CharSequence text) {
         CharSequence beforeText = ic.getTextBeforeCursor(text.length(), 0);
         return TextUtils.equals(text, beforeText);
     }
 
-    // "ic" must not null
+    // "ic" must not be null
     private void revertLastWord(final InputConnection ic) {
         if (mHasUncommittedTypedChars || mComposingStringBuilder.length() <= 0) {
             sendDownUpKeyEvents(KeyEvent.KEYCODE_DEL);
@@ -2061,7 +2114,7 @@
         mHandler.postUpdateSuggestions();
     }
 
-    // "ic" must not null
+    // "ic" must not be null
     private boolean revertDoubleSpace(final InputConnection ic) {
         mHandler.cancelDoubleSpacesTimer();
         // Here we test whether we indeed have a period and a space before us. This should not
@@ -2076,13 +2129,28 @@
         return true;
     }
 
+    private boolean revertSwapPunctuation(final InputConnection ic) {
+        // Here we test whether we indeed have a space and something else before us. This should not
+        // be needed, but it's there just in case something went wrong.
+        final CharSequence textBeforeCursor = ic.getTextBeforeCursor(2, 0);
+        // NOTE: This does not work with surrogate pairs. Hopefully when the keyboard is able to
+        // enter surrogate pairs this code will have been removed.
+        if (Keyboard.CODE_SPACE != textBeforeCursor.charAt(1))
+            return false;
+        ic.beginBatchEdit();
+        ic.deleteSurroundingText(2, 0);
+        ic.commitText(" " + textBeforeCursor.subSequence(0, 1), 1);
+        ic.endBatchEdit();
+        return true;
+    }
+
     public boolean isWordSeparator(int code) {
         return mSettingsValues.isWordSeparator(code);
     }
 
     private void sendMagicSpace() {
         sendKeyChar((char)Keyboard.CODE_SPACE);
-        mJustAddedMagicSpace = true;
+        mSpaceState = SPACE_STATE_MAGIC;
         mKeyboardSwitcher.updateShiftState();
     }
 
@@ -2104,12 +2172,16 @@
         loadSettings();
     }
 
+    private void hapticAndAudioFeedback(int primaryCode) {
+        vibrate();
+        playKeyClick(primaryCode);
+    }
+
     @Override
     public void onPress(int primaryCode, boolean withSliding) {
         final KeyboardSwitcher switcher = mKeyboardSwitcher;
         if (switcher.isVibrateAndSoundFeedbackRequired()) {
-            vibrate();
-            playKeyClick(primaryCode);
+            hapticAndAudioFeedback(primaryCode);
         }
         final boolean distinctMultiTouch = switcher.hasDistinctMultitouch();
         if (distinctMultiTouch && primaryCode == Keyboard.CODE_SHIFT) {
diff --git a/java/src/com/android/inputmethod/latin/SuggestionsView.java b/java/src/com/android/inputmethod/latin/SuggestionsView.java
index c25ecb3..3b0715c 100644
--- a/java/src/com/android/inputmethod/latin/SuggestionsView.java
+++ b/java/src/com/android/inputmethod/latin/SuggestionsView.java
@@ -30,7 +30,6 @@
 import android.graphics.drawable.ColorDrawable;
 import android.graphics.drawable.Drawable;
 import android.os.Message;
-import android.os.SystemClock;
 import android.text.Spannable;
 import android.text.SpannableString;
 import android.text.Spanned;
@@ -832,8 +831,7 @@
                 // Decided to be in the sliding input mode only when the touch point has been moved
                 // upward.
                 mMoreSuggestionsMode = MORE_SUGGESTIONS_IN_SLIDING_MODE;
-                tracker.onShowMoreKeysPanel(
-                        translatedX, translatedY, SystemClock.uptimeMillis(), moreKeysPanel);
+                tracker.onShowMoreKeysPanel(translatedX, translatedY, moreKeysPanel);
             } else if (action == MotionEvent.ACTION_UP || action == MotionEvent.ACTION_POINTER_UP) {
                 // Decided to be in the modal input mode
                 mMoreSuggestionsMode = MORE_SUGGESTIONS_IN_MODAL_MODE;
diff --git a/java/src/com/android/inputmethod/latin/TextEntryState.java b/java/src/com/android/inputmethod/latin/TextEntryState.java
index 79b3bde..82242f8 100644
--- a/java/src/com/android/inputmethod/latin/TextEntryState.java
+++ b/java/src/com/android/inputmethod/latin/TextEntryState.java
@@ -30,13 +30,10 @@
     private static final int IN_WORD = 2;
     private static final int ACCEPTED_DEFAULT = 3;
     private static final int PICKED_SUGGESTION = 4;
-    private static final int PUNCTUATION_AFTER_WORD = 5;
-    private static final int PUNCTUATION_AFTER_ACCEPTED = 6;
-    private static final int SPACE_AFTER_ACCEPTED = 7;
-    private static final int SPACE_AFTER_PICKED = 8;
-    private static final int UNDO_COMMIT = 9;
-    private static final int RECORRECTING = 10;
-    private static final int PICKED_RECORRECTION = 11;
+    private static final int PUNCTUATION_AFTER_ACCEPTED = 5;
+    private static final int SPACE_AFTER_ACCEPTED = 6;
+    private static final int SPACE_AFTER_PICKED = 7;
+    private static final int UNDO_COMMIT = 8;
 
     private static int sState = UNKNOWN;
     private static int sPreviousState = UNKNOWN;
@@ -79,27 +76,11 @@
     }
 
     public static void acceptedSuggestion(CharSequence typedWord, CharSequence actualWord) {
-        if (sState == RECORRECTING || sState == PICKED_RECORRECTION) {
-            setState(PICKED_RECORRECTION);
-        } else {
-            setState(PICKED_SUGGESTION);
-        }
+        setState(PICKED_SUGGESTION);
         if (DEBUG)
             displayState("acceptedSuggestion", "typedWord", typedWord, "actualWord", actualWord);
     }
 
-    public static void selectedForRecorrection() {
-        setState(RECORRECTING);
-        if (DEBUG) displayState("selectedForRecorrection");
-    }
-
-    public static void onAbortRecorrection() {
-        if (sState == RECORRECTING || sState == PICKED_RECORRECTION) {
-            setState(START);
-        }
-        if (DEBUG) displayState("onAbortRecorrection");
-    }
-
     public static void typedCharacter(char c, boolean isSeparator, int x, int y) {
         final boolean isSpace = (c == Keyboard.CODE_SPACE);
         switch (sState) {
@@ -123,7 +104,6 @@
             }
             break;
         case PICKED_SUGGESTION:
-        case PICKED_RECORRECTION:
             if (isSpace) {
                 setState(SPACE_AFTER_PICKED);
             } else if (isSeparator) {
@@ -136,7 +116,6 @@
         case START:
         case UNKNOWN:
         case SPACE_AFTER_ACCEPTED:
-        case PUNCTUATION_AFTER_WORD:
             if (!isSpace && !isSeparator) {
                 setState(IN_WORD);
             } else {
@@ -150,9 +129,6 @@
                 setState(IN_WORD);
             }
             break;
-        case RECORRECTING:
-            setState(START);
-            break;
         }
         RingCharBuffer.getInstance().push(c, x, y);
         if (isSeparator) {
@@ -178,26 +154,10 @@
         if (DEBUG) displayState("reset");
     }
 
-    public static boolean isAcceptedDefault() {
-        return sState == ACCEPTED_DEFAULT;
-    }
-
-    public static boolean isSpaceAfterPicked() {
-        return sState == SPACE_AFTER_PICKED;
-    }
-
     public static boolean isUndoCommit() {
         return sState == UNDO_COMMIT;
     }
 
-    public static boolean isPunctuationAfterAccepted() {
-        return sState == PUNCTUATION_AFTER_ACCEPTED;
-    }
-
-    public static boolean isRecorrecting() {
-        return sState == RECORRECTING || sState == PICKED_RECORRECTION;
-    }
-
     public static String getState() {
         return stateName(sState);
     }
@@ -208,13 +168,10 @@
         case IN_WORD: return "IN_WORD";
         case ACCEPTED_DEFAULT: return "ACCEPTED_DEFAULT";
         case PICKED_SUGGESTION: return "PICKED_SUGGESTION";
-        case PUNCTUATION_AFTER_WORD: return "PUNCTUATION_AFTER_WORD";
         case PUNCTUATION_AFTER_ACCEPTED: return "PUNCTUATION_AFTER_ACCEPTED";
         case SPACE_AFTER_ACCEPTED: return "SPACE_AFTER_ACCEPTED";
         case SPACE_AFTER_PICKED: return "SPACE_AFTER_PICKED";
         case UNDO_COMMIT: return "UNDO_COMMIT";
-        case RECORRECTING: return "RECORRECTING";
-        case PICKED_RECORRECTION: return "PICKED_RECORRECTION";
         default: return "UNKNOWN";
         }
     }
diff --git a/native/src/unigram_dictionary.cpp b/native/src/unigram_dictionary.cpp
index 8eb5a97..7ff2f4a 100644
--- a/native/src/unigram_dictionary.cpp
+++ b/native/src/unigram_dictionary.cpp
@@ -253,12 +253,6 @@
     mProximityInfo = proximityInfo;
 }
 
-static inline void registerNextLetter(unsigned short c, int *nextLetters, int nextLettersSize) {
-    if (c < nextLettersSize) {
-        nextLetters[c]++;
-    }
-}
-
 // TODO: We need to optimize addWord by using STL or something
 // TODO: This needs to take an const unsigned short* and not tinker with its contents
 bool UnigramDictionary::addWord(unsigned short *word, int length, int frequency) {