Merge "Promote a word with a missing space because the formula was changed by Ifa4338c5f4"
diff --git a/java/res/xml/kbd_qwerty_row4.xml b/java/res/xml/kbd_qwerty_row4.xml
index 0db0116..18ff608 100644
--- a/java/res/xml/kbd_qwerty_row4.xml
+++ b/java/res/xml/kbd_qwerty_row4.xml
@@ -35,29 +35,26 @@
                     latin:keyEdgeFlags="left" />
                 <include
                     latin:keyboardLayout="@xml/kbd_qwerty_f1" />
+                <Key
+                    latin:keyStyle="spaceKeyStyle"
+                    latin:keyWidth="40%p" />
                 <switch>
                     <case
                         latin:mode="web"
                     >
-                        <Key
-                            latin:keyStyle="spaceKeyStyle"
-                            latin:keyWidth="20%p" />
-                        <Key
+                         <Key
                             latin:keyStyle="tabKeyStyle"
-                            latin:keyWidth="20%p" />
+                            latin:keyWidth="10%p" />
                     </case>
                     <default>
                         <Key
-                            latin:keyStyle="spaceKeyStyle"
-                            latin:keyWidth="40%p" />
+                            latin:keyLabel="."
+                            latin:keyHintIcon="@drawable/hint_popup"
+                            latin:popupCharacters="@string/alternates_for_punctuation"
+                            latin:maxPopupKeyboardColumn="7"
+                            latin:keyStyle="functionalKeyStyle" />
                     </default>
                 </switch>
-                <Key
-                    latin:keyLabel="."
-                    latin:keyHintIcon="@drawable/hint_popup"
-                    latin:popupCharacters="@string/alternates_for_punctuation"
-                    latin:maxPopupKeyboardColumn="7"
-                    latin:keyStyle="functionalKeyStyle" />
                 <switch>
                     <case
                         latin:mode="im"
diff --git a/java/src/com/android/inputmethod/keyboard/KeyboardSwitcher.java b/java/src/com/android/inputmethod/keyboard/KeyboardSwitcher.java
index dd25b34..a58ad23 100644
--- a/java/src/com/android/inputmethod/keyboard/KeyboardSwitcher.java
+++ b/java/src/com/android/inputmethod/keyboard/KeyboardSwitcher.java
@@ -153,7 +153,7 @@
 
         makeSymbolsKeyboardIds(id.mMode, attribute);
         mCurrentId = id;
-        mInputView.setPreviewEnabled(mInputMethodService.getPopupOn());
+        mInputView.setKeyPreviewEnabled(mInputMethodService.getPopupOn());
         setKeyboard(getKeyboard(id));
     }
 
diff --git a/java/src/com/android/inputmethod/keyboard/KeyboardView.java b/java/src/com/android/inputmethod/keyboard/KeyboardView.java
index 3550dcb..eb09a45 100644
--- a/java/src/com/android/inputmethod/keyboard/KeyboardView.java
+++ b/java/src/com/android/inputmethod/keyboard/KeyboardView.java
@@ -106,26 +106,24 @@
     // Main keyboard
     private Keyboard mKeyboard;
 
-    // Key preview popup
+    // Key preview
     private boolean mInForeground;
     private TextView mPreviewText;
     private int mPreviewTextSizeLarge;
     private final int[] mOffsetInWindow = new int[2];
-    private int mOldPreviewKeyIndex = KeyDetector.NOT_A_KEY;
-    private boolean mShowPreview = true;
-    private int mPopupPreviewOffsetX;
-    private int mPopupPreviewOffsetY;
-    private int mPopupPreviewDisplayedY;
+    private boolean mShowKeyPreview = true;
+    private int mKeyPreviewDisplayedY;
     private final int mDelayBeforePreview;
     private final int mDelayAfterPreview;
+    private ViewGroup mPreviewPlacer;
 
-    // Popup mini keyboard
-    private PopupWindow mMiniKeyboardPopup;
+    // Mini keyboard
+    private PopupWindow mMiniKeyboardWindow;
     private KeyboardView mMiniKeyboardView;
     private final WeakHashMap<Key, View> mMiniKeyboardCache = new WeakHashMap<Key, View>();
     private int mMiniKeyboardOriginX;
     private int mMiniKeyboardOriginY;
-    private long mMiniKeyboardPopupTime;
+    private long mMiniKeyboardDisplayedTime;
     private int[] mWindowOffset;
     private final float mMiniKeyboardSlideAllowance;
     private int mMiniKeyboardTrackerId;
