diff --git a/res/values/strings.xml b/res/values/strings.xml
index a19aa3ba7b6cc14d1c358f18d63d4a48a8642731..2ac48a6e0f7e85ad531c1a90a6a447222af7b5cc 100644
--- a/res/values/strings.xml
+++ b/res/values/strings.xml
@@ -316,4 +316,7 @@
     <string name="language_selection_title">Select input languages</string>
     <!-- Title summary for input language selection screen -->
     <string name="language_selection_summary">Slide your finger across the spacebar to switch</string>
+    
+    <!-- Add to dictionary hint -->
+    <string name="hint_add_to_dictionary">\u2190 Tap again to save</string>
 </resources>
diff --git a/src/com/android/inputmethod/latin/CandidateView.java b/src/com/android/inputmethod/latin/CandidateView.java
index 0b6b89e6bc385d716a8b39cab0882fbcc8f13f31..0c0373b3b70bd6e8e18f29c6ae51f49d4c390e5d 100755
--- a/src/com/android/inputmethod/latin/CandidateView.java
+++ b/src/com/android/inputmethod/latin/CandidateView.java
@@ -21,7 +21,7 @@ import java.util.Arrays;
 import java.util.List;
 
 import android.content.Context;
-import android.content.Intent;
+import android.content.res.Resources;
 import android.graphics.Canvas;
 import android.graphics.Paint;
 import android.graphics.Rect;
