Merge "Lower the key preview position a bit on phone layout"
diff --git a/java/res/values/strings.xml b/java/res/values/strings.xml
index 51fafd4..88f07f5 100644
--- a/java/res/values/strings.xml
+++ b/java/res/values/strings.xml
@@ -46,6 +46,18 @@
     <!-- Category title for misc options  -->
     <string name="misc_category">Other options</string>
 
+    <!-- Option name for advanced settings screen [CHAR LIMIT=25] -->
+    <string name="advanced_settings">Advanced settings</string>
+    <!-- Option summary for advanced settings screen [CHAR LIMIT=65 (two lines) or 30 (fits on one line, preferable)] -->
+    <string name="advanced_settings_summary">Options for expert users</string>
+
+    <!-- Option for the dismiss delay of the key popup [CHAR LIMIT=25] -->
+    <string name="key_preview_popup_dismiss_delay">Key popup dismiss delay</string>
+    <!-- Description for delay for dismissing a popup on keypress: no delay [CHAR LIMIT=15] -->
+    <string name="key_preview_popup_dismiss_no_delay">No delay</string>
+    <!-- Description for delay for dismissing a popup on screen: default value of the delay [CHAR LIMIT=15] -->
+    <string name="key_preview_popup_dismiss_default_delay">Default</string>
+
     <!-- Option to enable auto capitalization of sentences -->
     <string name="auto_cap">Auto-capitalization</string>
 
diff --git a/java/res/xml/prefs.xml b/java/res/xml/prefs.xml
index 24a1d45..b0497a5 100644
--- a/java/res/xml/prefs.xml
+++ b/java/res/xml/prefs.xml
@@ -130,6 +130,15 @@
           android:entries="@array/keyboard_layout_modes"
           android:defaultValue="@string/config_default_keyboard_theme_id" />
     </PreferenceCategory>
+    <PreferenceScreen
+          android:key="pref_advanced_settings"
+          android:title="@string/advanced_settings"
+          android:summary="@string/advanced_settings_summary">
+          <!-- Values for popup dismiss delay are added programatically -->
+          <ListPreference
+              android:key="pref_key_preview_popup_dismiss_delay"
+              android:title="@string/key_preview_popup_dismiss_delay" />
+    </PreferenceScreen>
     <!-- <Preference
         android:title="Debug Settings"
         android:key="debug_settings">
diff --git a/java/src/com/android/inputmethod/deprecated/recorrection/Recorrection.java b/java/src/com/android/inputmethod/deprecated/recorrection/Recorrection.java
index adf4204..bf69d5c 100644
--- a/java/src/com/android/inputmethod/deprecated/recorrection/Recorrection.java
+++ b/java/src/com/android/inputmethod/deprecated/recorrection/Recorrection.java
@@ -42,7 +42,8 @@
 /**
  * Manager of re-correction functionalities
  */