@@ -185,8 +183,8 @@
     private final UIHandler mHandler = new UIHandler();
 
     class UIHandler extends Handler {
-        private static final int MSG_POPUP_PREVIEW = 1;
-        private static final int MSG_DISMISS_PREVIEW = 2;
+        private static final int MSG_SHOW_KEY_PREVIEW = 1;
+        private static final int MSG_DISMISS_KEY_PREVIEW = 2;
         private static final int MSG_REPEAT_KEY = 3;
         private static final int MSG_LONGPRESS_KEY = 4;
         private static final int MSG_LONGPRESS_SHIFT_KEY = 5;
@@ -196,10 +194,10 @@
         @Override
         public void handleMessage(Message msg) {
             switch (msg.what) {
-                case MSG_POPUP_PREVIEW:
+                case MSG_SHOW_KEY_PREVIEW:
                     showKey(msg.arg1, (PointerTracker)msg.obj);
                     break;
-                case MSG_DISMISS_PREVIEW:
+                case MSG_DISMISS_KEY_PREVIEW:
                     mPreviewText.setVisibility(View.INVISIBLE);
                     break;
                 case MSG_REPEAT_KEY: {
@@ -210,7 +208,7 @@
                 }
                 case MSG_LONGPRESS_KEY: {
                     final PointerTracker tracker = (PointerTracker)msg.obj;
-                    openPopupIfRequired(msg.arg1, tracker);
+                    openMiniKeyboardIfRequired(msg.arg1, tracker);
                     break;
                 }
                 case MSG_LONGPRESS_SHIFT_KEY: {
@@ -221,26 +219,35 @@
             }
         }
 
-        public void popupPreview(long delay, int keyIndex, PointerTracker tracker) {
-            removeMessages(MSG_POPUP_PREVIEW);
+        public void showKeyPreview(long delay, int keyIndex, PointerTracker tracker) {
+            removeMessages(MSG_SHOW_KEY_PREVIEW);
             if (mPreviewText.getVisibility() == VISIBLE || delay == 0) {
                 // Show right away, if it's already visible and finger is moving around
                 showKey(keyIndex, tracker);
             } else {
-                sendMessageDelayed(obtainMessage(MSG_POPUP_PREVIEW, keyIndex, 0, tracker), delay);
+                sendMessageDelayed(
+                        obtainMessage(MSG_SHOW_KEY_PREVIEW, keyIndex, 0, tracker), delay);
             }
         }
 
-        public void cancelPopupPreview() {
-            removeMessages(MSG_POPUP_PREVIEW);
+        public void cancelShowKeyPreview(PointerTracker tracker) {
+            removeMessages(MSG_SHOW_KEY_PREVIEW, tracker);
         }
 
-        public void dismissPreview(long delay) {
-            sendMessageDelayed(obtainMessage(MSG_DISMISS_PREVIEW), delay);
+        public void cancelAllShowKeyPreviews() {
+            removeMessages(MSG_SHOW_KEY_PREVIEW);
         }
 
-        public void cancelDismissPreview() {
-            removeMessages(MSG_DISMISS_PREVIEW);
+        public void dismissKeyPreview(long delay, PointerTracker tracker) {
+            sendMessageDelayed(obtainMessage(MSG_DISMISS_KEY_PREVIEW, tracker), delay);
+        }
+
+        public void cancelDismissKeyPreview(PointerTracker tracker) {
+            removeMessages(MSG_DISMISS_KEY_PREVIEW, tracker);
+        }
+
+        public void cancelAllDismissKeyPreviews() {
+            removeMessages(MSG_DISMISS_KEY_PREVIEW);
         }
 
         public void startKeyRepeatTimer(long delay, int keyIndex, PointerTracker tracker) {
@@ -282,8 +289,8 @@
 
         public void cancelAllMessages() {
             cancelKeyTimers();
-            cancelPopupPreview();
-            cancelDismissPreview();
+            cancelAllShowKeyPreviews();
+            cancelAllDismissKeyPreviews();
         }
     }
 
@@ -363,18 +370,18 @@
             mPreviewText = (TextView) LayoutInflater.from(context).inflate(previewLayout, null);
             mPreviewTextSizeLarge = (int) res.getDimension(R.dimen.key_preview_text_size_large);
         } else {
-            mShowPreview = false;
+            mShowKeyPreview = false;
         }
         mDelayBeforePreview = res.getInteger(R.integer.config_delay_before_preview);
         mDelayAfterPreview = res.getInteger(R.integer.config_delay_after_preview);
         mKeyLabelHorizontalPadding = (int)res.getDimension(
                 R.dimen.key_label_horizontal_alignment_padding);
 
-        mMiniKeyboardPopup = new PopupWindow(context);
-        mMiniKeyboardPopup.setBackgroundDrawable(null);
-        mMiniKeyboardPopup.setAnimationStyle(R.style.MiniKeyboardAnimation);
+        mMiniKeyboardWindow = new PopupWindow(context);
+        mMiniKeyboardWindow.setBackgroundDrawable(null);
+        mMiniKeyboardWindow.setAnimationStyle(R.style.MiniKeyboardAnimation);
         // Allow popup window to be drawn off the screen.
-        mMiniKeyboardPopup.setClippingEnabled(false);
+        mMiniKeyboardWindow.setClippingEnabled(false);
 
         mPaint = new Paint();
         mPaint.setAntiAlias(true);
@@ -484,11 +491,11 @@
      */
     public void setKeyboard(Keyboard keyboard) {
         if (mKeyboard != null) {
-            dismissKeyPreview();
+            dismissAllKeyPreviews();
         }
         // Remove any pending messages, except dismissing preview
         mHandler.cancelKeyTimers();
-        mHandler.cancelPopupPreview();
+        mHandler.cancelAllShowKeyPreviews();
         mKeyboard = keyboard;
         LatinImeLogger.onSetKeyboard(keyboard);
         mKeyDetector.setKeyboard(keyboard, -getPaddingLeft(),
@@ -544,33 +551,28 @@
     }
 
     /**
-     * Enables or disables the key feedback popup. This is a popup that shows a magnified
+     * Enables or disables the key feedback preview. This is a preview 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 popup
-     * @see #isPreviewEnabled()
+     * @param previewEnabled whether or not to enable the key feedback preview
+     * @see #isKeyPreviewEnabled()
      */
-    public void setPreviewEnabled(boolean previewEnabled) {
-        mShowPreview = previewEnabled;
+    public void setKeyPreviewEnabled(boolean previewEnabled) {
+        mShowKeyPreview = previewEnabled;
     }
 
     /**
-     * Returns the enabled state of the key feedback popup.
-     * @return whether or not the key feedback popup is enabled
-     * @see #setPreviewEnabled(boolean)
+     * Returns the enabled state of the key feedback preview
+     * @return whether or not the key feedback preview is enabled
+     * @see #setKeyPreviewEnabled(boolean)
      */
-    public boolean isPreviewEnabled() {
-        return mShowPreview;
+    public boolean isKeyPreviewEnabled() {
+        return mShowKeyPreview;
     }
 
     public int getColorScheme() {
         return mColorScheme;
     }
 
-    public void setPopupOffset(int x, int y) {
-        mPopupPreviewOffsetX = x;
-        mPopupPreviewOffsetY = y;
-    }
-
     /**
      * When enabled, calls to {@link KeyboardActionListener#onCodeInput} will include key
      * codes for adjacent keys.  When disabled, only the primary key code will be
@@ -878,47 +880,62 @@
     }
 
     // TODO: clean up this method.
-    private void dismissKeyPreview() {
-        for (PointerTracker tracker : mPointerTrackers)
-            tracker.releaseKey();
-        showPreview(KeyDetector.NOT_A_KEY, null);
+    private void dismissAllKeyPreviews() {
+        for (PointerTracker tracker : mPointerTrackers) {
+            tracker.setReleasedKeyGraphics();
+            dismissKeyPreview(tracker);
+        }
     }
 
     @Override
-    public void showPreview(int keyIndex, PointerTracker tracker) {
-        int oldKeyIndex = mOldPreviewKeyIndex;
-        mOldPreviewKeyIndex = keyIndex;
-        if ((mShowPreview && oldKeyIndex != keyIndex) || mKeyboard.needSpacebarPreview(keyIndex)) {
-            if (keyIndex == KeyDetector.NOT_A_KEY) {
-                mHandler.cancelPopupPreview();
-                mHandler.dismissPreview(mDelayAfterPreview);
-            } else if (tracker != null) {
-                mHandler.popupPreview(mDelayBeforePreview, keyIndex, tracker);
+    public void showKeyPreview(int keyIndex, PointerTracker tracker) {
+        if (mShowKeyPreview || mKeyboard.needSpacebarPreview(keyIndex)) {
+            mHandler.showKeyPreview(mDelayBeforePreview, keyIndex, tracker);
+        }
+    }
+
+    @Override
+    public void dismissKeyPreview(PointerTracker tracker) {
+        if (mShowKeyPreview) {
+            mHandler.cancelShowKeyPreview(tracker);
+            mHandler.dismissKeyPreview(mDelayAfterPreview, tracker);
+        }
+    }
+
+    private void addKeyPreview(TextView keyPreview) {
+        ViewGroup placer = mPreviewPlacer;
+        if (placer == null) {
+            final FrameLayout screenContent = (FrameLayout) getRootView().findViewById(
+                    android.R.id.content);
+            if (android.os.Build.VERSION.SDK_INT >= /* HONEYCOMB */11) {
+                placer = screenContent;
+            } else {
+                // Insert LinearLayout to be able to setMargin because pre-Honeycomb FrameLayout
+                // could not handle setMargin properly.
+                placer = new LinearLayout(getContext());
+                screenContent.addView(placer);
             }
+            mPreviewPlacer = placer;
+        }
+        if (placer instanceof FrameLayout) {
+            placer.addView(keyPreview, new FrameLayout.LayoutParams(0, 0));
+        } else {
+            placer.addView(keyPreview, new LinearLayout.LayoutParams(0, 0));
         }
     }
 
     // TODO: Introduce minimum duration for displaying key previews
     // TODO: Display up to two key previews when the user presses two keys at the same time
     private void showKey(final int keyIndex, PointerTracker tracker) {
-        // If the preview popup has no parent view yet, add it to the screen FrameLayout.
+        // If the key preview has no parent view yet, add it to the ViewGroup which can place
+        // key preview absolutely in SoftInputWindow.
         if (mPreviewText.getParent() == null) {
-            final FrameLayout screenContent = (FrameLayout) getRootView()
-                    .findViewById(android.R.id.content);
-            if (android.os.Build.VERSION.SDK_INT >= /* HONEYCOMB */ 11) {
-                screenContent.addView(mPreviewText, new FrameLayout.LayoutParams(0, 0));
-            } else {
-                // Insert LinearLayout to be able to setMargin because pre-Honeycomb FrameLayout
-                // could not handle setMargin properly.
-                final LinearLayout placer = new LinearLayout(getContext());
-                screenContent.addView(placer);
-                placer.addView(mPreviewText, new LinearLayout.LayoutParams(0, 0));
-            }
+            addKeyPreview(mPreviewText);
         }
 
         final Key key = tracker.getKey(keyIndex);
         // If keyIndex is invalid or IME is already closed, we must not show key preview.
-        // Trying to show preview PopupWindow while root window is closed causes
+        // Trying to show key preview while root window is closed causes
         // WindowManager.BadTokenException.
         if (key == null || !mInForeground)
             return;
@@ -942,37 +959,35 @@
                    previewIcon != null ? previewIcon : key.getIcon());
             mPreviewText.setText(null);
         }
-        mPreviewText.measure(MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED),
-                MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED));
-        int popupWidth = Math.max(mPreviewText.getMeasuredWidth(), keyDrawWidth
-                + mPreviewText.getPaddingLeft() + mPreviewText.getPaddingRight());
-        final int popupHeight = mPreviewHeight;
-        final ViewGroup.LayoutParams lp = mPreviewText.getLayoutParams();
-        lp.width = popupWidth;
-        lp.height = popupHeight;
-
-        int popupPreviewX = keyDrawX - (popupWidth - keyDrawWidth) / 2;
-        int popupPreviewY = key.mY - popupHeight + mPreviewOffset;
-
-        mHandler.cancelDismissPreview();
-        getLocationInWindow(mOffsetInWindow);
-        mOffsetInWindow[0] += mPopupPreviewOffsetX; // Offset may be zero
-        mOffsetInWindow[1] += mPopupPreviewOffsetY; // Offset may be zero
-
         // Set the preview background state
         mPreviewText.getBackground().setState(
                 key.mPopupCharacters != null ? LONG_PRESSABLE_STATE_SET : EMPTY_STATE_SET);
-        popupPreviewX += mOffsetInWindow[0];
-        popupPreviewY += mOffsetInWindow[1];
+
+        mPreviewText.measure(MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED),
+                MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED));
+        int previewWidth = Math.max(mPreviewText.getMeasuredWidth(), keyDrawWidth
+                + mPreviewText.getPaddingLeft() + mPreviewText.getPaddingRight());
+        final int previewHeight = mPreviewHeight;
+        final ViewGroup.LayoutParams lp = mPreviewText.getLayoutParams();
+        lp.width = previewWidth;
+        lp.height = previewHeight;
+
+        int previewX = keyDrawX - (previewWidth - keyDrawWidth) / 2;
+        int previewY = key.mY - previewHeight + mPreviewOffset;
+
+        mHandler.cancelAllDismissKeyPreviews();
+        getLocationInWindow(mOffsetInWindow);
+        previewX += mOffsetInWindow[0];
+        previewY += mOffsetInWindow[1];
 
         // Place the key preview.
         // TODO: Adjust position of key previews which touch screen edges
         if (lp instanceof ViewGroup.MarginLayoutParams) {
             ViewGroup.MarginLayoutParams mlp = (ViewGroup.MarginLayoutParams)lp;
-            mlp.setMargins(popupPreviewX, popupPreviewY, 0, 0);
+            mlp.setMargins(previewX, previewY, 0, 0);
         }