@@ -82,6 +82,10 @@ public class CandidateView extends View {
     private Paint mPaint;
     private int mDescent;
     private boolean mScrolled;
+    private boolean mShowingAddToDictionary;
+    private CharSequence mWordToAddToDictionary;
+    private CharSequence mAddToDictionaryHint;
+
     private int mTargetScrollX;
 
     private int mMinTouchableWidth;
@@ -121,15 +125,17 @@ public class CandidateView extends View {
         LayoutInflater inflate =
             (LayoutInflater) context
                     .getSystemService(Context.LAYOUT_INFLATER_SERVICE);
+        Resources res = context.getResources();
         mPreviewPopup = new PopupWindow(context);
         mPreviewText = (TextView) inflate.inflate(R.layout.candidate_preview, null);
         mPreviewPopup.setWindowLayoutMode(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT);
         mPreviewPopup.setContentView(mPreviewText);
         mPreviewPopup.setBackgroundDrawable(null);
-        mColorNormal = context.getResources().getColor(R.color.candidate_normal);
-        mColorRecommended = context.getResources().getColor(R.color.candidate_recommended);
-        mColorOther = context.getResources().getColor(R.color.candidate_other);
-        mDivider = context.getResources().getDrawable(R.drawable.keyboard_suggest_strip_divider);
+        mColorNormal = res.getColor(R.color.candidate_normal);
+        mColorRecommended = res.getColor(R.color.candidate_recommended);
+        mColorOther = res.getColor(R.color.candidate_other);
+        mDivider = res.getDrawable(R.drawable.keyboard_suggest_strip_divider);
+        mAddToDictionaryHint = res.getString(R.string.hint_add_to_dictionary);
 
         mPaint = new Paint();
         mPaint.setColor(mColorNormal);
@@ -247,7 +253,7 @@ public class CandidateView extends View {
 
             if (touchX + scrollX >= x && touchX + scrollX < x + wordWidth && !scrolled &&
                     touchX != OUT_OF_BOUNDS) {
-                if (canvas != null) {
+                if (canvas != null && !mShowingAddToDictionary) {
                     canvas.translate(x, 0);
                     mSelectionHighlight.setBounds(0, bgPadding.top, wordWidth, height);
                     mSelectionHighlight.draw(canvas);
@@ -262,7 +268,10 @@ public class CandidateView extends View {
                 canvas.drawText(suggestion, 0, suggestion.length(), x + wordWidth / 2, y, paint);
                 paint.setColor(mColorOther);
                 canvas.translate(x + wordWidth, 0);
-                mDivider.draw(canvas);
+                // Draw a divider unless it's after the hint
+                if (!(mShowingAddToDictionary && i == 1)) {
+                    mDivider.draw(canvas);
+                }
                 canvas.translate(-x - wordWidth, 0);
             }
             paint.setTypeface(Typeface.DEFAULT);
@@ -315,6 +324,15 @@ public class CandidateView extends View {
         requestLayout();
     }
 
+    public void showAddToDictionaryHint(CharSequence word) {
+        mWordToAddToDictionary = word;
+        ArrayList<CharSequence> suggestions = new ArrayList<CharSequence>();
+        suggestions.add(word);
+        suggestions.add(mAddToDictionaryHint);
+        setSuggestions(suggestions, false, false, false);
+        mShowingAddToDictionary = true;
+    }
+
     public void scrollPrev() {
         int i = 0;
         final int count = mSuggestions.size();
@@ -364,6 +382,7 @@ public class CandidateView extends View {
         mTouchX = OUT_OF_BOUNDS;
         mSelectedString = null;
         mSelectedIndex = -1;
+        mShowingAddToDictionary = false;
         invalidate();
         Arrays.fill(mWordWidth, 0);
         Arrays.fill(mWordX, 0);
@@ -407,11 +426,16 @@ public class CandidateView extends View {
         case MotionEvent.ACTION_UP:
             if (!mScrolled) {
                 if (mSelectedString != null) {
-                    if (!mShowingCompletions) {
-                        TextEntryState.acceptedSuggestion(mSuggestions.get(0),
-                                mSelectedString);
+                    if (mShowingAddToDictionary) {
+                        longPressFirstWord();
+                        clear();
+                    } else {
+                        if (!mShowingCompletions) {
+                            TextEntryState.acceptedSuggestion(mSuggestions.get(0),
+                                    mSelectedString);
+                        }
+                        mService.pickSuggestionManually(mSelectedIndex, mSelectedString);
                     }
-                    mService.pickSuggestionManually(mSelectedIndex, mSelectedString);
                 }
             }
             mSelectedString = null;
diff --git a/src/com/android/inputmethod/latin/LatinIME.java b/src/com/android/inputmethod/latin/LatinIME.java
index 6d1a671daba71952ef9c527946ded4c725423f86..3f3793bef6c49eabac2aacb6fa544754876ec819 100644
--- a/src/com/android/inputmethod/latin/LatinIME.java
+++ b/src/com/android/inputmethod/latin/LatinIME.java
@@ -48,6 +48,7 @@ import android.text.TextUtils;
 import android.util.Log;
 import android.util.PrintWriterPrinter;
 import android.util.Printer;
+import android.view.HapticFeedbackConstants;
 import android.view.KeyEvent;
 import android.view.LayoutInflater;
 import android.view.View;
@@ -88,7 +89,6 @@ public class LatinIME extends InputMethodService
     private static final String PREF_SHOW_SUGGESTIONS = "show_suggestions";
     private static final String PREF_AUTO_COMPLETE = "auto_complete";
     private static final String PREF_VOICE_MODE = "voice_mode";
-    private static final String PREF_VOICE_SERVER_URL = "voice_server_url";
 
     // Whether or not the user has used voice input before (and thus, whether to show the
     // first-run warning dialog or not).
@@ -221,9 +221,6 @@ public class LatinIME extends InputMethodService
 
     private Tutorial mTutorial;
 
-    private Vibrator mVibrator;
-    private long mVibrateDuration;
-
     private AudioManager mAudioManager;
     // Align sound effect volume on music volume
     private final float FX_VOLUME = -1.0f;
@@ -236,7 +233,8 @@ public class LatinIME extends InputMethodService
     private long mSwipeTriggerTimeMillis;
 
     // For each word, a list of potential replacements, usually from voice.
-    private Map<String, List<CharSequence>> mWordToSuggestions = new HashMap();
+    private Map<String, List<CharSequence>> mWordToSuggestions =
+            new HashMap<String, List<CharSequence>>();
 
     private class VoiceResults {
         List<String> candidates;
@@ -295,8 +293,6 @@ public class LatinIME extends InputMethodService
         mOrientation = conf.orientation;
         initSuggestPuncList();
 
-        mVibrateDuration = mResources.getInteger(R.integer.vibrate_duration_ms);
-
         // register to receive ringer mode changes for silent mode
         IntentFilter filter = new IntentFilter(AudioManager.RINGER_MODE_CHANGED_ACTION);
         registerReceiver(mReceiver, filter);
@@ -1422,6 +1418,9 @@ public class LatinIME extends InputMethodService
         }
         // Fool the state watcher so that a subsequent backspace will not do a revert
         TextEntryState.typedCharacter((char) KEYCODE_SPACE, true);
+        if (index == 0 && !mSuggest.isValidWord(suggestion)) {
+            mCandidateView.showAddToDictionaryHint(suggestion);
+        }
     }
 
     private void pickSuggestion(CharSequence suggestion) {
@@ -1679,10 +1678,10 @@ public class LatinIME extends InputMethodService
         if (!mVibrateOn) {
             return;
         }
-        if (mVibrator == null) {
-            mVibrator = (Vibrator) getSystemService(VIBRATOR_SERVICE);
+        if (mInputView != null) {
+            mInputView.performHapticFeedback(HapticFeedbackConstants.VIRTUAL_KEY,
+                    HapticFeedbackConstants.FLAG_IGNORE_GLOBAL_SETTING);
         }
-        mVibrator.vibrate(mVibrateDuration);
     }
 
     private void checkTutorial(String privateImeOptions) {
@@ -1783,16 +1782,6 @@ public class LatinIME extends InputMethodService
         mLanguageSwitcher.loadLocales(sp);
     }
 
-    private String getPersistedInputLanguage() {
-        SharedPreferences sp = PreferenceManager.getDefaultSharedPreferences(this);
-        return sp.getString(PREF_INPUT_LANGUAGE, null);
-    }
-
-    private String getSelectedInputLanguages() {
-        SharedPreferences sp = PreferenceManager.getDefaultSharedPreferences(this);
-        return sp.getString(PREF_SELECTED_LANGUAGES, null);
-    }
-
     private void initSuggestPuncList() {
         mSuggestPuncList = new ArrayList<CharSequence>();
         String suggestPuncs = mResources.getString(R.string.suggested_punctuations);