-public class Recorrection {
+public class Recorrection implements SharedPreferences.OnSharedPreferenceChangeListener {
+    public static final boolean USE_LEGACY_RECORRECTION = true;
     private static final Recorrection sInstance = new Recorrection();
 
     private LatinIME mService;
@@ -69,20 +70,17 @@
     }
 
     private void initInternal(LatinIME context, SharedPreferences prefs) {
-        final Resources res = context.getResources();
-        // If the option should not be shown, do not read the re-correction preference
-        // but always use the default setting defined in the resources.
-        if (res.getBoolean(R.bool.config_enable_show_recorrection_option)) {
-            mRecorrectionEnabled = prefs.getBoolean(Settings.PREF_RECORRECTION_ENABLED,
-                    res.getBoolean(R.bool.config_default_recorrection_enabled));
-        } else {
-            mRecorrectionEnabled = res.getBoolean(R.bool.config_default_recorrection_enabled);
+        if (!USE_LEGACY_RECORRECTION) {
+            mRecorrectionEnabled = false;
+            return;
         }
+        updateRecorrectionEnabled(context.getResources(), prefs);
         mService = context;
+        prefs.registerOnSharedPreferenceChangeListener(this);
     }
 
     public void checkRecorrectionOnStart() {
-        if (!mRecorrectionEnabled) return;
+        if (!USE_LEGACY_RECORRECTION || !mRecorrectionEnabled) return;
 
         final InputConnection ic = mService.getCurrentInputConnection();
         if (ic == null) return;
@@ -112,7 +110,7 @@
             CandidateView candidateView, int candidatesStart, int candidatesEnd,
             int newSelStart, int newSelEnd, int oldSelStart, int lastSelectionStart,
             int lastSelectionEnd, boolean hasUncommittedTypedChars) {
-        if (!mRecorrectionEnabled) return;
+        if (!USE_LEGACY_RECORRECTION || !mRecorrectionEnabled) return;
         if (!mService.isShowingSuggestionsStrip()) return;
         if (!keyboardSwitcher.isInputViewShown()) return;
         if (!mService.isSuggestionsRequested()) return;
@@ -144,7 +142,7 @@
     }
 
     public void saveRecorrectionSuggestion(WordComposer word, CharSequence result) {
-        if (!mRecorrectionEnabled) return;
+        if (!USE_LEGACY_RECORRECTION || !mRecorrectionEnabled) return;
         if (word.size() <= 1) {
             return;
         }
@@ -172,6 +170,7 @@
      */
     public boolean applyTypedAlternatives(WordComposer word, Suggest suggest,
             KeyboardSwitcher keyboardSwitcher, EditingUtils.SelectedWord touching) {
+        if (!USE_LEGACY_RECORRECTION || !mRecorrectionEnabled) return false;
         // If we didn't find a match, search for result in typed word history
         WordComposer foundWord = null;
         RecorrectionSuggestionEntries alternatives = null;
@@ -224,6 +223,7 @@
             boolean hasUncommittedTypedChars, int lastSelectionStart, int lastSelectionEnd,
             String wordSeparators) {
         if (!InputConnectionCompatUtils.RECORRECTION_SUPPORTED) return;
+        if (!USE_LEGACY_RECORRECTION || !mRecorrectionEnabled) return;
         voiceProxy.setShowingVoiceSuggestions(false);
         if (candidateView != null && candidateView.isShowingAddToDictionaryHint()) {
             return;
@@ -257,6 +257,7 @@
     }
 
     public void abortRecorrection(boolean force) {
+        if (!USE_LEGACY_RECORRECTION) return;
         if (force || TextEntryState.isRecorrecting()) {
             TextEntryState.onAbortRecorrection();
             mService.setCandidatesViewShown(mService.isCandidateStripVisible());
@@ -264,4 +265,23 @@
             mService.clearSuggestions();
         }
     }
+
+    public void updateRecorrectionEnabled(Resources res, SharedPreferences prefs) {
+        // If the option should not be shown, do not read the re-correction preference
+        // but always use the default setting defined in the resources.
+        if (res.getBoolean(R.bool.config_enable_show_recorrection_option)) {
+            mRecorrectionEnabled = prefs.getBoolean(Settings.PREF_RECORRECTION_ENABLED,
+                    res.getBoolean(R.bool.config_default_recorrection_enabled));
+        } else {
+            mRecorrectionEnabled = res.getBoolean(R.bool.config_default_recorrection_enabled);
+        }
+    }
+
+    @Override
+    public void onSharedPreferenceChanged(SharedPreferences prefs, String key) {
+        if (!USE_LEGACY_RECORRECTION) return;
+        if (key.equals(Settings.PREF_RECORRECTION_ENABLED)) {
+            updateRecorrectionEnabled(mService.getResources(), prefs);
+        }
+    }
 }
diff --git a/java/src/com/android/inputmethod/keyboard/KeyboardSwitcher.java b/java/src/com/android/inputmethod/keyboard/KeyboardSwitcher.java
index 333fbc7..3841391 100644
--- a/java/src/com/android/inputmethod/keyboard/KeyboardSwitcher.java
+++ b/java/src/com/android/inputmethod/keyboard/KeyboardSwitcher.java
@@ -153,7 +153,9 @@
         final KeyboardId id = getKeyboardId(attribute, isSymbols);
         makeSymbolsKeyboardIds(id.mMode, attribute);
         mCurrentId = id;
-        mInputView.setKeyPreviewEnabled(mInputMethodService.getPopupOn());
+        final Resources res = mInputMethodService.getResources();
+        mInputView.setKeyPreviewPopupEnabled(Settings.Values.isKeyPreviewPopupEnabled(mPrefs, res),
+                Settings.Values.getKeyPreviewPopupDismissDelay(mPrefs, res));
         setKeyboard(getKeyboard(id));
     }
 
diff --git a/java/src/com/android/inputmethod/keyboard/KeyboardView.java b/java/src/com/android/inputmethod/keyboard/KeyboardView.java
index 1ea1436..4f85c03 100644
--- a/java/src/com/android/inputmethod/keyboard/KeyboardView.java
+++ b/java/src/com/android/inputmethod/keyboard/KeyboardView.java
@@ -58,14 +58,22 @@
  * A view that renders a virtual {@link Keyboard}. It handles rendering of keys and detecting key
  * presses and touch movements.
  *
+ * @attr ref R.styleable#KeyboardView_backgroundDimAmount
+ * @attr ref R.styleable#KeyboardView_colorScheme
  * @attr ref R.styleable#KeyboardView_keyBackground
+ * @attr ref R.styleable#KeyboardView_keyHysteresisDistance
+ * @attr ref R.styleable#KeyboardView_keyLetterRatio
+ * @attr ref R.styleable#KeyboardView_keyLetterStyle
  * @attr ref R.styleable#KeyboardView_keyPreviewLayout
  * @attr ref R.styleable#KeyboardView_keyPreviewOffset
- * @attr ref R.styleable#KeyboardView_labelTextSize
- * @attr ref R.styleable#KeyboardView_keyTextSize
+ * @attr ref R.styleable#KeyboardView_keyPreviewHeight
  * @attr ref R.styleable#KeyboardView_keyTextColor
+ * @attr ref R.styleable#KeyboardView_keyTextColorDisabled
+ * @attr ref R.styleable#KeyboardView_labelTextRatio
  * @attr ref R.styleable#KeyboardView_verticalCorrection
  * @attr ref R.styleable#KeyboardView_popupLayout
+ * @attr ref R.styleable#KeyboardView_shadowColor
+ * @attr ref R.styleable#KeyboardView_shadowRadius
  */
 public class KeyboardView extends View implements PointerTracker.UIProxy {
     private static final String TAG = KeyboardView.class.getSimpleName();
@@ -86,36 +94,36 @@
     private static final int HINT_ICON_VERTICAL_ADJUSTMENT_PIXEL = -1;
 
     // XML attribute
-    private float mKeyLetterRatio;
-    private int mKeyLetterSize;
-    private int mKeyTextColor;
-    private int mKeyTextColorDisabled;
-    private Typeface mKeyLetterStyle = Typeface.DEFAULT;
-    private float mLabelTextRatio;
-    private int mLabelTextSize;
-    private int mColorScheme = COLOR_SCHEME_WHITE;
-    private int mShadowColor;
-    private float mShadowRadius;
-    private Drawable mKeyBackground;
-    private float mBackgroundDimAmount;
-    private float mKeyHysteresisDistance;
-    private float mVerticalCorrection;
-    private int mPreviewOffset;
-    private int mPreviewHeight;
-    private int mPopupLayout;
+    private final float mKeyLetterRatio;
+    private final int mKeyTextColor;
+    private final int mKeyTextColorDisabled;
+    private final Typeface mKeyLetterStyle;
+    private final float mLabelTextRatio;
+    private final int mColorScheme;
+    private final int mShadowColor;
+    private final float mShadowRadius;
+    private final Drawable mKeyBackground;
+    private final float mBackgroundDimAmount;
+    private final float mKeyHysteresisDistance;
+    private final float mVerticalCorrection;
+    private final int mPreviewOffset;
+    private final int mPreviewHeight;
+    private final int mPopupLayout;
 
     // Main keyboard
     private Keyboard mKeyboard;
+    private int mKeyLetterSize;
+    private int mLabelTextSize;
 
     // Key preview
     private boolean mInForeground;
     private TextView mPreviewText;
     private float mPreviewTextRatio;
     private int mPreviewTextSize;
-    private boolean mShowKeyPreview = true;
-    private int mKeyPreviewDisplayedY;
+    private boolean mShowKeyPreviewPopup = true;
+    private int mKeyPreviewPopupDisplayedY;
     private final int mDelayBeforePreview;
-    private final int mDelayAfterPreview;
+    private int mDelayAfterPreview;
     private ViewGroup mPreviewPlacer;
     private final int[] mCoordinates = new int[2];
 
@@ -303,66 +311,28 @@
 
         final TypedArray a = context.obtainStyledAttributes(
                 attrs, R.styleable.KeyboardView, defStyle, R.style.KeyboardView);
-        int previewLayout = 0;
-        int keyTextSize = 0;
 
-        int n = a.getIndexCount();
-
-        for (int i = 0; i < n; i++) {
-            int attr = a.getIndex(i);
-
-            switch (attr) {
-            case R.styleable.KeyboardView_keyBackground:
-                mKeyBackground = a.getDrawable(attr);
-                break;
-            case R.styleable.KeyboardView_keyHysteresisDistance:
-                mKeyHysteresisDistance = a.getDimensionPixelOffset(attr, 0);
-                break;
-            case R.styleable.KeyboardView_verticalCorrection:
-                mVerticalCorrection = a.getDimensionPixelOffset(attr, 0);
-                break;
-            case R.styleable.KeyboardView_keyPreviewLayout:
-                previewLayout = a.getResourceId(attr, 0);
-                break;
-            case R.styleable.KeyboardView_keyPreviewOffset:
-                mPreviewOffset = a.getDimensionPixelOffset(attr, 0);
-                break;
-            case R.styleable.KeyboardView_keyPreviewHeight:
-                mPreviewHeight = a.getDimensionPixelSize(attr, 80);
-                break;
-            case R.styleable.KeyboardView_keyLetterRatio:
-                mKeyLetterRatio = getRatio(a, attr);
-                break;
-            case R.styleable.KeyboardView_keyTextColor:
-                mKeyTextColor = a.getColor(attr, 0xFF000000);
-                break;
-            case R.styleable.KeyboardView_keyTextColorDisabled:
-                mKeyTextColorDisabled = a.getColor(attr, 0xFF000000);
-                break;
-            case R.styleable.KeyboardView_labelTextRatio:
-                mLabelTextRatio = getRatio(a, attr);
-                break;
-            case R.styleable.KeyboardView_popupLayout:
-                mPopupLayout = a.getResourceId(attr, 0);
-                break;
-            case R.styleable.KeyboardView_shadowColor:
-                mShadowColor = a.getColor(attr, 0);
-                break;
-            case R.styleable.KeyboardView_shadowRadius:
-                mShadowRadius = a.getFloat(attr, 0f);
-                break;
-            // TODO: Use Theme (android.R.styleable.Theme_backgroundDimAmount)
-            case R.styleable.KeyboardView_backgroundDimAmount:
-                mBackgroundDimAmount = a.getFloat(attr, 0.5f);
-                break;
-            case R.styleable.KeyboardView_keyLetterStyle:
-                mKeyLetterStyle = Typeface.defaultFromStyle(a.getInt(attr, Typeface.NORMAL));
-                break;
-            case R.styleable.KeyboardView_colorScheme:
-                mColorScheme = a.getInt(attr, COLOR_SCHEME_WHITE);
-                break;
-            }
-        }
+        mKeyBackground = a.getDrawable(R.styleable.KeyboardView_keyBackground);
+        mKeyHysteresisDistance = a.getDimensionPixelOffset(
+                R.styleable.KeyboardView_keyHysteresisDistance, 0);
+        mVerticalCorrection = a.getDimensionPixelOffset(
+                R.styleable.KeyboardView_verticalCorrection, 0);
+        final int previewLayout = a.getResourceId(R.styleable.KeyboardView_keyPreviewLayout, 0);
+        mPreviewOffset = a.getDimensionPixelOffset(R.styleable.KeyboardView_keyPreviewOffset, 0);
+        mPreviewHeight = a.getDimensionPixelSize(R.styleable.KeyboardView_keyPreviewHeight, 80);
+        mKeyLetterRatio = getRatio(a, R.styleable.KeyboardView_keyLetterRatio);
+        mKeyTextColor = a.getColor(R.styleable.KeyboardView_keyTextColor, 0xFF000000);
+        mKeyTextColorDisabled = a.getColor(
+                R.styleable.KeyboardView_keyTextColorDisabled, 0xFF000000);
+        mLabelTextRatio = getRatio(a, R.styleable.KeyboardView_labelTextRatio);
+        mPopupLayout = a.getResourceId(R.styleable.KeyboardView_popupLayout, 0);
+        mShadowColor = a.getColor(R.styleable.KeyboardView_shadowColor, 0);
+        mShadowRadius = a.getFloat(R.styleable.KeyboardView_shadowRadius, 0f);
+        // TODO: Use Theme (android.R.styleable.Theme_backgroundDimAmount)
+        mBackgroundDimAmount = a.getFloat(R.styleable.KeyboardView_backgroundDimAmount, 0.5f);
+        mKeyLetterStyle = Typeface.defaultFromStyle(
+                a.getInt(R.styleable.KeyboardView_keyLetterStyle, Typeface.NORMAL));
+        mColorScheme = a.getInt(R.styleable.KeyboardView_colorScheme, COLOR_SCHEME_WHITE);
 
         final Resources res = getResources();
 
@@ -370,7 +340,7 @@
             mPreviewText = (TextView) LayoutInflater.from(context).inflate(previewLayout, null);
             mPreviewTextRatio = getRatio(res, R.fraction.key_preview_text_ratio);
         } else {
-            mShowKeyPreview = false;
+            mShowKeyPreviewPopup = false;
         }
         mDelayBeforePreview = res.getInteger(R.integer.config_delay_before_preview);
         mDelayAfterPreview = res.getInteger(R.integer.config_delay_after_preview);
@@ -379,7 +349,6 @@
 
         mPaint = new Paint();
         mPaint.setAntiAlias(true);
-        mPaint.setTextSize(keyTextSize);
         mPaint.setTextAlign(Align.CENTER);
         mPaint.setAlpha(255);
 
@@ -547,19 +516,21 @@
      * Enables or disables the key feedback popup. This is a popup that shows a magnified
      * version of the depressed key. By default the preview is enabled.
      * @param previewEnabled whether or not to enable the key feedback preview
-     * @see #isKeyPreviewEnabled()
+     * @param delay the delay after which the preview is dismissed
+     * @see #isKeyPreviewPopupEnabled()
      */
-    public void setKeyPreviewEnabled(boolean previewEnabled) {
-        mShowKeyPreview = previewEnabled;
+    public void setKeyPreviewPopupEnabled(boolean previewEnabled, int delay) {
+        mShowKeyPreviewPopup = previewEnabled;
+        mDelayAfterPreview = delay;
     }
 
     /**
      * Returns the enabled state of the key feedback preview
      * @return whether or not the key feedback preview is enabled
-     * @see #setKeyPreviewEnabled(boolean)
+     * @see #setKeyPreviewPopupEnabled(boolean, int)
      */
-    public boolean isKeyPreviewEnabled() {
-        return mShowKeyPreview;
+    public boolean isKeyPreviewPopupEnabled() {
+        return mShowKeyPreviewPopup;
     }
 
     public int getColorScheme() {
@@ -882,7 +853,7 @@
 
     @Override
     public void showKeyPreview(int keyIndex, PointerTracker tracker) {
-        if (mShowKeyPreview) {
+        if (mShowKeyPreviewPopup) {
             mHandler.showKeyPreview(mDelayBeforePreview, keyIndex, tracker);
         } else if (mKeyboard.needSpacebarPreview(keyIndex)) {
             // Show key preview (in this case, slide language switcher) without any delay.
@@ -892,7 +863,7 @@
 
     @Override
     public void dismissKeyPreview(PointerTracker tracker) {
-        if (mShowKeyPreview) {
+        if (mShowKeyPreviewPopup) {
             mHandler.cancelShowKeyPreview(tracker);
             mHandler.dismissKeyPreview(mDelayAfterPreview, tracker);
         } else if (mKeyboard.needSpacebarPreview(KeyDetector.NOT_A_KEY)) {
@@ -977,7 +948,7 @@
         final int previewX = keyDrawX - (previewWidth - keyDrawWidth) / 2 + mCoordinates[0];
         final int previewY = key.mY - previewHeight + mCoordinates[1] + mPreviewOffset;
         // Record key preview position to display mini-keyboard later at the same position
-        mKeyPreviewDisplayedY = previewY;
+        mKeyPreviewPopupDisplayedY = previewY;
 
         // Place the key preview.
         // TODO: Adjust position of key previews which touch screen edges
@@ -1128,7 +1099,7 @@
             mPopupWindow.setClippingEnabled(false);
         }
         mPopupMiniKeyboardPanel = popupPanel;
-        popupPanel.showPanel(this, parentKey, tracker, mKeyPreviewDisplayedY, mPopupWindow);
+        popupPanel.showPanel(this, parentKey, tracker, mKeyPreviewPopupDisplayedY, mPopupWindow);
 
         invalidateAllKeys();
         return true;
diff --git a/java/src/com/android/inputmethod/keyboard/LatinKeyboardView.java b/java/src/com/android/inputmethod/keyboard/LatinKeyboardView.java
index c98076f..583b997 100644
--- a/java/src/com/android/inputmethod/keyboard/LatinKeyboardView.java
+++ b/java/src/com/android/inputmethod/keyboard/LatinKeyboardView.java
@@ -55,14 +55,14 @@
     }
 
     @Override
-    public void setKeyPreviewEnabled(boolean previewEnabled) {
+    public void setKeyPreviewPopupEnabled(boolean previewEnabled, int delay) {
         LatinKeyboard latinKeyboard = getLatinKeyboard();
         if (latinKeyboard != null
                 && (latinKeyboard.isPhoneKeyboard() || latinKeyboard.isNumberKeyboard())) {
             // Phone and number keyboard never shows popup preview (except language switch).
-            super.setKeyPreviewEnabled(false);
+            super.setKeyPreviewPopupEnabled(false, delay);
         } else {
-            super.setKeyPreviewEnabled(previewEnabled);
+            super.setKeyPreviewPopupEnabled(previewEnabled, delay);
         }
     }
 
@@ -173,7 +173,8 @@
                 if (!mDroppingEvents) {
                     mDroppingEvents = true;
                     // Send an up event
-                    MotionEvent translated = MotionEvent.obtain(me.getEventTime(), me.getEventTime(),
+                    MotionEvent translated = MotionEvent.obtain(
+                            me.getEventTime(), me.getEventTime(),
                             MotionEvent.ACTION_UP,
                             mLastX, mLastY, me.getMetaState());
                     super.onTouchEvent(translated);
diff --git a/java/src/com/android/inputmethod/keyboard/PopupMiniKeyboardView.java b/java/src/com/android/inputmethod/keyboard/PopupMiniKeyboardView.java
index 12031f1..561dcbc 100644
--- a/java/src/com/android/inputmethod/keyboard/PopupMiniKeyboardView.java
+++ b/java/src/com/android/inputmethod/keyboard/PopupMiniKeyboardView.java
@@ -55,13 +55,14 @@
                 R.dimen.mini_keyboard_slide_allowance));
         // Remove gesture detector on mini-keyboard
         mGestureDetector = null;
-        setKeyPreviewEnabled(false);
+        setKeyPreviewPopupEnabled(false, 0);
     }
 
     @Override
-    public void setKeyPreviewEnabled(boolean previewEnabled) {
-        // Mini keyboard needs no pop-up key preview displayed.
-        super.setKeyPreviewEnabled(false);
+    public void setKeyPreviewPopupEnabled(boolean previewEnabled, int delay) {
+        // Mini keyboard needs no pop-up key preview displayed, so we pass always false with a
+        // delay of 0. The delay does not matter actually since the popup is not shown anyway.
+        super.setKeyPreviewPopupEnabled(false, 0);
     }
 
     @Override
@@ -82,8 +83,8 @@
                 - (container.getMeasuredHeight() - container.getPaddingBottom())
                 + parentKeyboardView.getPaddingTop() + mCoordinates[1];
         final int x = miniKeyboardX;
-        final int y = parentKeyboardView.isKeyPreviewEnabled() && miniKeyboard.isOneRowKeyboard()
-                ? keyPreviewY : miniKeyboardY;
+        final int y = parentKeyboardView.isKeyPreviewPopupEnabled() &&
+                miniKeyboard.isOneRowKeyboard() ? keyPreviewY : miniKeyboardY;
 
         if (miniKeyboard.setShifted(parentKeyboard.isShiftedOrShiftLocked())) {
             invalidateAllKeys();
diff --git a/java/src/com/android/inputmethod/latin/LatinIME.java b/java/src/com/android/inputmethod/latin/LatinIME.java
index 2d4d7b9..964dd99 100644
--- a/java/src/com/android/inputmethod/latin/LatinIME.java
+++ b/java/src/com/android/inputmethod/latin/LatinIME.java
@@ -556,7 +556,8 @@
 
         updateCorrectionMode();
 
-        inputView.setKeyPreviewEnabled(mSettingsValues.mPopupOn);
+        inputView.setKeyPreviewPopupEnabled(mSettingsValues.mKeyPreviewPopupOn,
+                mSettingsValues.mKeyPreviewPopupDismissDelay);
         inputView.setProximityCorrectionEnabled(true);
         // If we just entered a text field, maybe it has some old text that requires correction
         mRecorrection.checkRecorrectionOnStart();
@@ -1465,14 +1466,17 @@
         // in most cases, suggestion count is 1 when typed word's length is 1, but we do always
         // need to clear the previous state when the user starts typing a word (i.e. typed word's
         // length == 1).
-        if (builder.size() > 1 || typedWord.length() == 1 || typedWordValid
-                || mCandidateView.isShowingAddToDictionaryHint()) {
-            builder.setTypedWordValid(typedWordValid).setHasMinimalSuggestion(correctionAvailable);
-        } else {
-            final SuggestedWords previousSuggestions = mCandidateView.getSuggestions();
-            if (previousSuggestions == mSettingsValues.mSuggestPuncList)
-                return;
-            builder.addTypedWordAndPreviousSuggestions(typedWord, previousSuggestions);
+        if (typedWord != null) {
+            if (builder.size() > 1 || typedWord.length() == 1 || typedWordValid
+                    || mCandidateView.isShowingAddToDictionaryHint()) {
+                builder.setTypedWordValid(typedWordValid).setHasMinimalSuggestion(
+                        correctionAvailable);
+            } else {
+                final SuggestedWords previousSuggestions = mCandidateView.getSuggestions();
+                if (previousSuggestions == mSettingsValues.mSuggestPuncList)
+                    return;
+                builder.addTypedWordAndPreviousSuggestions(typedWord, previousSuggestions);
+            }
         }
         showSuggestions(builder.build(), typedWord);
     }
@@ -1603,11 +1607,8 @@
             // TextEntryState.State.PICKED_SUGGESTION state.
             TextEntryState.typedCharacter((char) Keyboard.CODE_SPACE, true,
                     WordComposer.NOT_A_COORDINATE, WordComposer.NOT_A_COORDINATE);
-            // On Honeycomb+, onUpdateSelection() will fire, but in Gingerbread- in WebTextView
-            // only it does not, for some reason. Force update suggestions so that it works
-            // in Gingerbread- in WebTextView too.
-            mHandler.postUpdateSuggestions();
-        } else if (!showingAddToDictionaryHint) {
+        }
+        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.
             clearSuggestions();
@@ -1920,9 +1921,6 @@
         return mWord;
     }
 
-    public boolean getPopupOn() {
-        return mSettingsValues.mPopupOn;
-    }
     boolean isSoundOn() {
         return mSettingsValues.mSoundOn && !mSilentModeOn;
     }
@@ -2066,7 +2064,7 @@
         p.println("  TextEntryState.state=" + TextEntryState.getState());
         p.println("  mSoundOn=" + mSettingsValues.mSoundOn);
         p.println("  mVibrateOn=" + mSettingsValues.mVibrateOn);
-        p.println("  mPopupOn=" + mSettingsValues.mPopupOn);
+        p.println("  mKeyPreviewPopupOn=" + mSettingsValues.mKeyPreviewPopupOn);
     }
 
     // Characters per second measurement
diff --git a/java/src/com/android/inputmethod/latin/Settings.java b/java/src/com/android/inputmethod/latin/Settings.java
index 8953731..7c323c1 100644
--- a/java/src/com/android/inputmethod/latin/Settings.java
+++ b/java/src/com/android/inputmethod/latin/Settings.java
@@ -56,7 +56,7 @@
     public static final String PREF_GENERAL_SETTINGS_KEY = "general_settings";
     public static final String PREF_VIBRATE_ON = "vibrate_on";
     public static final String PREF_SOUND_ON = "sound_on";
-    public static final String PREF_POPUP_ON = "popup_on";
+    public static final String PREF_KEY_PREVIEW_POPUP_ON = "popup_on";
     public static final String PREF_RECORRECTION_ENABLED = "recorrection_enabled";
     public static final String PREF_AUTO_CAP = "auto_cap";
     public static final String PREF_SETTINGS_KEY = "settings_key";
@@ -77,6 +77,9 @@
 
     public static final String PREF_MISC_SETTINGS_KEY = "misc_settings";
 
+    public static final String PREF_KEY_PREVIEW_POPUP_DISMISS_DELAY =
+            "pref_key_preview_popup_dismiss_delay";
+
     public static final String PREF_USABILITY_STUDY_MODE = "usability_study_mode";
 
     // Dialog ids
@@ -102,7 +105,8 @@
         // From preferences:
         public final boolean mSoundOn; // Sound setting private to Latin IME (see mSilentModeOn)
         public final boolean mVibrateOn;
-        public final boolean mPopupOn; // Warning : this escapes through LatinIME#isPopupOn
+        public final boolean mKeyPreviewPopupOn;
+        public final int mKeyPreviewPopupDismissDelay;
         public final boolean mAutoCap;
         public final boolean mQuickFixes;
         public final boolean mAutoCorrectEnabled;
@@ -161,7 +165,8 @@
             mSoundOn = prefs.getBoolean(Settings.PREF_SOUND_ON,
                     res.getBoolean(R.bool.config_default_sound_enabled));
 
-            mPopupOn = isPopupEnabled(prefs, res);
+            mKeyPreviewPopupOn = isKeyPreviewPopupEnabled(prefs, res);
+            mKeyPreviewPopupDismissDelay = getKeyPreviewPopupDismissDelay(prefs, res);
             mAutoCap = prefs.getBoolean(Settings.PREF_AUTO_CAP, true);
             mQuickFixes = isQuickFixesEnabled(prefs, res);
 
@@ -202,6 +207,7 @@
             return sp.getBoolean(Settings.PREF_QUICK_FIXES, resources.getBoolean(
                     R.bool.config_default_quick_fixes));
         }
+
         private static boolean isAutoCorrectEnabled(SharedPreferences sp, Resources resources) {
             final String currentAutoCorrectionSetting = sp.getString(
                     Settings.PREF_AUTO_CORRECTION_THRESHOLD,
@@ -210,13 +216,24 @@
                     R.string.auto_correction_threshold_mode_index_off);
             return !currentAutoCorrectionSetting.equals(autoCorrectionOff);
         }
-        private static boolean isPopupEnabled(SharedPreferences sp, Resources resources) {
+
+        // Public to access from KeyboardSwitcher. Should it have access to some
+        // process-global instance instead?
+        public static boolean isKeyPreviewPopupEnabled(SharedPreferences sp, Resources resources) {
             final boolean showPopupOption = resources.getBoolean(
                     R.bool.config_enable_show_popup_on_keypress_option);
             if (!showPopupOption) return resources.getBoolean(R.bool.config_default_popup_preview);
-            return sp.getBoolean(Settings.PREF_POPUP_ON,
+            return sp.getBoolean(Settings.PREF_KEY_PREVIEW_POPUP_ON,
                     resources.getBoolean(R.bool.config_default_popup_preview));
         }
+
+        // Likewise
+        public static int getKeyPreviewPopupDismissDelay(SharedPreferences sp,
+                Resources resources) {
+            return Integer.parseInt(sp.getString(Settings.PREF_KEY_PREVIEW_POPUP_DISMISS_DELAY,
+                    Integer.toString(resources.getInteger(R.integer.config_delay_after_preview))));
+        }
+
         private static boolean isBigramSuggestionEnabled(SharedPreferences sp, Resources resources,
                 boolean autoCorrectEnabled) {
             final boolean showBigramSuggestionsOption = resources.getBoolean(
@@ -227,11 +244,13 @@
             return sp.getBoolean(Settings.PREF_BIGRAM_SUGGESTIONS, resources.getBoolean(
                     R.bool.config_default_bigram_suggestions));
         }
+
         private static boolean isBigramPredictionEnabled(SharedPreferences sp,
                 Resources resources) {
             return sp.getBoolean(Settings.PREF_BIGRAM_PREDICTIONS, resources.getBoolean(
                     R.bool.config_default_bigram_prediction));
         }
+
         private static double getAutoCorrectionThreshold(SharedPreferences sp,
                 Resources resources) {
             final String currentAutoCorrectionSetting = sp.getString(
@@ -257,6 +276,7 @@
             }
             return autoCorrectionThreshold;
         }
+
         private static SuggestedWords createSuggestPuncList(final String puncs) {
             SuggestedWords.Builder builder = new SuggestedWords.Builder();
             if (puncs != null) {
@@ -274,6 +294,7 @@
     private ListPreference mSettingsKeyPreference;
     private ListPreference mShowCorrectionSuggestionsPreference;
     private ListPreference mAutoCorrectionThreshold;
+    private ListPreference mKeyPreviewPopupDismissDelay;
     // Suggestion: use bigrams to adjust scores of suggestions obtained from unigram dictionary
     private CheckBoxPreference mBigramSuggestion;
     // Prediction: use bigrams to predict the next word when there is no input for it yet
@@ -299,6 +320,8 @@
     @Override
     protected void onCreate(Bundle icicle) {
         super.onCreate(icicle);
+        final Resources res = getResources();
+
         addPreferencesFromResource(R.xml.prefs);
         mInputLanguageSelection = (PreferenceScreen) findPreference(PREF_SUBTYPES);
         mInputLanguageSelection.setOnPreferenceClickListener(this);
@@ -334,13 +357,13 @@
         final PreferenceGroup bigramGroup =
                 (PreferenceGroup) findPreference(PREF_NGRAM_SETTINGS_KEY);
 
-        final boolean showSettingsKeyOption = getResources().getBoolean(
+        final boolean showSettingsKeyOption = res.getBoolean(
                 R.bool.config_enable_show_settings_key_option);
         if (!showSettingsKeyOption) {
             generalSettings.removePreference(mSettingsKeyPreference);
         }
 
-        final boolean showVoiceKeyOption = getResources().getBoolean(
+        final boolean showVoiceKeyOption = res.getBoolean(
                 R.bool.config_enable_show_voice_key_option);
         if (!showVoiceKeyOption) {
             generalSettings.removePreference(mVoicePreference);
@@ -350,43 +373,60 @@
             generalSettings.removePreference(findPreference(PREF_VIBRATE_ON));
         }
 
-        final boolean showSubtypeSettings = getResources().getBoolean(
+        final boolean showSubtypeSettings = res.getBoolean(
                 R.bool.config_enable_show_subtype_settings);
         if (InputMethodServiceCompatWrapper.CAN_HANDLE_ON_CURRENT_INPUT_METHOD_SUBTYPE_CHANGED
                 && !showSubtypeSettings) {
             generalSettings.removePreference(findPreference(PREF_SUBTYPES));
         }
 
-        final boolean showPopupOption = getResources().getBoolean(
+        final boolean showPopupOption = res.getBoolean(
                 R.bool.config_enable_show_popup_on_keypress_option);
         if (!showPopupOption) {
-            generalSettings.removePreference(findPreference(PREF_POPUP_ON));
+            generalSettings.removePreference(findPreference(PREF_KEY_PREVIEW_POPUP_ON));
         }
 
-        final boolean showRecorrectionOption = getResources().getBoolean(
+        final boolean showRecorrectionOption = res.getBoolean(
                 R.bool.config_enable_show_recorrection_option);
         if (!showRecorrectionOption) {
             generalSettings.removePreference(findPreference(PREF_RECORRECTION_ENABLED));
         }
 
-        final boolean showQuickFixesOption = getResources().getBoolean(
+        final boolean showQuickFixesOption = res.getBoolean(
                 R.bool.config_enable_quick_fixes_option);
         if (!showQuickFixesOption) {
             textCorrectionGroup.removePreference(findPreference(PREF_QUICK_FIXES));
         }
 
-        final boolean showBigramSuggestionsOption = getResources().getBoolean(
+        final boolean showBigramSuggestionsOption = res.getBoolean(
                 R.bool.config_enable_bigram_suggestions_option);
         if (!showBigramSuggestionsOption) {
             textCorrectionGroup.removePreference(findPreference(PREF_BIGRAM_SUGGESTIONS));
             textCorrectionGroup.removePreference(findPreference(PREF_BIGRAM_PREDICTIONS));
         }
 
-        final boolean showUsabilityModeStudyOption = getResources().getBoolean(
+        final boolean showUsabilityModeStudyOption = res.getBoolean(
                 R.bool.config_enable_usability_study_mode_option);
         if (!showUsabilityModeStudyOption) {
             getPreferenceScreen().removePreference(findPreference(PREF_USABILITY_STUDY_MODE));
         }
+
+        mKeyPreviewPopupDismissDelay =
+                (ListPreference)findPreference(PREF_KEY_PREVIEW_POPUP_DISMISS_DELAY);
+        final String[] entries = new String[] {
+                res.getString(R.string.key_preview_popup_dismiss_no_delay),
+                res.getString(R.string.key_preview_popup_dismiss_default_delay),
+        };
+        final String popupDismissDelayDefaultValue = Integer.toString(res.getInteger(
+                R.integer.config_delay_after_preview));
+        mKeyPreviewPopupDismissDelay.setEntries(entries);
+        mKeyPreviewPopupDismissDelay.setEntryValues(
+                new String[] { "0", popupDismissDelayDefaultValue });
+        if (null == mKeyPreviewPopupDismissDelay.getValue()) {
+            mKeyPreviewPopupDismissDelay.setValue(popupDismissDelayDefaultValue);
+        }
+        mKeyPreviewPopupDismissDelay.setEnabled(
+                Settings.Values.isKeyPreviewPopupEnabled(prefs, res));
     }
 
     @Override
@@ -405,6 +445,7 @@
         }
         updateSettingsKeySummary();
         updateShowCorrectionSuggestionsSummary();
+        updateKeyPreviewPopupDelaySummary();
     }
 
     @Override
@@ -423,6 +464,12 @@
                     .equals(mVoiceModeOff)) {
                 showVoiceConfirmation();
             }
+        } else if (key.equals(PREF_KEY_PREVIEW_POPUP_ON)) {
+            final ListPreference popupDismissDelay =
+                (ListPreference)findPreference(PREF_KEY_PREVIEW_POPUP_DISMISS_DELAY);
+            if (null != popupDismissDelay) {
+                popupDismissDelay.setEnabled(prefs.getBoolean(PREF_KEY_PREVIEW_POPUP_ON, true));
+            }
         }
         ensureConsistencyOfAutoCorrectionSettings();
         mVoiceOn = !(prefs.getString(PREF_VOICE_SETTINGS_KEY, mVoiceModeOff)
@@ -430,6 +477,7 @@
         updateVoiceModeSummary();
         updateSettingsKeySummary();
         updateShowCorrectionSuggestionsSummary();
+        updateKeyPreviewPopupDelaySummary();
     }
 
     @Override
@@ -451,11 +499,17 @@
     }
 
     private void updateSettingsKeySummary() {
+        final ListPreference lp = mSettingsKeyPreference;
         mSettingsKeyPreference.setSummary(
                 getResources().getStringArray(R.array.settings_key_modes)
                 [mSettingsKeyPreference.findIndexOfValue(mSettingsKeyPreference.getValue())]);
     }
 
+    private void updateKeyPreviewPopupDelaySummary() {
+        final ListPreference lp = mKeyPreviewPopupDismissDelay;
+        lp.setSummary(lp.getEntries()[lp.findIndexOfValue(lp.getValue())]);
+    }
+
     private void showVoiceConfirmation() {
         mOkClicked = false;
         showDialog(VOICE_INPUT_CONFIRM_DIALOG);