-        // Record popup preview position to display mini-keyboard later at the same position
-        mPopupPreviewDisplayedY = popupPreviewY;
+        // Record key preview position to display mini-keyboard later at the same position
+        mKeyPreviewDisplayedY = previewY;
         mPreviewText.setVisibility(VISIBLE);
     }
 
@@ -1007,29 +1022,26 @@
         invalidate(mInvalidatedKeyRect);
     }
 
-    private boolean openPopupIfRequired(int keyIndex, PointerTracker tracker) {
+    private boolean openMiniKeyboardIfRequired(int keyIndex, PointerTracker tracker) {
         // Check if we have a popup layout specified first.
         if (mPopupLayout == 0) {
             return false;
         }
 
-        Key popupKey = tracker.getKey(keyIndex);
-        if (popupKey == null)
+        Key parentKey = tracker.getKey(keyIndex);
+        if (parentKey == null)
             return false;
-        boolean result = onLongPress(popupKey, tracker);
+        boolean result = onLongPress(parentKey, tracker);
         if (result) {
-            dismissKeyPreview();
+            dismissAllKeyPreviews();
             mMiniKeyboardTrackerId = tracker.mPointerId;
-            // Mark this tracker "already processed" and remove it from the pointer queue
-            tracker.setAlreadyProcessed();
-            mPointerQueue.remove(tracker);
+            tracker.onLongPressed(mPointerQueue);
         }
         return result;
     }
 
     private void onLongPressShiftKey(PointerTracker tracker) {
-        tracker.setAlreadyProcessed();
-        mPointerQueue.remove(tracker);
+        tracker.onLongPressed(mPointerQueue);
         mKeyboardActionListener.onCodeInput(Keyboard.CODE_CAPSLOCK, null, 0, 0);
     }
 
@@ -1040,7 +1052,7 @@
         mKeyboardActionListener.onCodeInput(Keyboard.CODE_CAPSLOCK, null, 0, 0);
     }
 
