diff --git a/java/res/values/keycodes.xml b/java/res/values/keycodes.xml
index 0f0a74aea11e79dbe72f46796c4b7070b6f0eb90..7145af5741638c8c5aa120343f12d662278573bd 100644
--- a/java/res/values/keycodes.xml
+++ b/java/res/values/keycodes.xml
@@ -23,6 +23,9 @@
     <integer name="key_tab">9</integer>
     <integer name="key_return">10</integer>
     <integer name="key_space">32</integer>
+    <integer name="key_dash">45</integer>
+    <integer name="key_single_quote">39</integer>
+    <integer name="key_double_quote">34</integer>
     <integer name="key_shift">-1</integer>
     <integer name="key_switch_alpha_symbol">-2</integer>
     <integer name="key_delete">-5</integer>
diff --git a/java/src/com/android/inputmethod/keyboard/Keyboard.java b/java/src/com/android/inputmethod/keyboard/Keyboard.java
index c02c224941bec71d94fd7c235bf65a5294e22858..fa2e0855dfe6699bd5f98e5792ed1e5ffb5d9008 100644
--- a/java/src/com/android/inputmethod/keyboard/Keyboard.java
+++ b/java/src/com/android/inputmethod/keyboard/Keyboard.java
@@ -63,6 +63,9 @@ public class Keyboard {
     public static final int CODE_TAB = '\t';
     public static final int CODE_SPACE = ' ';
     public static final int CODE_PERIOD = '.';
+    public static final int CODE_DASH = '-';
+    public static final int CODE_SINGLE_QUOTE = '\'';
+    public static final int CODE_DOUBLE_QUOTE = '"';
 
     /** Special keys code.  These should be aligned with values/keycodes.xml */
     public static final int CODE_DUMMY = 0;
diff --git a/java/src/com/android/inputmethod/keyboard/KeyboardSwitcher.java b/java/src/com/android/inputmethod/keyboard/KeyboardSwitcher.java
index fbfde97ef9b554560a1e7f445fc282b82e7ebfae..4f1ad576d4018bc2555a9105e7f4a5ab64bb2912 100644
--- a/java/src/com/android/inputmethod/keyboard/KeyboardSwitcher.java
+++ b/java/src/com/android/inputmethod/keyboard/KeyboardSwitcher.java
@@ -626,7 +626,7 @@ public class KeyboardSwitcher implements SharedPreferences.OnSharedPreferenceCha
 
     private static boolean isQuoteCharacter(int c) {
         // Apostrophe, quotation mark.
-        if (c == '\'' || c == '"')
+        if (c == Keyboard.CODE_SINGLE_QUOTE || c == Keyboard.CODE_DOUBLE_QUOTE)
             return true;
         // \u2018: Left single quotation mark
         // \u2019: Right single quotation mark
diff --git a/java/src/com/android/inputmethod/latin/ContactsDictionary.java b/java/src/com/android/inputmethod/latin/ContactsDictionary.java
index bdb68cac7154ac5ece621e4de9c3ac51bdce2825..b057cf4e3719dd8289a6bed11e98f6baf3da2e28 100644
--- a/java/src/com/android/inputmethod/latin/ContactsDictionary.java
+++ b/java/src/com/android/inputmethod/latin/ContactsDictionary.java
@@ -26,6 +26,8 @@ import android.provider.ContactsContract.Contacts;
 import android.text.TextUtils;
 import android.util.Log;
 
+import com.android.inputmethod.keyboard.Keyboard;
+
 public class ContactsDictionary extends ExpandableDictionary {
 
     private static final String[] PROJECTION = {
@@ -123,8 +125,9 @@ public class ContactsDictionary extends ExpandableDictionary {
                                 for (j = i + 1; j < len; j++) {
                                     char c = name.charAt(j);
 
-                                    if (!(c == '-' || c == '\'' ||
-                                          Character.isLetter(c))) {
+                                    if (!(c == Keyboard.CODE_DASH
+                                            || c == Keyboard.CODE_SINGLE_QUOTE
+                                            || Character.isLetter(c))) {
                                         break;
                                     }
                                 }
diff --git a/java/src/com/android/inputmethod/latin/DictionaryFactory.java b/java/src/com/android/inputmethod/latin/DictionaryFactory.java
index 2dbd582f35b8de8481e3886dad834b46087ce7de..fcb634371db3fa81193ad36a9568fe1a63b4b73c 100644
--- a/java/src/com/android/inputmethod/latin/DictionaryFactory.java
+++ b/java/src/com/android/inputmethod/latin/DictionaryFactory.java
@@ -18,7 +18,6 @@ package com.android.inputmethod.latin;
 
 import android.content.Context;
 import android.content.res.AssetFileDescriptor;
-import android.content.res.Configuration;
 import android.content.res.Resources;
 import android.util.Log;
 
diff --git a/java/src/com/android/inputmethod/latin/ExpandableDictionary.java b/java/src/com/android/inputmethod/latin/ExpandableDictionary.java
index be2c6b21bffd406c62e2bc0c1c3e04247f2a85c9..26391fe46a6f88fccbacb2250597bb87b501a7ac 100644
--- a/java/src/com/android/inputmethod/latin/ExpandableDictionary.java
+++ b/java/src/com/android/inputmethod/latin/ExpandableDictionary.java
@@ -19,6 +19,8 @@ package com.android.inputmethod.latin;
 import android.content.Context;
 import android.os.AsyncTask;
 
+import com.android.inputmethod.keyboard.Keyboard;
+
 import java.util.LinkedList;
 
 /**
@@ -41,8 +43,6 @@ public class ExpandableDictionary extends Dictionary {
     private int mMaxDepth;
     private int mInputLength;
 
-    private static final char QUOTE = '\'';
-
     private boolean mRequiresReload;
 
     private boolean mUpdatingDictionary;
@@ -304,7 +304,8 @@ public class ExpandableDictionary extends Dictionary {
                     getWordsRec(children, codes, word, depth + 1, completion, snr, inputIndex,
                             skipPos, callback);
                 }
-            } else if ((c == QUOTE && currentChars[0] != QUOTE) || depth == skipPos) {
+            } else if ((c == Keyboard.CODE_SINGLE_QUOTE
+                    && currentChars[0] != Keyboard.CODE_SINGLE_QUOTE) || depth == skipPos) {
                 // Skip the ' and continue deeper
                 word[depth] = c;
                 if (children != null) {
diff --git a/java/src/com/android/inputmethod/latin/LatinIME.java b/java/src/com/android/inputmethod/latin/LatinIME.java
index 105ec5a62bc2fb3aec92ad5e9ceb0ab9798fd0f2..17a78d590f57bc7e609906f0f96c13bee2ddb65d 100644
--- a/java/src/com/android/inputmethod/latin/LatinIME.java
+++ b/java/src/com/android/inputmethod/latin/LatinIME.java
@@ -161,8 +161,9 @@ public class LatinIME extends InputMethodServiceCompatWrapper implements Keyboar
     private ContactsDictionary mContactsDictionary;
     private AutoDictionary mAutoDictionary;
 
+    // TODO: Create an inner class to group options and pseudo-options to improve readability.
     // These variables are initialized according to the {@link EditorInfo#inputType}.
-    private boolean mAutoSpace;
+    private boolean mShouldInsertMagicSpace;
     private boolean mInputTypeNoAutoCorrect;
     private boolean mIsSettingsSuggestionStripOn;
     private boolean mApplicationSpecifiedCompletionOn;
@@ -174,7 +175,9 @@ public class LatinIME extends InputMethodServiceCompatWrapper implements Keyboar
     private CharSequence mBestWord;
     private boolean mHasUncommittedTypedChars;
     private boolean mHasDictionary;
-    private boolean mJustAddedAutoSpace;
+    // 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.
     private boolean mAutoCorrectEnabled;
     private boolean mRecorrectionEnabled;
     // Suggestion: use bigrams to adjust scores of suggestions obtained from unigram dictionary
@@ -593,7 +596,7 @@ public class LatinIME extends InputMethodServiceCompatWrapper implements Keyboar
         mComposing.setLength(0);
         mHasUncommittedTypedChars = false;
         mDeleteCount = 0;
-        mJustAddedAutoSpace = false;
+        mJustAddedMagicSpace = false;
 
         loadSettings(attribute);
         if (mSubtypeSwitcher.isKeyboardMode()) {
@@ -628,7 +631,7 @@ public class LatinIME extends InputMethodServiceCompatWrapper implements Keyboar
             return;
         final int inputType = attribute.inputType;
         final int variation = inputType & InputType.TYPE_MASK_VARIATION;
-        mAutoSpace = false;
+        mShouldInsertMagicSpace = false;
         mInputTypeNoAutoCorrect = false;
         mIsSettingsSuggestionStripOn = false;
         mApplicationSpecifiedCompletionOn = false;
@@ -643,9 +646,9 @@ public class LatinIME extends InputMethodServiceCompatWrapper implements Keyboar
             }
             if (InputTypeCompatUtils.isEmailVariation(variation)
                     || variation == InputType.TYPE_TEXT_VARIATION_PERSON_NAME) {
-                mAutoSpace = false;
+                mShouldInsertMagicSpace = false;
             } else {
-                mAutoSpace = true;
+                mShouldInsertMagicSpace = true;
             }
             if (InputTypeCompatUtils.isEmailVariation(variation)) {
                 mIsSettingsSuggestionStripOn = false;
@@ -789,7 +792,7 @@ public class LatinIME extends InputMethodServiceCompatWrapper implements Keyboar
             if (TextEntryState.isAcceptedDefault() || TextEntryState.isSpaceAfterPicked()) {
                 if (TextEntryState.isAcceptedDefault())
                     TextEntryState.reset();
-                mJustAddedAutoSpace = false; // The user moved the cursor.
+                mJustAddedMagicSpace = false; // The user moved the cursor.
             }
         }
         mJustAccepted = false;
@@ -1042,23 +1045,6 @@ public class LatinIME extends InputMethodServiceCompatWrapper implements Keyboar
             ic.commitText(lastTwo.charAt(1) + " ", 1);
             ic.endBatchEdit();
             mKeyboardSwitcher.updateShiftState();
-            mJustAddedAutoSpace = true;
-        }
-    }
-
-    private void reswapPeriodAndSpace() {
-        final InputConnection ic = getCurrentInputConnection();
-        if (ic == null) return;
-        CharSequence lastThree = ic.getTextBeforeCursor(3, 0);
-        if (lastThree != null && lastThree.length() == 3
-                && lastThree.charAt(0) == Keyboard.CODE_PERIOD
-                && lastThree.charAt(1) == Keyboard.CODE_SPACE
-                && lastThree.charAt(2) == Keyboard.CODE_PERIOD) {
-            ic.beginBatchEdit();
-            ic.deleteSurroundingText(3, 0);
-            ic.commitText(" ..", 1);
-            ic.endBatchEdit();
-            mKeyboardSwitcher.updateShiftState();
         }
     }
 
@@ -1078,7 +1064,6 @@ public class LatinIME extends InputMethodServiceCompatWrapper implements Keyboar
             ic.commitText(". ", 1);
             ic.endBatchEdit();
             mKeyboardSwitcher.updateShiftState();
-            mJustAddedAutoSpace = true;
         } else {
             mHandler.startDoubleSpacesTimer();
         }
@@ -1205,9 +1190,6 @@ public class LatinIME extends InputMethodServiceCompatWrapper implements Keyboar
             handleTab();
             break;
         default:
-            if (primaryCode != Keyboard.CODE_ENTER) {
-                mJustAddedAutoSpace = false;
-            }
             if (isWordSeparator(primaryCode)) {
                 handleSeparator(primaryCode, x, y);
             } else {
@@ -1232,7 +1214,7 @@ public class LatinIME extends InputMethodServiceCompatWrapper implements Keyboar
         ic.endBatchEdit();
         mKeyboardSwitcher.updateShiftState();
         mKeyboardSwitcher.onKey(Keyboard.CODE_DUMMY);
-        mJustAddedAutoSpace = false;
+        mJustAddedMagicSpace = false;
         mEnteredText = text;
     }
 
@@ -1342,6 +1324,13 @@ public class LatinIME extends InputMethodServiceCompatWrapper implements Keyboar
     private void handleCharacter(int primaryCode, int[] keyCodes, int x, int y) {
         mVoiceProxy.handleCharacter();
 
+        if (mJustAddedMagicSpace && primaryCode == Keyboard.CODE_SINGLE_QUOTE) {
+            removeTrailingSpace();
+        }
+        if (primaryCode != Keyboard.CODE_ENTER) {
+            mJustAddedMagicSpace = false;
+        }
+
         if (mLastSelectionStart == mLastSelectionEnd && TextEntryState.isRecorrecting()) {
             abortRecorrection(false);
         }
@@ -1420,35 +1409,33 @@ public class LatinIME extends InputMethodServiceCompatWrapper implements Keyboar
             // not to auto correct, but accept the typed word. For instance,
             // in Italian dov' should not be expanded to dove' because the elision
             // requires the last vowel to be removed.
-            if (mAutoCorrectOn && primaryCode != '\'') {
+            if (mAutoCorrectOn && primaryCode != Keyboard.CODE_SINGLE_QUOTE) {
                 pickedDefault = pickDefaultSuggestion(primaryCode);
-                // Picked the suggestion by the space key.  We consider this
-                // as "added an auto space".
-                if (primaryCode == Keyboard.CODE_SPACE) {
-                    mJustAddedAutoSpace = true;
-                }
             } else {
                 commitTyped(ic);
             }
         }
-        if (mJustAddedAutoSpace && primaryCode == Keyboard.CODE_ENTER) {
-            removeTrailingSpace();
-            mJustAddedAutoSpace = false;
-        }
-        sendKeyChar((char)primaryCode);
 
-        // Handle the case of ". ." -> " .." with auto-space if necessary
-        // before changing the TextEntryState.
-        if (TextEntryState.isPunctuationAfterAccepted() && primaryCode == Keyboard.CODE_PERIOD) {
-            reswapPeriodAndSpace();
+        if (mJustAddedMagicSpace && primaryCode == Keyboard.CODE_SPACE) {
+            mJustAddedMagicSpace = false;
+        } else if (mJustAddedMagicSpace && primaryCode == Keyboard.CODE_ENTER) {
+            removeTrailingSpace();
+            mJustAddedMagicSpace = false;
+            sendKeyChar((char)primaryCode);
+        } else {
+            sendKeyChar((char)primaryCode);
         }
 
         TextEntryState.typedCharacter((char) primaryCode, true, x, y);
 
-        if (TextEntryState.isPunctuationAfterAccepted() && primaryCode != Keyboard.CODE_ENTER) {
+        if (TextEntryState.isPunctuationAfterAccepted() && primaryCode != Keyboard.CODE_ENTER
+                && mJustAddedMagicSpace) {
             swapPunctuationAndSpace();
         } else if (isSuggestionsRequested() && primaryCode == Keyboard.CODE_SPACE) {
             doubleSpace();
+            mJustAddedMagicSpace = false;
+        } else {
+            mJustAddedMagicSpace = false;
         }
         if (pickedDefault) {
             CharSequence typedWord = mWord.getTypedWord();
@@ -1720,9 +1707,8 @@ public class LatinIME extends InputMethodServiceCompatWrapper implements Keyboar
                 index, suggestions.mWords);
         TextEntryState.acceptedSuggestion(mComposing.toString(), suggestion);
         // Follow it with a space
-        if (mAutoSpace && !recorrecting) {
-            sendSpace();
-            mJustAddedAutoSpace = true;
+        if (mShouldInsertMagicSpace && !recorrecting) {
+            sendMagicSpace();
         }
 
         // We should show the hint if the user pressed the first entry AND either:
@@ -2005,8 +1991,9 @@ public class LatinIME extends InputMethodServiceCompatWrapper implements Keyboar
         return mSentenceSeparators.contains(String.valueOf((char)code));
     }
 
-    private void sendSpace() {
+    private void sendMagicSpace() {
         sendKeyChar((char)Keyboard.CODE_SPACE);
+        mJustAddedMagicSpace = true;
         mKeyboardSwitcher.updateShiftState();
     }
 
@@ -2402,7 +2389,7 @@ public class LatinIME extends InputMethodServiceCompatWrapper implements Keyboar
         p.println("  mCorrectionMode=" + mCorrectionMode);
         p.println("  mHasUncommittedTypedChars=" + mHasUncommittedTypedChars);
         p.println("  mAutoCorrectOn=" + mAutoCorrectOn);
-        p.println("  mAutoSpace=" + mAutoSpace);
+        p.println("  mShouldInsertMagicSpace=" + mShouldInsertMagicSpace);
         p.println("  mApplicationSpecifiedCompletionOn=" + mApplicationSpecifiedCompletionOn);
         p.println("  TextEntryState.state=" + TextEntryState.getState());
         p.println("  mSoundOn=" + mSoundOn);