-    private View inflateMiniKeyboardContainer(Key popupKey) {
+    private View inflateMiniKeyboardContainer(Key parentKey) {
         final View container = LayoutInflater.from(getContext()).inflate(mPopupLayout, null);
         if (container == null)
             throw new NullPointerException();
@@ -1051,19 +1063,19 @@
             @Override
             public void onCodeInput(int primaryCode, int[] keyCodes, int x, int y) {
                 mKeyboardActionListener.onCodeInput(primaryCode, keyCodes, x, y);
-                dismissPopupKeyboard();
+                dismissMiniKeyboard();
             }
 
             @Override
             public void onTextInput(CharSequence text) {
                 mKeyboardActionListener.onTextInput(text);
-                dismissPopupKeyboard();
+                dismissMiniKeyboard();
             }
 
             @Override
             public void onCancelInput() {
                 mKeyboardActionListener.onCancelInput();
-                dismissPopupKeyboard();
+                dismissMiniKeyboard();
             }
 
             @Override
@@ -1085,7 +1097,7 @@
         miniKeyboardView.mGestureDetector = null;
 
         final Keyboard keyboard = new MiniKeyboardBuilder(this, mKeyboard.getPopupKeyboardResId(),
-                popupKey).build();
+                parentKey).build();
         miniKeyboardView.setKeyboard(keyboard);
 
         container.measure(MeasureSpec.makeMeasureSpec(getWidth(), MeasureSpec.AT_MOST),
@@ -1108,20 +1120,20 @@
     }
 
     /**
-     * Called when a key is long pressed. By default this will open any popup keyboard associated
+     * Called when a key is long pressed. By default this will open any mini keyboard associated
      * with this key through the attributes popupLayout and popupCharacters.
-     * @param popupKey the key that was long pressed
+     * @param parentKey the key that was long pressed
      * @return true if the long press is handled, false otherwise. Subclasses should call the
      * method on the base class if the subclass doesn't wish to handle the call.
      */
-    protected boolean onLongPress(Key popupKey, PointerTracker tracker) {
-        if (popupKey.mPopupCharacters == null)
+    protected boolean onLongPress(Key parentKey, PointerTracker tracker) {
+        if (parentKey.mPopupCharacters == null)
             return false;
 
-        View container = mMiniKeyboardCache.get(popupKey);
+        View container = mMiniKeyboardCache.get(parentKey);
         if (container == null) {
-            container = inflateMiniKeyboardContainer(popupKey);
-            mMiniKeyboardCache.put(popupKey, container);
+            container = inflateMiniKeyboardContainer(parentKey);
+            mMiniKeyboardCache.put(parentKey, container);
         }
         mMiniKeyboardView = (KeyboardView)container.findViewById(R.id.KeyboardView);
         final MiniKeyboard miniKeyboard = (MiniKeyboard)mMiniKeyboardView.getKeyboard();
@@ -1131,36 +1143,35 @@
             getLocationInWindow(mWindowOffset);
         }
         final int pointX = (mConfigShowMiniKeyboardAtTouchedPoint) ? tracker.getLastX()
-                : popupKey.mX + popupKey.mWidth / 2;
-        final int popupX = pointX - miniKeyboard.getDefaultCoordX()
+                : parentKey.mX + parentKey.mWidth / 2;
+        final int miniKeyboardX = pointX - miniKeyboard.getDefaultCoordX()
                 - container.getPaddingLeft()
                 + getPaddingLeft() + mWindowOffset[0];
-        final int popupY = popupKey.mY - mKeyboard.getVerticalGap()
+        final int miniKeyboardY = parentKey.mY - mKeyboard.getVerticalGap()
                 - (container.getMeasuredHeight() - container.getPaddingBottom())
                 + getPaddingTop() + mWindowOffset[1];
-        final int x = popupX;
-        final int y = mShowPreview && isOneRowKeys(miniKeyboard.getKeys())
-                ? mPopupPreviewDisplayedY : popupY;
+        final int x = miniKeyboardX;
+        final int y = mShowKeyPreview && isOneRowKeys(miniKeyboard.getKeys())
+                ? mKeyPreviewDisplayedY : miniKeyboardY;
 
         mMiniKeyboardOriginX = x + container.getPaddingLeft() - mWindowOffset[0];
         mMiniKeyboardOriginY = y + container.getPaddingTop() - mWindowOffset[1];
-        mMiniKeyboardView.setPopupOffset(x, y);
         if (miniKeyboard.setShifted(
                 mKeyboard == null ? false : mKeyboard.isShiftedOrShiftLocked())) {
             mMiniKeyboardView.invalidateAllKeys();
         }
         // Mini keyboard needs no pop-up key preview displayed.
-        mMiniKeyboardView.setPreviewEnabled(false);
-        mMiniKeyboardPopup.setContentView(container);
-        mMiniKeyboardPopup.setWidth(container.getMeasuredWidth());
-        mMiniKeyboardPopup.setHeight(container.getMeasuredHeight());
-        mMiniKeyboardPopup.showAtLocation(this, Gravity.NO_GRAVITY, x, y);
+        mMiniKeyboardView.setKeyPreviewEnabled(false);
+        mMiniKeyboardWindow.setContentView(container);
+        mMiniKeyboardWindow.setWidth(container.getMeasuredWidth());
+        mMiniKeyboardWindow.setHeight(container.getMeasuredHeight());
+        mMiniKeyboardWindow.showAtLocation(this, Gravity.NO_GRAVITY, x, y);
 
         // Inject down event on the key to mini keyboard.
         final long eventTime = SystemClock.uptimeMillis();
-        mMiniKeyboardPopupTime = eventTime;
+        mMiniKeyboardDisplayedTime = eventTime;
         final MotionEvent downEvent = generateMiniKeyboardMotionEvent(MotionEvent.ACTION_DOWN,
-                pointX, popupKey.mY + popupKey.mHeight / 2, eventTime);
+                pointX, parentKey.mY + parentKey.mHeight / 2, eventTime);
         mMiniKeyboardView.onTouchEvent(downEvent);
         downEvent.recycle();
 
@@ -1169,7 +1180,7 @@
     }
 
     private MotionEvent generateMiniKeyboardMotionEvent(int action, int x, int y, long eventTime) {
-        return MotionEvent.obtain(mMiniKeyboardPopupTime, eventTime, action,
+        return MotionEvent.obtain(mMiniKeyboardDisplayedTime, eventTime, action,
                     x - mMiniKeyboardOriginX, y - mMiniKeyboardOriginY, 0);
     }
 
@@ -1180,7 +1191,7 @@
         // Create pointer trackers until we can get 'id+1'-th tracker, if needed.
         for (int i = pointers.size(); i <= id; i++) {
             final PointerTracker tracker =
-                new PointerTracker(i, mHandler, mKeyDetector, this, getResources());
+                new PointerTracker(i, this, mHandler, mKeyDetector, this);
             if (mKeyboard != null)
                 tracker.setKeyboard(mKeyboard, mKeyHysteresisDistance);
             if (listener != null)
@@ -1226,7 +1237,7 @@
         // TODO: Reconcile gesture detection and accessibility features.
         if (mMiniKeyboardView == null && !mIsAccessibilityEnabled
                 && mGestureDetector != null && mGestureDetector.onTouchEvent(me)) {
-            dismissKeyPreview();
+            dismissAllKeyPreviews();
             mHandler.cancelKeyTimers();
             return true;
         }
@@ -1323,7 +1334,7 @@
         mPreviewText.setVisibility(View.GONE);
         mHandler.cancelAllMessages();
 
-        dismissPopupKeyboard();
+        dismissMiniKeyboard();
         mDirtyRect.union(0, 0, getWidth(), getHeight());
         mMiniKeyboardCache.clear();
         requestLayout();
@@ -1340,9 +1351,9 @@
         closing();
     }
 
-    private void dismissPopupKeyboard() {
-        if (mMiniKeyboardPopup.isShowing()) {
-            mMiniKeyboardPopup.dismiss();
+    private void dismissMiniKeyboard() {
+        if (mMiniKeyboardWindow.isShowing()) {
+            mMiniKeyboardWindow.dismiss();
             mMiniKeyboardView = null;
             mMiniKeyboardOriginX = 0;
             mMiniKeyboardOriginY = 0;
@@ -1351,8 +1362,8 @@
     }
 
     public boolean handleBack() {
-        if (mMiniKeyboardPopup.isShowing()) {
-            dismissPopupKeyboard();
+        if (mMiniKeyboardWindow.isShowing()) {
+            dismissMiniKeyboard();
             return true;
         }
         return false;
diff --git a/java/src/com/android/inputmethod/keyboard/LatinKeyboardView.java b/java/src/com/android/inputmethod/keyboard/LatinKeyboardView.java
index d6c3723..9120745 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 setPreviewEnabled(boolean previewEnabled) {
+    public void setKeyPreviewEnabled(boolean previewEnabled) {
         LatinKeyboard latinKeyboard = getLatinKeyboard();
         if (latinKeyboard != null
                 && (latinKeyboard.isPhoneKeyboard() || latinKeyboard.isNumberKeyboard())) {
             // Phone and number keyboard never shows popup preview (except language switch).
-            super.setPreviewEnabled(false);
+            super.setKeyPreviewEnabled(false);
         } else {
-            super.setPreviewEnabled(previewEnabled);
+            super.setKeyPreviewEnabled(previewEnabled);
         }
     }
 
diff --git a/java/src/com/android/inputmethod/keyboard/PointerTracker.java b/java/src/com/android/inputmethod/keyboard/PointerTracker.java
index 5d137b9..64f2f96 100644
--- a/java/src/com/android/inputmethod/keyboard/PointerTracker.java
+++ b/java/src/com/android/inputmethod/keyboard/PointerTracker.java
@@ -37,7 +37,8 @@
 
     public interface UIProxy {
         public void invalidateKey(Key key);
-        public void showPreview(int keyIndex, PointerTracker tracker);
+        public void showKeyPreview(int keyIndex, PointerTracker tracker);
+        public void dismissKeyPreview(PointerTracker tracker);
         public boolean hasDistinctMultitouch();
         public boolean isAccessibilityEnabled();
     }
@@ -49,9 +50,7 @@
     private final int mLongPressKeyTimeout;
     private final int mLongPressShiftKeyTimeout;
 
-    // Miscellaneous constants
-    private static final int NOT_A_KEY = KeyDetector.NOT_A_KEY;
-
+    private final KeyboardView mKeyboardView;
     private final UIProxy mProxy;
     private final UIHandler mHandler;
     private final KeyDetector mKeyDetector;
@@ -91,9 +90,6 @@
     // ignore modifier key if true
     private boolean mIgnoreModifierKey;
 
-    // pressed key
-    private int mPreviousKey = NOT_A_KEY;
-
     // Empty {@link KeyboardActionListener}
     private static final KeyboardActionListener EMPTY_LISTENER = new KeyboardActionListener() {
         @Override
@@ -110,11 +106,12 @@
         public void onSwipeDown() {}
     };
 
-    public PointerTracker(int id, UIHandler handler, KeyDetector keyDetector,
-            UIProxy proxy, Resources res) {
+    public PointerTracker(int id, KeyboardView keyboardView, UIHandler handler,
+            KeyDetector keyDetector, UIProxy proxy) {
         if (proxy == null || handler == null || keyDetector == null)
             throw new NullPointerException();
         mPointerId = id;
+        mKeyboardView = keyboardView;
         mProxy = proxy;
         mHandler = handler;
         mKeyDetector = keyDetector;
@@ -122,6 +119,7 @@
         mKeyState = new PointerTrackerKeyState(keyDetector);
         mIsAccessibilityEnabled = proxy.isAccessibilityEnabled();
         mHasDistinctMultitouch = proxy.hasDistinctMultitouch();
+        final Resources res = mKeyboardView.getResources();
         mConfigSlidingKeyInputEnabled = res.getBoolean(R.bool.config_sliding_key_input_enabled);
         mDelayBeforeKeyRepeatStart = res.getInteger(R.integer.config_delay_before_key_repeat_start);
         mLongPressKeyTimeout = res.getInteger(R.integer.config_long_press_key_timeout);
@@ -250,29 +248,24 @@
         return key != null && key.mCode == Keyboard.CODE_SPACE;
     }
 
-    public void releaseKey() {
-        updateKeyGraphics(NOT_A_KEY);
+    public void setReleasedKeyGraphics() {
+        setReleasedKeyGraphics(mKeyState.getKeyIndex());
     }
 
-    private void updateKeyGraphics(int keyIndex) {
-        int oldKeyIndex = mPreviousKey;
-        mPreviousKey = keyIndex;
-        if (keyIndex != oldKeyIndex) {
-            if (isValidKeyIndex(oldKeyIndex)) {
-                final Key oldKey = mKeys.get(oldKeyIndex);
-                oldKey.onReleased();
-                mProxy.invalidateKey(oldKey);
-            }
-            if (isValidKeyIndex(keyIndex)) {
-                final Key newKey = mKeys.get(keyIndex);
-                newKey.onPressed();
-                mProxy.invalidateKey(newKey);
-            }
+    private void setReleasedKeyGraphics(int keyIndex) {
+        final Key key = getKey(keyIndex);
+        if (key != null) {
+            key.onReleased();
+            mProxy.invalidateKey(key);
         }
     }
 
-    public void setAlreadyProcessed() {
-        mKeyAlreadyProcessed = true;
+    private void setPressedKeyGraphics(int keyIndex) {
+        final Key key = getKey(keyIndex);
+        if (key != null && key.mEnabled) {
+            key.onPressed();
+            mProxy.invalidateKey(key);
+        }
     }
 
     private void checkAssertion(PointerTrackerQueue queue) {
@@ -318,7 +311,7 @@
                 if (DEBUG_MODE)
                     Log.w(TAG, "onDownEvent: ignore potential noise: time=" + deltaT
                             + " distance=" + distanceSquared);
-                setAlreadyProcessed();
+                mKeyAlreadyProcessed = true;
                 return;
             }
         }
@@ -346,24 +339,25 @@
         mIsRepeatableKey = false;
         mIsInSlidingKeyInput = false;
         mIgnoreModifierKey = false;
-        final Key key = getKey(keyIndex);
-        if (key != null) {
+        if (isValidKeyIndex(keyIndex)) {
             // This onPress call may have changed keyboard layout. Those cases are detected at
             // {@link #setKeyboard}. In those cases, we should update keyIndex according to the new
             // keyboard layout.
-            if (callListenerOnPressAndCheckKeyboardLayoutChange(key, false))
+            if (callListenerOnPressAndCheckKeyboardLayoutChange(getKey(keyIndex), false))
                 keyIndex = mKeyState.onDownKey(x, y, eventTime);
 
             // Accessibility disables key repeat because users may need to pause on a key to hear
             // its spoken description.
-            if (key.mRepeatable && !mIsAccessibilityEnabled) {
+            final Key key = getKey(keyIndex);
+            if (key != null && key.mRepeatable && !mIsAccessibilityEnabled) {
                 repeatKey(keyIndex);
                 mHandler.startKeyRepeatTimer(mDelayBeforeKeyRepeatStart, keyIndex, this);
                 mIsRepeatableKey = true;
             }
             startLongPressTimer(keyIndex);
+            showKeyPreview(keyIndex);
+            setPressedKeyGraphics(keyIndex);
         }
-        showKeyPreviewAndUpdateKeyGraphics(keyIndex);
     }
 
     private void startSlidingKeyInput(Key key) {
@@ -382,8 +376,9 @@
 
         final int lastX = keyState.getLastX();
         final int lastY = keyState.getLastY();
+        final int oldKeyIndex = keyState.getKeyIndex();
+        final Key oldKey = getKey(oldKeyIndex);
         int keyIndex = keyState.onMoveKey(x, y);
-        final Key oldKey = getKey(keyState.getKeyIndex());
         if (isValidKeyIndex(keyIndex)) {
             if (oldKey == null) {
                 // The pointer has been slid in to the new key, but the finger was not on any keys.
@@ -395,10 +390,13 @@
                     keyIndex = keyState.onMoveKey(x, y);
                 keyState.onMoveToNewKey(keyIndex, x, y);
                 startLongPressTimer(keyIndex);
+                showKeyPreview(keyIndex);
+                setPressedKeyGraphics(keyIndex);
             } else if (!isMinorMoveBounce(x, y, keyIndex)) {
                 // The pointer has been slid in to the new key from the previous key, we must call
                 // onRelease() first to notify that the previous key has been released, then call
                 // onPress() to notify that the new key is being pressed.
+                setReleasedKeyGraphics(oldKeyIndex);
                 callListenerOnRelease(oldKey, oldKey.mCode, true);
                 startSlidingKeyInput(oldKey);
                 mHandler.cancelLongPressTimers();
@@ -410,6 +408,8 @@
                         keyIndex = keyState.onMoveKey(x, y);
                     keyState.onMoveToNewKey(keyIndex, x, y);
                     startLongPressTimer(keyIndex);
+                    setPressedKeyGraphics(keyIndex);
+                    showKeyPreview(keyIndex);
                 } else {
                     // HACK: On some devices, quick successive touches may be translated to sudden
                     // move by touch panel firmware. This hack detects the case and translates the
@@ -421,11 +421,12 @@
                         if (DEBUG_MODE)
                             Log.w(TAG, String.format("onMoveEvent: sudden move is translated to "
                                     + "up[%d,%d]/down[%d,%d] events", lastX, lastY, x, y));
-                        onUpEventInternal(lastX, lastY, eventTime);
+                        onUpEventInternal(lastX, lastY, eventTime, true);
                         onDownEventInternal(x, y, eventTime);
                     } else {
-                        setAlreadyProcessed();
-                        showKeyPreviewAndUpdateKeyGraphics(NOT_A_KEY);
+                        mKeyAlreadyProcessed = true;
+                        dismissKeyPreview();
+                        setReleasedKeyGraphics(oldKeyIndex);
                     }
                     return;
                 }
@@ -434,19 +435,19 @@
             if (oldKey != null && !isMinorMoveBounce(x, y, keyIndex)) {
                 // The pointer has been slid out from the previous key, we must call onRelease() to
                 // notify that the previous key has been released.
+                setReleasedKeyGraphics(oldKeyIndex);
                 callListenerOnRelease(oldKey, oldKey.mCode, true);
                 startSlidingKeyInput(oldKey);
                 mHandler.cancelLongPressTimers();
                 if (mIsAllowedSlidingKeyInput) {
-                    keyState.onMoveToNewKey(keyIndex, x ,y);
+                    keyState.onMoveToNewKey(keyIndex, x, y);
                 } else {
-                    setAlreadyProcessed();
-                    showKeyPreviewAndUpdateKeyGraphics(NOT_A_KEY);
+                    mKeyAlreadyProcessed = true;
+                    dismissKeyPreview();
                     return;
                 }
             }
         }
-        showKeyPreviewAndUpdateKeyGraphics(keyState.getKeyIndex());
     }
 
     public void onUpEvent(int x, int y, long eventTime, PointerTrackerQueue queue) {
@@ -464,35 +465,48 @@
             }
             queue.remove(this);
         }
-        onUpEventInternal(x, y, eventTime);
+        onUpEventInternal(x, y, eventTime, true);
     }
 
-    public void onUpEventForRelease(int x, int y, long eventTime) {
-        onUpEventInternal(x, y, eventTime);
+    // Let this pointer tracker know that one of newer-than-this pointer trackers got an up event.
+    // This pointer tracker needs to keep the key top graphics "pressed", but needs to get a
+    // "virtual" up event.
+    public void onPhantomUpEvent(int x, int y, long eventTime) {
+        onUpEventInternal(x, y, eventTime, false);
+        mKeyAlreadyProcessed = true;
     }
 
-    private void onUpEventInternal(int pointX, int pointY, long eventTime) {
-        int x = pointX;
-        int y = pointY;
+    private void onUpEventInternal(int x, int y, long eventTime,
+            boolean updateReleasedKeyGraphics) {
         mHandler.cancelKeyTimers();
-        mHandler.cancelPopupPreview();
-        showKeyPreviewAndUpdateKeyGraphics(NOT_A_KEY);
+        mHandler.cancelShowKeyPreview(this);
         mIsInSlidingKeyInput = false;
+        final PointerTrackerKeyState keyState = mKeyState;
+        final int keyX, keyY;
+        if (!isMinorMoveBounce(x, y, keyState.onMoveKey(x, y))) {
+            keyX = x;
+            keyY = y;
+        } else {
+            // Use previous fixed key coordinates.
+            keyX = keyState.getKeyX();
+            keyY = keyState.getKeyY();
+        }
+        final int keyIndex = keyState.onUpKey(keyX, keyY, eventTime);
+        dismissKeyPreview();
+        if (updateReleasedKeyGraphics)
+            setReleasedKeyGraphics(keyIndex);
         if (mKeyAlreadyProcessed)
             return;
-        final PointerTrackerKeyState keyState = mKeyState;
-        int keyIndex = keyState.onUpKey(x, y, eventTime);
-        if (isMinorMoveBounce(x, y, keyIndex)) {
-            // Use previous fixed key index and coordinates.
-            keyIndex = keyState.getKeyIndex();
-            x = keyState.getKeyX();
-            y = keyState.getKeyY();
-        }
         if (!mIsRepeatableKey) {
-            detectAndSendKey(keyIndex, x, y);
+            detectAndSendKey(keyIndex, keyX, keyY);
         }
     }
 
+    public void onLongPressed(PointerTrackerQueue queue) {
+        mKeyAlreadyProcessed = true;
+        queue.remove(this);
+    }
+
     public void onCancelEvent(int x, int y, long eventTime, PointerTrackerQueue queue) {
         if (ENABLE_ASSERTION) checkAssertion(queue);
         if (DEBUG_EVENT)
@@ -505,8 +519,9 @@
 
     private void onCancelEventInternal() {
         mHandler.cancelKeyTimers();
-        mHandler.cancelPopupPreview();
-        showKeyPreviewAndUpdateKeyGraphics(NOT_A_KEY);
+        mHandler.cancelShowKeyPreview(this);
+        dismissKeyPreview();
+        setReleasedKeyGraphics(mKeyState.getKeyIndex());
         mIsInSlidingKeyInput = false;
     }
 
@@ -542,7 +557,7 @@
         }
     }
 
-    private void showKeyPreviewAndUpdateKeyGraphics(int keyIndex) {
+    private void showKeyPreview(int keyIndex) {
         final Key key = getKey(keyIndex);
         if (key != null && !key.mEnabled)
             return;
@@ -550,12 +565,13 @@
         // supported. On the other hand, if multi-touch is not supported, the modifier key should
         // be shown as preview. If accessibility is turned on, the modifier key should be shown as
         // preview.
-        if (mHasDistinctMultitouch && isModifier() && !mIsAccessibilityEnabled) {
-            mProxy.showPreview(NOT_A_KEY, this);
-        } else {
-            mProxy.showPreview(keyIndex, this);
-        }
-        updateKeyGraphics(keyIndex);
+        if (mHasDistinctMultitouch && isModifier() && !mIsAccessibilityEnabled)
+            return;
+        mProxy.showKeyPreview(keyIndex, this);
+    }
+
+    private void dismissKeyPreview() {
+        mProxy.dismissKeyPreview(this);
     }
 
     private void startLongPressTimer(int keyIndex) {
diff --git a/java/src/com/android/inputmethod/keyboard/PointerTrackerKeyState.java b/java/src/com/android/inputmethod/keyboard/PointerTrackerKeyState.java
index a62ed96..b3ed1e2 100644
--- a/java/src/com/android/inputmethod/keyboard/PointerTrackerKeyState.java
+++ b/java/src/com/android/inputmethod/keyboard/PointerTrackerKeyState.java
@@ -92,6 +92,7 @@
 
     public int onUpKey(int x, int y, long eventTime) {
         mUpTime = eventTime;
+        mKeyIndex = KeyDetector.NOT_A_KEY;
         return onMoveKeyInternal(x, y);
     }
 }
diff --git a/java/src/com/android/inputmethod/keyboard/PointerTrackerQueue.java b/java/src/com/android/inputmethod/keyboard/PointerTrackerQueue.java
index 928f3cd..0a94100 100644
--- a/java/src/com/android/inputmethod/keyboard/PointerTrackerQueue.java
+++ b/java/src/com/android/inputmethod/keyboard/PointerTrackerQueue.java
@@ -29,14 +29,13 @@
         if (mQueue.lastIndexOf(tracker) < 0) {
             return;
         }
-        LinkedList<PointerTracker> queue = mQueue;
+        final LinkedList<PointerTracker> queue = mQueue;
         int oldestPos = 0;
         for (PointerTracker t = queue.get(oldestPos); t != tracker; t = queue.get(oldestPos)) {
             if (t.isModifier()) {
                 oldestPos++;
             } else {
-                t.onUpEventForRelease(t.getLastX(), t.getLastY(), eventTime);
-                t.setAlreadyProcessed();
+                t.onPhantomUpEvent(t.getLastX(), t.getLastY(), eventTime);
                 queue.remove(oldestPos);
             }
         }
@@ -50,8 +49,7 @@
         for (PointerTracker t : mQueue) {
             if (t == tracker)
                 continue;
-            t.onUpEventForRelease(t.getLastX(), t.getLastY(), eventTime);
-            t.setAlreadyProcessed();
+            t.onPhantomUpEvent(t.getLastX(), t.getLastY(), eventTime);
         }
         mQueue.clear();
         if (tracker != null)
diff --git a/java/src/com/android/inputmethod/latin/InputLanguageSelection.java b/java/src/com/android/inputmethod/latin/InputLanguageSelection.java
index be5e015..40ab28c 100644
--- a/java/src/com/android/inputmethod/latin/InputLanguageSelection.java
+++ b/java/src/com/android/inputmethod/latin/InputLanguageSelection.java
@@ -162,36 +162,42 @@
         for (int i = 0 ; i < origSize; i++ ) {
             String s = locales[i];
             int len = s.length();
+            String language = "";
+            String country = "";
             if (len == 5) {
-                String language = s.substring(0, 2);
-                String country = s.substring(3, 5);
-                Locale l = new Locale(language, country);
+                language = s.substring(0, 2);
+                country = s.substring(3, 5);
+            } else if (len < 5) {
+                language = s;
+            }
+            Locale l = new Locale(language, country);
 
-                // Exclude languages that are not relevant to LatinIME
-                if (arrayContains(BLACKLIST_LANGUAGES, language)) continue;
+            // Exclude languages that are not relevant to LatinIME
+            if (arrayContains(BLACKLIST_LANGUAGES, language) || TextUtils.isEmpty(language)) {
+                continue;
+            }
 
-                if (finalSize == 0) {
+            if (finalSize == 0) {
+                preprocess[finalSize++] =
+                        new Loc(SubtypeSwitcher.getFullDisplayName(l, true), l);
+            } else {
+                // check previous entry:
+                //  same lang and a country -> upgrade to full name and
+                //    insert ours with full name
+                //  diff lang -> insert ours with lang-only name
+                if (preprocess[finalSize-1].mLocale.getLanguage().equals(
+                        language)) {
+                    preprocess[finalSize-1].setLabel(SubtypeSwitcher.getFullDisplayName(
+                            preprocess[finalSize-1].mLocale, false));
                     preprocess[finalSize++] =
-                            new Loc(SubtypeSwitcher.getFullDisplayName(l, true), l);
+                            new Loc(SubtypeSwitcher.getFullDisplayName(l, false), l);
                 } else {
-                    // check previous entry:
-                    //  same lang and a country -> upgrade to full name and
-                    //    insert ours with full name
-                    //  diff lang -> insert ours with lang-only name
-                    if (preprocess[finalSize-1].mLocale.getLanguage().equals(
-                            language)) {
-                        preprocess[finalSize-1].setLabel(SubtypeSwitcher.getFullDisplayName(
-                                preprocess[finalSize-1].mLocale, false));
-                        preprocess[finalSize++] =
-                                new Loc(SubtypeSwitcher.getFullDisplayName(l, false), l);
+                    String displayName;
+                    if (s.equals("zz_ZZ")) {
+                        // ignore this locale
                     } else {
-                        String displayName;
-                        if (s.equals("zz_ZZ")) {
-                            // ignore this locale
-                        } else {
-                            displayName = SubtypeSwitcher.getFullDisplayName(l, true);
-                            preprocess[finalSize++] = new Loc(displayName, l);
-                        }
+                        displayName = SubtypeSwitcher.getFullDisplayName(l, true);
+                        preprocess[finalSize++] = new Loc(displayName, l);
                     }
                 }
             }
diff --git a/java/src/com/android/inputmethod/latin/LatinIME.java b/java/src/com/android/inputmethod/latin/LatinIME.java
index 88b3ded..13ef4ff 100644
--- a/java/src/com/android/inputmethod/latin/LatinIME.java
+++ b/java/src/com/android/inputmethod/latin/LatinIME.java
@@ -592,7 +592,7 @@
 
         final boolean accessibilityEnabled = mAccessibilityUtils.isAccessibilityEnabled();
 
-        inputView.setPreviewEnabled(mPopupOn);
+        inputView.setKeyPreviewEnabled(mPopupOn);
         inputView.setProximityCorrectionEnabled(true);
         inputView.setAccessibilityEnabled(accessibilityEnabled);
         // If we just entered a text field, maybe it has some old text that requires correction
diff --git a/native/src/unigram_dictionary.cpp b/native/src/unigram_dictionary.cpp
index 8e82f14..3832d47 100644
--- a/native/src/unigram_dictionary.cpp
+++ b/native/src/unigram_dictionary.cpp
@@ -417,6 +417,54 @@
     }
 }
 
+inline static int calcFreqForSplitTwoWords(
+        const int typedLetterMultiplier, const int firstWordLength,
+        const int secondWordLength, const int firstFreq, const int secondFreq) {
+    if (firstWordLength == 0 || secondWordLength == 0) {
+        return 0;
+    }
+    const int firstDemotionRate = 100 - 100 / (firstWordLength + 1);
+    int tempFirstFreq = firstFreq;
+    multiplyRate(firstDemotionRate, &tempFirstFreq);
+
+    const int secondDemotionRate = 100 - 100 / (secondWordLength + 1);
+    int tempSecondFreq = secondFreq;
+    multiplyRate(secondDemotionRate, &tempSecondFreq);
+
+    const int totalLength = firstWordLength + secondWordLength;
+
+    // Promote pairFreq with multiplying by 2, because the word length is the same as the typed
+    // length.
+    int totalFreq = tempFirstFreq + tempSecondFreq;
+
+    // This is a workaround to try offsetting the not-enough-demotion which will be done in
+    // calcNormalizedScore in Utils.java.
+    // In calcNormalizedScore the score will be demoted by (1 - 1 / length)
+    // but we demoted only (1 - 1 / (length + 1)) so we will additionally adjust freq by
+    // (1 - 1 / length) / (1 - 1 / (length + 1)) = (1 - 1 / (length * length))
+    const int normalizedScoreNotEnoughDemotionAdjustment = 100 - 100 / (totalLength * totalLength);
+    multiplyRate(normalizedScoreNotEnoughDemotionAdjustment, &totalFreq);
+
+    // At this moment, totalFreq is calculated by the following formula:
+    // (firstFreq * (1 - 1 / (firstWordLength + 1)) + secondFreq * (1 - 1 / (secondWordLength + 1)))
+    //        * (1 - 1 / totalLength) / (1 - 1 / (totalLength + 1))
+
+    for (int i = 0; i < totalLength; ++i) {
+        totalFreq *= typedLetterMultiplier;
+    }
+
+    // This is another workaround to offset the demotion which will be done in
+    // calcNormalizedScore in Utils.java.
+    // In calcNormalizedScore the score will be demoted by (1 - 1 / length) so we have to promote
+    // the same amount because we already have adjusted the synthetic freq of this "missing or
+    // mistyped space" suggestion candidate above in this method.
+    const int normalizedScoreDemotionRateOffset = (100 + 100 / totalLength);
+    multiplyRate(normalizedScoreDemotionRateOffset, &totalFreq);
+
+    multiplyRate(WORDS_WITH_MISSING_SPACE_CHARACTER_DEMOTION_RATE, &totalFreq);
+    return totalFreq;
+}
+
 bool UnigramDictionary::getSplitTwoWordsSuggestion(const int inputLength,
         const int firstWordStartPos, const int firstWordLength, const int secondWordStartPos,
         const int secondWordLength) {
@@ -448,15 +496,12 @@
         word[i] = mWord[i - firstWordLength - 1];
     }
 
-    // Promote pairFreq with multiplying by 2, because the word length is the same as the typed
-    // length.
-    int pairFreq = firstFreq + secondFreq;
-    for (int i = 0; i < inputLength; ++i) pairFreq *= TYPED_LETTER_MULTIPLIER;
+    int pairFreq = calcFreqForSplitTwoWords(
+            TYPED_LETTER_MULTIPLIER, firstWordLength, secondWordLength, firstFreq, secondFreq);
     if (DEBUG_DICT) {
         LOGI("Missing space:  %d, %d, %d, %d, %d", firstFreq, secondFreq, pairFreq, inputLength,
                 TYPED_LETTER_MULTIPLIER);
     }
-    multiplyRate(WORDS_WITH_MISSING_SPACE_CHARACTER_DEMOTION_RATE, &pairFreq);
     addWord(word, newWordLength, pairFreq);
     return true;
 }