Merge "refactor proximity info"
diff --git a/java/res/values/attrs.xml b/java/res/values/attrs.xml
index 5c6d0e8..e10bdb1 100644
--- a/java/res/values/attrs.xml
+++ b/java/res/values/attrs.xml
@@ -51,46 +51,13 @@
         <!-- Blur radius of key text shadow. -->
         <attr name="keyTextShadowRadius" format="float" />
 
-        <!-- Layout resource for key press feedback.-->
-        <attr name="keyPreviewLayout" format="reference" />
         <!-- Key preview background states -->
         <attr name="state_left_edge" format="boolean" />
         <attr name="state_right_edge" format="boolean" />
         <attr name="state_has_morekeys" format="boolean" />
-        <!-- Vertical offset of the key press feedback from the key. -->
-        <attr name="keyPreviewOffset" format="dimension" />
-        <!-- Height of the key press feedback popup. -->
-        <attr name="keyPreviewHeight" format="dimension" />
-        <!-- Delay after key releasing and key press feedback dismissing in millisecond -->
-        <attr name="keyPreviewLingerTimeout" format="integer" />
 
         <!-- Amount to offset the touch Y coordinate by, for bias correction. -->
         <attr name="verticalCorrection" format="dimension" />
-
-        <!-- Layout resource for more keys panel -->
-        <attr name="moreKeysLayout" format="reference" />
-
-        <attr name="backgroundDimAlpha" format="integer" />
-
-        <!-- Attributes for PreviewPlacerView -->
-        <attr name="gestureFloatingPreviewTextSize" format="dimension" />
-        <attr name="gestureFloatingPreviewTextColor" format="color" />
-        <attr name="gestureFloatingPreviewTextOffset" format="dimension" />
-        <attr name="gestureFloatingPreviewColor" format="color" />
-        <attr name="gestureFloatingPreviewHorizontalPadding" format="dimension" />
-        <attr name="gestureFloatingPreviewVerticalPadding" format="dimension" />
-        <attr name="gestureFloatingPreviewRoundRadius" format="dimension" />
-        <!-- Delay after gesture input and gesture floating preview text dismissing in millisecond -->
-        <attr name="gestureFloatingPreviewTextLingerTimeout" format="integer" />
-        <!-- Delay after gesture trail starts fading out in millisecond. -->
-        <attr name="gesturePreviewTrailFadeoutStartDelay" format="integer" />
-        <!-- Duration while gesture preview trail is fading out in millisecond. -->
-        <attr name="gesturePreviewTrailFadeoutDuration" format="integer" />
-        <!-- Interval of updating gesture preview trail in millisecond. -->
-        <attr name="gesturePreviewTrailUpdateInterval" format="integer" />
-        <attr name="gesturePreviewTrailColor" format="color" />
-        <attr name="gesturePreviewTrailStartWidth" format="dimension" />
-        <attr name="gesturePreviewTrailEndWidth" format="dimension" />
     </declare-styleable>
 
     <declare-styleable name="MainKeyboardView">
@@ -126,8 +93,38 @@
         <attr name="longPressShiftKeyTimeout" format="integer" />
         <!-- Ignore special key timeout while typing in millisecond. -->
         <attr name="ignoreAltCodeKeyTimeout" format="integer" />
+        <!-- Layout resource for key press feedback.-->
+        <attr name="keyPreviewLayout" format="reference" />
+        <!-- Vertical offset of the key press feedback from the key. -->
+        <attr name="keyPreviewOffset" format="dimension" />
+        <!-- Height of the key press feedback popup. -->
+        <attr name="keyPreviewHeight" format="dimension" />
+        <!-- Delay after key releasing and key press feedback dismissing in millisecond -->
+        <attr name="keyPreviewLingerTimeout" format="integer" />
+        <!-- Layout resource for more keys keyboard -->
+        <attr name="moreKeysKeyboardLayout" format="reference" />
+        <attr name="backgroundDimAlpha" format="integer" />
         <!-- More keys keyboard will shown at touched point. -->
         <attr name="showMoreKeysKeyboardAtTouchedPoint" format="boolean" />
+        <!-- Delay after gesture trail starts fading out in millisecond. -->
+        <attr name="gesturePreviewTrailFadeoutStartDelay" format="integer" />
+        <!-- Duration while gesture preview trail is fading out in millisecond. -->
+        <attr name="gesturePreviewTrailFadeoutDuration" format="integer" />
+        <!-- Interval of updating gesture preview trail in millisecond. -->
+        <attr name="gesturePreviewTrailUpdateInterval" format="integer" />
+        <attr name="gesturePreviewTrailColor" format="color" />
+        <attr name="gesturePreviewTrailStartWidth" format="dimension" />
+        <attr name="gesturePreviewTrailEndWidth" format="dimension" />
+        <!-- Delay after gesture input and gesture floating preview text dismissing in millisecond -->
+        <attr name="gestureFloatingPreviewTextLingerTimeout" format="integer" />
+        <!-- Attributes for GestureFloatingPreviewText -->
+        <attr name="gestureFloatingPreviewTextSize" format="dimension" />
+        <attr name="gestureFloatingPreviewTextColor" format="color" />
+        <attr name="gestureFloatingPreviewTextOffset" format="dimension" />
+        <attr name="gestureFloatingPreviewColor" format="color" />
+        <attr name="gestureFloatingPreviewHorizontalPadding" format="dimension" />
+        <attr name="gestureFloatingPreviewVerticalPadding" format="dimension" />
+        <attr name="gestureFloatingPreviewRoundRadius" format="dimension" />
         <!-- Static threshold for gesture after fast typing (msec) -->
         <attr name="gestureStaticTimeThresholdAfterFastTyping" format="integer" />
         <!-- Static threshold for starting gesture detection (keyWidth%/sec) -->
diff --git a/java/res/values/strings.xml b/java/res/values/strings.xml
index f726c1c..54ff23b 100644
--- a/java/res/values/strings.xml
+++ b/java/res/values/strings.xml
@@ -377,6 +377,14 @@
     <string name="prefs_keypress_sound_volume_settings">Keypress sound volume settings</string>
     <!-- Title of the settings for reading an external dictionary file -->
     <string name="prefs_read_external_dictionary">Read external dictionary file</string>
+    <!-- Message to show when there are no files to install as an external dictionary [CHAR LIMIT=100] -->
+    <string name="read_external_dictionary_no_files_message">No dictionary files in the Downloads folder</string>
+    <!-- Title of the dialog that selects a file to install as an external dictionary [CHAR LIMIT=50] -->
+    <string name="read_external_dictionary_multiple_files_title">Select a dictionary file to install</string>
+    <!-- Title of the confirmation dialog to install a file as an external dictionary [CHAR LIMIT=50] -->
+    <string name="read_external_dictionary_confirm_install_message">Really install this file for <xliff:g id="locale_name">%s</xliff:g>?</string>
+    <!-- Title for an error dialog that contains the details of the error in the body [CHAR LIMIT=80] -->
+    <string name="error">There was an error</string>
 
     <!-- Title of the button to revert to the default value of the device in the settings dialog [CHAR LIMIT=15] -->
     <string name="button_default">Default</string>
diff --git a/java/res/values/styles.xml b/java/res/values/styles.xml
index 0bcf943..c398b59 100644
--- a/java/res/values/styles.xml
+++ b/java/res/values/styles.xml
@@ -53,13 +53,8 @@
         <item name="keyHintLetterPadding">@dimen/key_hint_letter_padding</item>
         <item name="keyPopupHintLetterPadding">@dimen/key_popup_hint_letter_padding</item>
         <item name="keyShiftedLetterHintPadding">@dimen/key_uppercase_letter_padding</item>
-        <item name="keyPreviewLayout">@layout/key_preview</item>
         <item name="keyPreviewTextColor">@color/key_text_color_default</item>
-        <item name="keyPreviewOffset">@dimen/key_preview_offset</item>
-        <item name="keyPreviewHeight">@dimen/key_preview_height</item>
         <item name="keyPreviewTextRatio">@fraction/key_preview_text_ratio</item>
-        <item name="keyPreviewLingerTimeout">@integer/config_key_preview_linger_timeout</item>
-        <item name="moreKeysLayout">@layout/more_keys_keyboard</item>
         <item name="verticalCorrection">@dimen/keyboard_vertical_correction</item>
         <item name="keyTextShadowColor">@color/key_text_shadow_color_default</item>
         <item name="keyTextShadowRadius">2.75</item>
@@ -71,7 +66,6 @@
         <item name="gestureFloatingPreviewHorizontalPadding">@dimen/gesture_floating_preview_horizontal_padding</item>
         <item name="gestureFloatingPreviewVerticalPadding">@dimen/gesture_floating_preview_vertical_padding</item>
         <item name="gestureFloatingPreviewRoundRadius">@dimen/gesture_floating_preview_round_radius</item>
-        <item name="gestureFloatingPreviewTextLingerTimeout">@integer/config_gesture_floating_preview_text_linger_timeout</item>
         <item name="gesturePreviewTrailFadeoutStartDelay">@integer/config_gesture_preview_trail_fadeout_start_delay</item>
         <item name="gesturePreviewTrailFadeoutDuration">@integer/config_gesture_preview_trail_fadeout_duration</item>
         <item name="gesturePreviewTrailUpdateInterval">@integer/config_gesture_preview_trail_update_interval</item>
@@ -89,12 +83,18 @@
         <item name="longPressKeyTimeout">@integer/config_long_press_key_timeout</item>
         <item name="longPressShiftKeyTimeout">@integer/config_long_press_shift_key_timeout</item>
         <item name="ignoreAltCodeKeyTimeout">@integer/config_ignore_alt_code_key_timeout</item>
+        <item name="keyPreviewLayout">@layout/key_preview</item>
+        <item name="keyPreviewOffset">@dimen/key_preview_offset</item>
+        <item name="keyPreviewHeight">@dimen/key_preview_height</item>
+        <item name="keyPreviewLingerTimeout">@integer/config_key_preview_linger_timeout</item>
+        <item name="moreKeysKeyboardLayout">@layout/more_keys_keyboard</item>
         <item name="showMoreKeysKeyboardAtTouchedPoint">@bool/config_show_more_keys_keyboard_at_touched_point</item>
         <item name="languageOnSpacebarFinalAlpha">@integer/config_language_on_spacebar_final_alpha</item>
         <item name="languageOnSpacebarFadeoutAnimator">@anim/language_on_spacebar_fadeout</item>
         <item name="altCodeKeyWhileTypingFadeoutAnimator">@anim/alt_code_key_while_typing_fadeout</item>
         <item name="altCodeKeyWhileTypingFadeinAnimator">@anim/alt_code_key_while_typing_fadein</item>
         <!-- Common attributes of MainKeyboardView for gesture typing detection and recognition -->
+        <item name="gestureFloatingPreviewTextLingerTimeout">@integer/config_gesture_floating_preview_text_linger_timeout</item>
         <item name="gestureStaticTimeThresholdAfterFastTyping">@integer/config_gesture_static_time_threshold_after_fast_typing</item>
         <item name="gestureDetectFastMoveSpeedThreshold">@fraction/config_gesture_detect_fast_move_speed_threshold</item>
         <item name="gestureDynamicThresholdDecayDuration">@integer/config_gesture_dynamic_threshold_decay_duration</item>
diff --git a/java/src/com/android/inputmethod/keyboard/KeyboardView.java b/java/src/com/android/inputmethod/keyboard/KeyboardView.java
index 7fc68e0..670564c 100644
--- a/java/src/com/android/inputmethod/keyboard/KeyboardView.java
+++ b/java/src/com/android/inputmethod/keyboard/KeyboardView.java
@@ -28,29 +28,16 @@
 import android.graphics.Region;
 import android.graphics.Typeface;
 import android.graphics.drawable.Drawable;
-import android.os.Message;
 import android.util.AttributeSet;
-import android.util.DisplayMetrics;
-import android.util.Log;
 import android.util.SparseArray;
-import android.util.TypedValue;
-import android.view.LayoutInflater;
 import android.view.View;
-import android.view.ViewGroup;
-import android.widget.TextView;
 
 import com.android.inputmethod.keyboard.internal.KeyDrawParams;
-import com.android.inputmethod.keyboard.internal.KeyPreviewDrawParams;
 import com.android.inputmethod.keyboard.internal.KeyVisualAttributes;
-import com.android.inputmethod.keyboard.internal.PreviewPlacerView;
 import com.android.inputmethod.latin.CollectionUtils;
 import com.android.inputmethod.latin.Constants;
-import com.android.inputmethod.latin.CoordinateUtils;
 import com.android.inputmethod.latin.LatinImeLogger;
 import com.android.inputmethod.latin.R;
-import com.android.inputmethod.latin.StaticInnerHandlerWrapper;
-import com.android.inputmethod.latin.StringUtils;
-import com.android.inputmethod.latin.SuggestedWords;
 import com.android.inputmethod.latin.define.ProductionFlag;
 import com.android.inputmethod.research.ResearchLogger;
 
@@ -60,30 +47,11 @@
  * A view that renders a virtual {@link Keyboard}.
  *
  * @attr ref R.styleable#KeyboardView_keyBackground
- * @attr ref R.styleable#KeyboardView_moreKeysLayout
- * @attr ref R.styleable#KeyboardView_keyPreviewLayout
- * @attr ref R.styleable#KeyboardView_keyPreviewOffset
- * @attr ref R.styleable#KeyboardView_keyPreviewHeight
- * @attr ref R.styleable#KeyboardView_keyPreviewLingerTimeout
  * @attr ref R.styleable#KeyboardView_keyLabelHorizontalPadding
  * @attr ref R.styleable#KeyboardView_keyHintLetterPadding
  * @attr ref R.styleable#KeyboardView_keyPopupHintLetterPadding
  * @attr ref R.styleable#KeyboardView_keyShiftedLetterHintPadding
  * @attr ref R.styleable#KeyboardView_keyTextShadowRadius
- * @attr ref R.styleable#KeyboardView_backgroundDimAlpha
- * @attr ref R.styleable#KeyboardView_gestureFloatingPreviewTextSize
- * @attr ref R.styleable#KeyboardView_gestureFloatingPreviewTextColor
- * @attr ref R.styleable#KeyboardView_gestureFloatingPreviewTextOffset
- * @attr ref R.styleable#KeyboardView_gestureFloatingPreviewColor
- * @attr ref R.styleable#KeyboardView_gestureFloatingPreviewHorizontalPadding
- * @attr ref R.styleable#KeyboardView_gestureFloatingPreviewVerticalPadding
- * @attr ref R.styleable#KeyboardView_gestureFloatingPreviewRoundRadius
- * @attr ref R.styleable#KeyboardView_gestureFloatingPreviewTextLingerTimeout
- * @attr ref R.styleable#KeyboardView_gesturePreviewTrailFadeoutStartDelay
- * @attr ref R.styleable#KeyboardView_gesturePreviewTrailFadeoutDuration
- * @attr ref R.styleable#KeyboardView_gesturePreviewTrailUpdateInterval
- * @attr ref R.styleable#KeyboardView_gesturePreviewTrailColor
- * @attr ref R.styleable#KeyboardView_gesturePreviewTrailWidth
  * @attr ref R.styleable#KeyboardView_verticalCorrection
  * @attr ref R.styleable#Keyboard_Key_keyTypeface
  * @attr ref R.styleable#Keyboard_Key_keyLetterSize
@@ -103,10 +71,7 @@
  * @attr ref R.styleable#Keyboard_Key_keyShiftedLetterHintActivatedColor
  * @attr ref R.styleable#Keyboard_Key_keyPreviewTextColor
  */
-public class KeyboardView extends View implements PointerTracker.DrawingProxy,
-        MoreKeysPanel.Controller {
-    private static final String TAG = KeyboardView.class.getSimpleName();
-
+public class KeyboardView extends View {
     // XML attributes
     protected final KeyVisualAttributes mKeyVisualAttributes;
     private final int mKeyLabelHorizontalPadding;
@@ -115,10 +80,8 @@
     private final float mKeyShiftedLetterHintPadding;
     private final float mKeyTextShadowRadius;
     protected final float mVerticalCorrection;
-    protected final int mMoreKeysLayout;
     protected final Drawable mKeyBackground;
     protected final Rect mKeyBackgroundPadding = new Rect();
-    private final int mBackgroundDimAlpha;
 
     // HORIZONTAL ELLIPSIS "...", character for popup hint.
     private static final String POPUP_HINT_CHAR = "\u2026";
@@ -135,54 +98,7 @@
     private Keyboard mKeyboard;
     protected final KeyDrawParams mKeyDrawParams = new KeyDrawParams();
 
-    // Preview placer view
-    private final PreviewPlacerView mPreviewPlacerView;
-    private final int[] mOriginCoords = CoordinateUtils.newInstance();
-
-    // More keys panel (used by both more keys keyboard and more suggestions view)
-    // TODO: Consider extending to support multiple more keys panels
-    protected MoreKeysPanel mMoreKeysPanel;
-
-    // Key preview
-    private static final int PREVIEW_ALPHA = 240;
-    private final int mKeyPreviewLayoutId;
-    private final int mKeyPreviewOffset;
-    private final int mKeyPreviewHeight;
-    private final SparseArray<TextView> mKeyPreviewTexts = CollectionUtils.newSparseArray();
-    protected final KeyPreviewDrawParams mKeyPreviewDrawParams = new KeyPreviewDrawParams();
-    private boolean mShowKeyPreviewPopup = true;
-    private int mKeyPreviewLingerTimeout;
-
-    // Gesture floating preview text
-    // TODO: Make this parameter customizable by user via settings.
-    private int mGestureFloatingPreviewTextLingerTimeout;
-
-    // Background state set
-    private static final int[][][] KEY_PREVIEW_BACKGROUND_STATE_TABLE = {
-        { // STATE_MIDDLE
-            EMPTY_STATE_SET,
-            { R.attr.state_has_morekeys }
-        },
-        { // STATE_LEFT
-            { R.attr.state_left_edge },
-            { R.attr.state_left_edge, R.attr.state_has_morekeys }
-        },
-        { // STATE_RIGHT
-            { R.attr.state_right_edge },
-            { R.attr.state_right_edge, R.attr.state_has_morekeys }
-        }
-    };
-    private static final int STATE_MIDDLE = 0;
-    private static final int STATE_LEFT = 1;
-    private static final int STATE_RIGHT = 2;
-    private static final int STATE_NORMAL = 0;
-    private static final int STATE_HAS_MOREKEYS = 1;
-    private static final int[] KEY_PREVIEW_BACKGROUND_DEFAULT_STATE =
-            KEY_PREVIEW_BACKGROUND_STATE_TABLE[STATE_MIDDLE][STATE_NORMAL];
-
     // Drawing
-    /** True if the entire keyboard needs to be dimmed. */
-    private boolean mNeedsToDimEntireKeyboard;
     /** True if all keys should be drawn */
     private boolean mInvalidateAllKeys;
     /** The keys that should be drawn */
@@ -204,55 +120,6 @@
     private static final char[] KEY_LABEL_REFERENCE_CHAR = { 'M' };
     private static final char[] KEY_NUMERIC_HINT_LABEL_REFERENCE_CHAR = { '8' };
 
-    private final DrawingHandler mDrawingHandler = new DrawingHandler(this);
-
-    public static class DrawingHandler extends StaticInnerHandlerWrapper<KeyboardView> {
-        private static final int MSG_DISMISS_KEY_PREVIEW = 0;
-        private static final int MSG_DISMISS_GESTURE_FLOATING_PREVIEW_TEXT = 1;
-
-        public DrawingHandler(final KeyboardView outerInstance) {
-            super(outerInstance);
-        }
-
-        @Override
-        public void handleMessage(final Message msg) {
-            final KeyboardView keyboardView = getOuterInstance();
-            if (keyboardView == null) return;
-            final PointerTracker tracker = (PointerTracker) msg.obj;
-            switch (msg.what) {
-            case MSG_DISMISS_KEY_PREVIEW:
-                final TextView previewText = keyboardView.mKeyPreviewTexts.get(tracker.mPointerId);
-                if (previewText != null) {
-                    previewText.setVisibility(INVISIBLE);
-                }
-                break;
-            case MSG_DISMISS_GESTURE_FLOATING_PREVIEW_TEXT:
-                keyboardView.mPreviewPlacerView.setGestureFloatingPreviewText(SuggestedWords.EMPTY);
-                break;
-            }
-        }
-
-        public void dismissKeyPreview(final long delay, final PointerTracker tracker) {
-            sendMessageDelayed(obtainMessage(MSG_DISMISS_KEY_PREVIEW, tracker), delay);
-        }
-
-        public void cancelDismissKeyPreview(final PointerTracker tracker) {
-            removeMessages(MSG_DISMISS_KEY_PREVIEW, tracker);
-        }
-
-        private void cancelAllDismissKeyPreviews() {
-            removeMessages(MSG_DISMISS_KEY_PREVIEW);
-        }
-
-        public void dismissGestureFloatingPreviewText(final long delay) {
-            sendMessageDelayed(obtainMessage(MSG_DISMISS_GESTURE_FLOATING_PREVIEW_TEXT), delay);
-        }
-
-        public void cancelAllMessages() {
-            cancelAllDismissKeyPreviews();
-        }
-    }
-
     public KeyboardView(final Context context, final AttributeSet attrs) {
         this(context, attrs, R.attr.keyboardViewStyle);
     }
@@ -264,12 +131,6 @@
                 R.styleable.KeyboardView, defStyle, R.style.KeyboardView);
         mKeyBackground = keyboardViewAttr.getDrawable(R.styleable.KeyboardView_keyBackground);
         mKeyBackground.getPadding(mKeyBackgroundPadding);
-        mKeyPreviewOffset = keyboardViewAttr.getDimensionPixelOffset(
-                R.styleable.KeyboardView_keyPreviewOffset, 0);
-        mKeyPreviewHeight = keyboardViewAttr.getDimensionPixelSize(
-                R.styleable.KeyboardView_keyPreviewHeight, 80);
-        mKeyPreviewLingerTimeout = keyboardViewAttr.getInt(
-                R.styleable.KeyboardView_keyPreviewLingerTimeout, 0);
         mKeyLabelHorizontalPadding = keyboardViewAttr.getDimensionPixelOffset(
                 R.styleable.KeyboardView_keyLabelHorizontalPadding, 0);
         mKeyHintLetterPadding = keyboardViewAttr.getDimension(
@@ -280,19 +141,8 @@
                 R.styleable.KeyboardView_keyShiftedLetterHintPadding, 0);
         mKeyTextShadowRadius = keyboardViewAttr.getFloat(
                 R.styleable.KeyboardView_keyTextShadowRadius, 0.0f);
-        mKeyPreviewLayoutId = keyboardViewAttr.getResourceId(
-                R.styleable.KeyboardView_keyPreviewLayout, 0);
-        if (mKeyPreviewLayoutId == 0) {
-            mShowKeyPreviewPopup = false;
-        }
         mVerticalCorrection = keyboardViewAttr.getDimension(
                 R.styleable.KeyboardView_verticalCorrection, 0);
-        mMoreKeysLayout = keyboardViewAttr.getResourceId(
-                R.styleable.KeyboardView_moreKeysLayout, 0);
-        mBackgroundDimAlpha = keyboardViewAttr.getInt(
-                R.styleable.KeyboardView_backgroundDimAlpha, 0);
-        mGestureFloatingPreviewTextLingerTimeout = keyboardViewAttr.getInt(
-                R.styleable.KeyboardView_gestureFloatingPreviewTextLingerTimeout, 0);
         keyboardViewAttr.recycle();
 
         final TypedArray keyAttr = context.obtainStyledAttributes(attrs,
@@ -300,7 +150,6 @@
         mKeyVisualAttributes = KeyVisualAttributes.newInstance(keyAttr);
         keyAttr.recycle();
 
-        mPreviewPlacerView = new PreviewPlacerView(context, attrs);
         mPaint.setAntiAlias(true);
     }
 
@@ -336,33 +185,6 @@
         return mKeyboard;
     }
 
-    /**
-     * 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
-     * @param delay the delay after which the preview is dismissed
-     * @see #isKeyPreviewPopupEnabled()
-     */
-    public void setKeyPreviewPopupEnabled(final boolean previewEnabled, final int delay) {
-        mShowKeyPreviewPopup = previewEnabled;
-        mKeyPreviewLingerTimeout = delay;
-    }
-
-    /**
-     * Returns the enabled state of the key feedback preview
-     * @return whether or not the key feedback preview is enabled
-     * @see #setKeyPreviewPopupEnabled(boolean, int)
-     */
-    public boolean isKeyPreviewPopupEnabled() {
-        return mShowKeyPreviewPopup;
-    }
-
-    public void setGesturePreviewMode(final boolean drawsGesturePreviewTrail,
-            final boolean drawsGestureFloatingPreviewText) {
-        mPreviewPlacerView.setGesturePreviewMode(
-                drawsGesturePreviewTrail, drawsGestureFloatingPreviewText);
-    }
-
     @Override
     protected void onMeasure(final int widthMeasureSpec, final int heightMeasureSpec) {
         if (mKeyboard != null) {
@@ -375,7 +197,7 @@
     }
 
     @Override
-    public void onDraw(final Canvas canvas) {
+    protected void onDraw(final Canvas canvas) {
         super.onDraw(canvas);
         if (canvas.isHardwareAccelerated()) {
             onDrawKeyboard(canvas);
@@ -465,14 +287,6 @@
             }
         }
 
-        // Overlay a dark rectangle to dim.
-        if (mNeedsToDimEntireKeyboard) {
-            paint.setColor(Color.BLACK);
-            paint.setAlpha(mBackgroundDimAlpha);
-            // Note: clipRegion() above is in effect if it was called.
-            canvas.drawRect(0, 0, width, height, paint);
-        }
-
         // ResearchLogging indicator.
         // TODO: Reimplement using a keyboard background image specific to the ResearchLogger,
         // and remove this call.
@@ -484,14 +298,6 @@
         mInvalidateAllKeys = false;
     }
 
-    public void dimEntireKeyboard(final boolean dimmed) {
-        final boolean needsRedrawing = mNeedsToDimEntireKeyboard != dimmed;
-        mNeedsToDimEntireKeyboard = dimmed;
-        if (needsRedrawing) {
-            invalidateAllKeys();
-        }
-    }
-
     private void onDrawKey(final Key key, final Canvas canvas, final Paint paint) {
         final int keyDrawX = key.getDrawX() + getPaddingLeft();
         final int keyDrawY = key.mY + getPaddingTop();
@@ -801,200 +607,6 @@
         return paint;
     }
 
-    public void cancelAllMessages() {
-        mDrawingHandler.cancelAllMessages();
-    }
-
-    private TextView getKeyPreviewText(final int pointerId) {
-        TextView previewText = mKeyPreviewTexts.get(pointerId);
-        if (previewText != null) {
-            return previewText;
-        }
-        final Context context = getContext();
-        if (mKeyPreviewLayoutId != 0) {
-            previewText = (TextView)LayoutInflater.from(context).inflate(mKeyPreviewLayoutId, null);
-        } else {
-            previewText = new TextView(context);
-        }
-        mKeyPreviewTexts.put(pointerId, previewText);
-        return previewText;
-    }
-
-    private void dismissAllKeyPreviews() {
-        final int pointerCount = mKeyPreviewTexts.size();
-        for (int id = 0; id < pointerCount; id++) {
-            final TextView previewText = mKeyPreviewTexts.get(id);
-            if (previewText != null) {
-                previewText.setVisibility(INVISIBLE);
-            }
-        }
-        PointerTracker.setReleasedKeyGraphicsToAllKeys();
-    }
-
-    @Override
-    public void dismissKeyPreview(final PointerTracker tracker) {
-        mDrawingHandler.dismissKeyPreview(mKeyPreviewLingerTimeout, tracker);
-    }
-
-    private void addKeyPreview(final TextView keyPreview) {
-        locatePreviewPlacerView();
-        mPreviewPlacerView.addView(
-                keyPreview, ViewLayoutUtils.newLayoutParam(mPreviewPlacerView, 0, 0));
-    }
-
-    private void locatePreviewPlacerView() {
-        if (mPreviewPlacerView.getParent() != null) {
-            return;
-        }
-        final int width = getWidth();
-        final int height = getHeight();
-        if (width == 0 || height == 0) {
-            // In transient state.
-            return;
-        }
-        getLocationInWindow(mOriginCoords);
-        final DisplayMetrics dm = getResources().getDisplayMetrics();
-        if (CoordinateUtils.y(mOriginCoords) < dm.heightPixels / 4) {
-            // In transient state.
-            return;
-        }
-        final View rootView = getRootView();
-        if (rootView == null) {
-            Log.w(TAG, "Cannot find root view");
-            return;
-        }
-        final ViewGroup windowContentView = (ViewGroup)rootView.findViewById(android.R.id.content);
-        // Note: It'd be very weird if we get null by android.R.id.content.
-        if (windowContentView == null) {
-            Log.w(TAG, "Cannot find android.R.id.content view to add PreviewPlacerView");
-        } else {
-            windowContentView.addView(mPreviewPlacerView);
-            mPreviewPlacerView.setKeyboardViewGeometry(mOriginCoords, width, height);
-        }
-    }
-
-    @Override
-    public void showSlidingKeyInputPreview(final PointerTracker tracker) {
-        locatePreviewPlacerView();
-        mPreviewPlacerView.showSlidingKeyInputPreview(tracker);
-    }
-
-    @Override
-    public void dismissSlidingKeyInputPreview() {
-        mPreviewPlacerView.dismissSlidingKeyInputPreview();
-    }
-
-    public void showGestureFloatingPreviewText(final SuggestedWords suggestedWords) {
-        locatePreviewPlacerView();
-        mPreviewPlacerView.setGestureFloatingPreviewText(suggestedWords);
-    }
-
-    public void dismissGestureFloatingPreviewText() {
-        locatePreviewPlacerView();
-        mDrawingHandler.dismissGestureFloatingPreviewText(mGestureFloatingPreviewTextLingerTimeout);
-    }
-
-    @Override
-    public void showGesturePreviewTrail(final PointerTracker tracker,
-            final boolean isOldestTracker) {
-        locatePreviewPlacerView();
-        mPreviewPlacerView.invalidatePointer(tracker, isOldestTracker);
-    }
-
-    @Override
-    public void showKeyPreview(final PointerTracker tracker) {
-        final KeyPreviewDrawParams previewParams = mKeyPreviewDrawParams;
-        if (!mShowKeyPreviewPopup) {
-            previewParams.mPreviewVisibleOffset = -mKeyboard.mVerticalGap;
-            return;
-        }
-
-        final TextView previewText = getKeyPreviewText(tracker.mPointerId);
-        // If the key preview has no parent view yet, add it to the ViewGroup which can place
-        // key preview absolutely in SoftInputWindow.
-        if (previewText.getParent() == null) {
-            addKeyPreview(previewText);
-        }
-
-        mDrawingHandler.cancelDismissKeyPreview(tracker);
-        final Key key = tracker.getKey();
-        // If key is invalid or IME is already closed, we must not show key preview.
-        // Trying to show key preview while root window is closed causes
-        // WindowManager.BadTokenException.
-        if (key == null) {
-            return;
-        }
-
-        final KeyDrawParams drawParams = mKeyDrawParams;
-        previewText.setTextColor(drawParams.mPreviewTextColor);
-        final Drawable background = previewText.getBackground();
-        if (background != null) {
-            background.setState(KEY_PREVIEW_BACKGROUND_DEFAULT_STATE);
-            background.setAlpha(PREVIEW_ALPHA);
-        }
-        final String label = key.isShiftedLetterActivated() ? key.mHintLabel : key.mLabel;
-        // What we show as preview should match what we show on a key top in onDraw().
-        if (label != null) {
-            // TODO Should take care of temporaryShiftLabel here.
-            previewText.setCompoundDrawables(null, null, null, null);
-            if (StringUtils.codePointCount(label) > 1) {
-                previewText.setTextSize(TypedValue.COMPLEX_UNIT_PX, drawParams.mLetterSize);
-                previewText.setTypeface(Typeface.DEFAULT_BOLD);
-            } else {
-                previewText.setTextSize(TypedValue.COMPLEX_UNIT_PX, drawParams.mPreviewTextSize);
-                previewText.setTypeface(key.selectTypeface(drawParams));
-            }
-            previewText.setText(label);
-        } else {
-            previewText.setCompoundDrawables(null, null, null,
-                    key.getPreviewIcon(mKeyboard.mIconsSet));
-            previewText.setText(null);
-        }
-
-        previewText.measure(
-                ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT);
-        final int keyDrawWidth = key.getDrawWidth();
-        final int previewWidth = previewText.getMeasuredWidth();
-        final int previewHeight = mKeyPreviewHeight;
-        // The width and height of visible part of the key preview background. The content marker
-        // of the background 9-patch have to cover the visible part of the background.
-        previewParams.mPreviewVisibleWidth = previewWidth - previewText.getPaddingLeft()
-                - previewText.getPaddingRight();
-        previewParams.mPreviewVisibleHeight = previewHeight - previewText.getPaddingTop()
-                - previewText.getPaddingBottom();
-        // The distance between the top edge of the parent key and the bottom of the visible part
-        // of the key preview background.
-        previewParams.mPreviewVisibleOffset = mKeyPreviewOffset - previewText.getPaddingBottom();
-        getLocationInWindow(mOriginCoords);
-        // The key preview is horizontally aligned with the center of the visible part of the
-        // parent key. If it doesn't fit in this {@link KeyboardView}, it is moved inward to fit and
-        // the left/right background is used if such background is specified.
-        final int statePosition;
-        int previewX = key.getDrawX() - (previewWidth - keyDrawWidth) / 2
-                + CoordinateUtils.x(mOriginCoords);
-        if (previewX < 0) {
-            previewX = 0;
-            statePosition = STATE_LEFT;
-        } else if (previewX > getWidth() - previewWidth) {
-            previewX = getWidth() - previewWidth;
-            statePosition = STATE_RIGHT;
-        } else {
-            statePosition = STATE_MIDDLE;
-        }
-        // The key preview is placed vertically above the top edge of the parent key with an
-        // arbitrary offset.
-        final int previewY = key.mY - previewHeight + mKeyPreviewOffset
-                + CoordinateUtils.y(mOriginCoords);
-
-        if (background != null) {
-            final int hasMoreKeys = (key.mMoreKeys != null) ? STATE_HAS_MOREKEYS : STATE_NORMAL;
-            background.setState(KEY_PREVIEW_BACKGROUND_STATE_TABLE[statePosition][hasMoreKeys]);
-        }
-        ViewLayoutUtils.placeViewAt(
-                previewText, previewX, previewY, previewWidth, previewHeight);
-        previewText.setVisibility(VISIBLE);
-    }
-
     /**
      * Requests a redraw of the entire keyboard. Calling {@link #invalidate} is not sufficient
      * because the keyboard renders the keys to an off-screen buffer and an invalidate() only
@@ -1014,7 +626,6 @@
      * @param key key in the attached {@link Keyboard}.
      * @see #invalidateAllKeys
      */
-    @Override
     public void invalidateKey(final Key key) {
         if (mInvalidateAllKeys) return;
         if (key == null) return;
@@ -1025,49 +636,15 @@
     }
 
     public void closing() {
-        dismissAllKeyPreviews();
-        cancelAllMessages();
-        onCancelMoreKeysPanel();
         mInvalidateAllKeys = true;
         mKeyboard = null;
         requestLayout();
     }
 
     @Override
-    public void onShowMoreKeysPanel(final MoreKeysPanel panel) {
-        if (isShowingMoreKeysPanel()) {
-            onDismissMoreKeysPanel();
-        }
-        mMoreKeysPanel = panel;
-        mPreviewPlacerView.addView(mMoreKeysPanel.getContainerView());
-    }
-
-    public boolean isShowingMoreKeysPanel() {
-        return (mMoreKeysPanel != null);
-    }
-
-    @Override
-    public void onCancelMoreKeysPanel() {
-        if (isShowingMoreKeysPanel()) {
-            mMoreKeysPanel.dismissMoreKeysPanel();
-        }
-    }
-
-    @Override
-    public boolean onDismissMoreKeysPanel() {
-        if (isShowingMoreKeysPanel()) {
-            mPreviewPlacerView.removeView(mMoreKeysPanel.getContainerView());
-            mMoreKeysPanel = null;
-            return true;
-        }
-        return false;
-    }
-
-    @Override
     protected void onDetachedFromWindow() {
         super.onDetachedFromWindow();
         closing();
-        mPreviewPlacerView.removeAllViews();
         freeOffscreenBuffer();
     }
 }
diff --git a/java/src/com/android/inputmethod/keyboard/MainKeyboardView.java b/java/src/com/android/inputmethod/keyboard/MainKeyboardView.java
index 625575d..1ce61fb 100644
--- a/java/src/com/android/inputmethod/keyboard/MainKeyboardView.java
+++ b/java/src/com/android/inputmethod/keyboard/MainKeyboardView.java
@@ -24,6 +24,7 @@
 import android.content.res.Resources;
 import android.content.res.TypedArray;
 import android.graphics.Canvas;
+import android.graphics.Color;
 import android.graphics.Paint;
 import android.graphics.Paint.Align;
 import android.graphics.Typeface;
@@ -32,13 +33,17 @@
 import android.os.SystemClock;
 import android.preference.PreferenceManager;
 import android.util.AttributeSet;
+import android.util.DisplayMetrics;
 import android.util.Log;
+import android.util.SparseArray;
+import android.util.TypedValue;
 import android.view.LayoutInflater;
 import android.view.MotionEvent;
 import android.view.View;
 import android.view.ViewConfiguration;
 import android.view.ViewGroup;
 import android.view.inputmethod.InputMethodSubtype;
+import android.widget.TextView;
 
 import com.android.inputmethod.accessibility.AccessibilityUtils;
 import com.android.inputmethod.accessibility.AccessibleKeyboardViewProxy;
@@ -46,7 +51,10 @@
 import com.android.inputmethod.keyboard.PointerTracker.DrawingProxy;
 import com.android.inputmethod.keyboard.PointerTracker.TimerProxy;
 import com.android.inputmethod.keyboard.internal.KeyDrawParams;
+import com.android.inputmethod.keyboard.internal.KeyPreviewDrawParams;
+import com.android.inputmethod.keyboard.internal.PreviewPlacerView;
 import com.android.inputmethod.keyboard.internal.TouchScreenRegulator;
+import com.android.inputmethod.latin.CollectionUtils;
 import com.android.inputmethod.latin.Constants;
 import com.android.inputmethod.latin.CoordinateUtils;
 import com.android.inputmethod.latin.DebugSettings;
@@ -57,6 +65,7 @@
 import com.android.inputmethod.latin.StaticInnerHandlerWrapper;
 import com.android.inputmethod.latin.StringUtils;
 import com.android.inputmethod.latin.SubtypeLocale;
+import com.android.inputmethod.latin.SuggestedWords;
 import com.android.inputmethod.latin.Utils.UsabilityStudyLogUtils;
 import com.android.inputmethod.latin.define.ProductionFlag;
 import com.android.inputmethod.research.ResearchLogger;
@@ -85,7 +94,14 @@
  * @attr ref R.styleable#MainKeyboardView_longPressKeyTimeout
  * @attr ref R.styleable#MainKeyboardView_longPressShiftKeyTimeout
  * @attr ref R.styleable#MainKeyboardView_ignoreAltCodeKeyTimeout
+ * @attr ref R.styleable#MainKeyboardView_keyPreviewLayout
+ * @attr ref R.styleable#MainKeyboardView_keyPreviewOffset
+ * @attr ref R.styleable#MainKeyboardView_keyPreviewHeight
+ * @attr ref R.styleable#MainKeyboardView_keyPreviewLingerTimeout
+ * @attr ref R.styleable#MainKeyboardView_moreKeysKeyboardLayout
+ * @attr ref R.styleable#MainKeyboardView_backgroundDimAlpha
  * @attr ref R.styleable#MainKeyboardView_showMoreKeysKeyboardAtTouchPoint
+ * @attr ref R.styleable#MainKeyboardView_gestureFloatingPreviewTextLingerTimeout
  * @attr ref R.styleable#MainKeyboardView_gestureStaticTimeThresholdAfterFastTyping
  * @attr ref R.styleable#MainKeyboardView_gestureDetectFastMoveSpeedThreshold
  * @attr ref R.styleable#MainKeyboardView_gestureDynamicThresholdDecayDuration
@@ -99,6 +115,7 @@
  * @attr ref R.styleable#MainKeyboardView_suppressKeyPreviewAfterBatchInputDuration
  */
 public final class MainKeyboardView extends KeyboardView implements PointerTracker.KeyEventHandler,
+        PointerTracker.DrawingProxy, MoreKeysPanel.Controller,
         TouchScreenRegulator.ProcessMotionEvent {
     private static final String TAG = MainKeyboardView.class.getSimpleName();
 
@@ -134,14 +151,38 @@
     private ObjectAnimator mAltCodeKeyWhileTypingFadeinAnimator;
     private int mAltCodeKeyWhileTypingAnimAlpha = Constants.Color.ALPHA_OPAQUE;
 
+    // Preview placer view
+    private final PreviewPlacerView mPreviewPlacerView;
+    private final int[] mOriginCoords = CoordinateUtils.newInstance();
+
+    // Key preview
+    private static final int PREVIEW_ALPHA = 240;
+    private final int mKeyPreviewLayoutId;
+    private final int mKeyPreviewOffset;
+    private final int mKeyPreviewHeight;
+    private final SparseArray<TextView> mKeyPreviewTexts = CollectionUtils.newSparseArray();
+    private final KeyPreviewDrawParams mKeyPreviewDrawParams = new KeyPreviewDrawParams();
+    private boolean mShowKeyPreviewPopup = true;
+    private int mKeyPreviewLingerTimeout;
+
     // More keys keyboard
+    private final Paint mBackgroundDimAlphaPaint = new Paint();
+    private boolean mNeedsToDimEntireKeyboard;
     private final WeakHashMap<Key, MoreKeysPanel> mMoreKeysPanelCache =
             new WeakHashMap<Key, MoreKeysPanel>();
+    private final int mMoreKeysLayout;
     private final boolean mConfigShowMoreKeysKeyboardAtTouchedPoint;
+    // More keys panel (used by both more keys keyboard and more suggestions view)
+    // TODO: Consider extending to support multiple more keys panels
+    private MoreKeysPanel mMoreKeysPanel;
+
+    // Gesture floating preview text
+    // TODO: Make this parameter customizable by user via settings.
+    private int mGestureFloatingPreviewTextLingerTimeout;
 
     private final TouchScreenRegulator mTouchScreenRegulator;
 
-    protected KeyDetector mKeyDetector;
+    private KeyDetector mKeyDetector;
     private final boolean mHasDistinctMultitouch;
     private int mOldPointerCount = 1;
     private Key mOldKey;
@@ -381,6 +422,57 @@
         }
     }
 
+    private final DrawingHandler mDrawingHandler = new DrawingHandler(this);
+
+    public static class DrawingHandler extends StaticInnerHandlerWrapper<MainKeyboardView> {
+        private static final int MSG_DISMISS_KEY_PREVIEW = 0;
+        private static final int MSG_DISMISS_GESTURE_FLOATING_PREVIEW_TEXT = 1;
+
+        public DrawingHandler(final MainKeyboardView outerInstance) {
+            super(outerInstance);
+        }
+
+        @Override
+        public void handleMessage(final Message msg) {
+            final MainKeyboardView mainKeyboardView = getOuterInstance();
+            if (mainKeyboardView == null) return;
+            final PointerTracker tracker = (PointerTracker) msg.obj;
+            switch (msg.what) {
+            case MSG_DISMISS_KEY_PREVIEW:
+                final TextView previewText = mainKeyboardView.mKeyPreviewTexts.get(
+                        tracker.mPointerId);
+                if (previewText != null) {
+                    previewText.setVisibility(INVISIBLE);
+                }
+                break;
+            case MSG_DISMISS_GESTURE_FLOATING_PREVIEW_TEXT:
+                mainKeyboardView.mPreviewPlacerView.setGestureFloatingPreviewText(
+                        SuggestedWords.EMPTY);
+                break;
+            }
+        }
+
+        public void dismissKeyPreview(final long delay, final PointerTracker tracker) {
+            sendMessageDelayed(obtainMessage(MSG_DISMISS_KEY_PREVIEW, tracker), delay);
+        }
+
+        public void cancelDismissKeyPreview(final PointerTracker tracker) {
+            removeMessages(MSG_DISMISS_KEY_PREVIEW, tracker);
+        }
+
+        private void cancelAllDismissKeyPreviews() {
+            removeMessages(MSG_DISMISS_KEY_PREVIEW);
+        }
+
+        public void dismissGestureFloatingPreviewText(final long delay) {
+            sendMessageDelayed(obtainMessage(MSG_DISMISS_GESTURE_FLOATING_PREVIEW_TEXT), delay);
+        }
+
+        public void cancelAllMessages() {
+            cancelAllDismissKeyPreviews();
+        }
+    }
+
     public MainKeyboardView(final Context context, final AttributeSet attrs) {
         this(context, attrs, R.attr.mainKeyboardViewStyle);
     }
@@ -402,38 +494,59 @@
                         res, R.array.phantom_sudden_move_event_device_list));
         PointerTracker.init(needsPhantomSuddenMoveEventHack);
 
-        final TypedArray a = context.obtainStyledAttributes(
+        final TypedArray mainKeyboardViewAttr = context.obtainStyledAttributes(
                 attrs, R.styleable.MainKeyboardView, defStyle, R.style.MainKeyboardView);
-        mAutoCorrectionSpacebarLedEnabled = a.getBoolean(
+        final int backgroundDimAlpha = mainKeyboardViewAttr.getInt(
+                R.styleable.MainKeyboardView_backgroundDimAlpha, 0);
+        mBackgroundDimAlphaPaint.setColor(Color.BLACK);
+        mBackgroundDimAlphaPaint.setAlpha(backgroundDimAlpha);
+        mAutoCorrectionSpacebarLedEnabled = mainKeyboardViewAttr.getBoolean(
                 R.styleable.MainKeyboardView_autoCorrectionSpacebarLedEnabled, false);
-        mAutoCorrectionSpacebarLedIcon = a.getDrawable(
+        mAutoCorrectionSpacebarLedIcon = mainKeyboardViewAttr.getDrawable(
                 R.styleable.MainKeyboardView_autoCorrectionSpacebarLedIcon);
-        mSpacebarTextRatio = a.getFraction(
+        mSpacebarTextRatio = mainKeyboardViewAttr.getFraction(
                 R.styleable.MainKeyboardView_spacebarTextRatio, 1, 1, 1.0f);
-        mSpacebarTextColor = a.getColor(R.styleable.MainKeyboardView_spacebarTextColor, 0);
-        mSpacebarTextShadowColor = a.getColor(
+        mSpacebarTextColor = mainKeyboardViewAttr.getColor(
+                R.styleable.MainKeyboardView_spacebarTextColor, 0);
+        mSpacebarTextShadowColor = mainKeyboardViewAttr.getColor(
                 R.styleable.MainKeyboardView_spacebarTextShadowColor, 0);
-        mLanguageOnSpacebarFinalAlpha = a.getInt(
+        mLanguageOnSpacebarFinalAlpha = mainKeyboardViewAttr.getInt(
                 R.styleable.MainKeyboardView_languageOnSpacebarFinalAlpha,
                 Constants.Color.ALPHA_OPAQUE);
-        final int languageOnSpacebarFadeoutAnimatorResId = a.getResourceId(
+        final int languageOnSpacebarFadeoutAnimatorResId = mainKeyboardViewAttr.getResourceId(
                 R.styleable.MainKeyboardView_languageOnSpacebarFadeoutAnimator, 0);
-        final int altCodeKeyWhileTypingFadeoutAnimatorResId = a.getResourceId(
+        final int altCodeKeyWhileTypingFadeoutAnimatorResId = mainKeyboardViewAttr.getResourceId(
                 R.styleable.MainKeyboardView_altCodeKeyWhileTypingFadeoutAnimator, 0);
-        final int altCodeKeyWhileTypingFadeinAnimatorResId = a.getResourceId(
+        final int altCodeKeyWhileTypingFadeinAnimatorResId = mainKeyboardViewAttr.getResourceId(
                 R.styleable.MainKeyboardView_altCodeKeyWhileTypingFadeinAnimator, 0);
 
-        final float keyHysteresisDistance = a.getDimension(
+        final float keyHysteresisDistance = mainKeyboardViewAttr.getDimension(
                 R.styleable.MainKeyboardView_keyHysteresisDistance, 0);
-        final float keyHysteresisDistanceForSlidingModifier = a.getDimension(
+        final float keyHysteresisDistanceForSlidingModifier = mainKeyboardViewAttr.getDimension(
                 R.styleable.MainKeyboardView_keyHysteresisDistanceForSlidingModifier, 0);
         mKeyDetector = new KeyDetector(
                 keyHysteresisDistance, keyHysteresisDistanceForSlidingModifier);
-        mKeyTimerHandler = new KeyTimerHandler(this, a);
-        mConfigShowMoreKeysKeyboardAtTouchedPoint = a.getBoolean(
+        mKeyTimerHandler = new KeyTimerHandler(this, mainKeyboardViewAttr);
+        mKeyPreviewOffset = mainKeyboardViewAttr.getDimensionPixelOffset(
+                R.styleable.MainKeyboardView_keyPreviewOffset, 0);
+        mKeyPreviewHeight = mainKeyboardViewAttr.getDimensionPixelSize(
+                R.styleable.MainKeyboardView_keyPreviewHeight, 0);
+        mKeyPreviewLingerTimeout = mainKeyboardViewAttr.getInt(
+                R.styleable.MainKeyboardView_keyPreviewLingerTimeout, 0);
+        mKeyPreviewLayoutId = mainKeyboardViewAttr.getResourceId(
+                R.styleable.MainKeyboardView_keyPreviewLayout, 0);
+        if (mKeyPreviewLayoutId == 0) {
+            mShowKeyPreviewPopup = false;
+        }
+        mMoreKeysLayout = mainKeyboardViewAttr.getResourceId(
+                R.styleable.MainKeyboardView_moreKeysKeyboardLayout, 0);
+        mConfigShowMoreKeysKeyboardAtTouchedPoint = mainKeyboardViewAttr.getBoolean(
                 R.styleable.MainKeyboardView_showMoreKeysKeyboardAtTouchedPoint, false);
-        PointerTracker.setParameters(a);
-        a.recycle();
+
+        mGestureFloatingPreviewTextLingerTimeout = mainKeyboardViewAttr.getInt(
+                R.styleable.MainKeyboardView_gestureFloatingPreviewTextLingerTimeout, 0);
+        PointerTracker.setParameters(mainKeyboardViewAttr);
+        mainKeyboardViewAttr.recycle();
 
         mLanguageOnSpacebarFadeoutAnimator = loadObjectAnimator(
                 languageOnSpacebarFadeoutAnimatorResId, this);
@@ -441,6 +554,8 @@
                 altCodeKeyWhileTypingFadeoutAnimatorResId, this);
         mAltCodeKeyWhileTypingFadeinAnimator = loadObjectAnimator(
                 altCodeKeyWhileTypingFadeinAnimatorResId, this);
+
+        mPreviewPlacerView = new PreviewPlacerView(context, attrs);
     }
 
     private ObjectAnimator loadObjectAnimator(final int resId, final Object target) {
@@ -538,6 +653,246 @@
         AccessibleKeyboardViewProxy.getInstance().setKeyboard();
     }
 
+    /**
+     * 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
+     * @param delay the delay after which the preview is dismissed
+     * @see #isKeyPreviewPopupEnabled()
+     */
+    public void setKeyPreviewPopupEnabled(final boolean previewEnabled, final int delay) {
+        mShowKeyPreviewPopup = previewEnabled;
+        mKeyPreviewLingerTimeout = delay;
+    }
+
+
+    private void locatePreviewPlacerView() {
+        if (mPreviewPlacerView.getParent() != null) {
+            return;
+        }
+        final int width = getWidth();
+        final int height = getHeight();
+        if (width == 0 || height == 0) {
+            // In transient state.
+            return;
+        }
+        getLocationInWindow(mOriginCoords);
+        final DisplayMetrics dm = getResources().getDisplayMetrics();
+        if (CoordinateUtils.y(mOriginCoords) < dm.heightPixels / 4) {
+            // In transient state.
+            return;
+        }
+        final View rootView = getRootView();
+        if (rootView == null) {
+            Log.w(TAG, "Cannot find root view");
+            return;
+        }
+        final ViewGroup windowContentView = (ViewGroup)rootView.findViewById(android.R.id.content);
+        // Note: It'd be very weird if we get null by android.R.id.content.
+        if (windowContentView == null) {
+            Log.w(TAG, "Cannot find android.R.id.content view to add PreviewPlacerView");
+        } else {
+            windowContentView.addView(mPreviewPlacerView);
+            mPreviewPlacerView.setKeyboardViewGeometry(mOriginCoords, width, height);
+        }
+    }
+
+    /**
+     * Returns the enabled state of the key feedback preview
+     * @return whether or not the key feedback preview is enabled
+     * @see #setKeyPreviewPopupEnabled(boolean, int)
+     */
+    public boolean isKeyPreviewPopupEnabled() {
+        return mShowKeyPreviewPopup;
+    }
+
+    private void addKeyPreview(final TextView keyPreview) {
+        locatePreviewPlacerView();
+        mPreviewPlacerView.addView(
+                keyPreview, ViewLayoutUtils.newLayoutParam(mPreviewPlacerView, 0, 0));
+    }
+
+    private TextView getKeyPreviewText(final int pointerId) {
+        TextView previewText = mKeyPreviewTexts.get(pointerId);
+        if (previewText != null) {
+            return previewText;
+        }
+        final Context context = getContext();
+        if (mKeyPreviewLayoutId != 0) {
+            previewText = (TextView)LayoutInflater.from(context).inflate(mKeyPreviewLayoutId, null);
+        } else {
+            previewText = new TextView(context);
+        }
+        mKeyPreviewTexts.put(pointerId, previewText);
+        return previewText;
+    }
+
+    private void dismissAllKeyPreviews() {
+        final int pointerCount = mKeyPreviewTexts.size();
+        for (int id = 0; id < pointerCount; id++) {
+            final TextView previewText = mKeyPreviewTexts.get(id);
+            if (previewText != null) {
+                previewText.setVisibility(INVISIBLE);
+            }
+        }
+        PointerTracker.setReleasedKeyGraphicsToAllKeys();
+    }
+
+    // Background state set
+    private static final int[][][] KEY_PREVIEW_BACKGROUND_STATE_TABLE = {
+        { // STATE_MIDDLE
+            EMPTY_STATE_SET,
+            { R.attr.state_has_morekeys }
+        },
+        { // STATE_LEFT
+            { R.attr.state_left_edge },
+            { R.attr.state_left_edge, R.attr.state_has_morekeys }
+        },
+        { // STATE_RIGHT
+            { R.attr.state_right_edge },
+            { R.attr.state_right_edge, R.attr.state_has_morekeys }
+        }
+    };
+    private static final int STATE_MIDDLE = 0;
+    private static final int STATE_LEFT = 1;
+    private static final int STATE_RIGHT = 2;
+    private static final int STATE_NORMAL = 0;
+    private static final int STATE_HAS_MOREKEYS = 1;
+    private static final int[] KEY_PREVIEW_BACKGROUND_DEFAULT_STATE =
+            KEY_PREVIEW_BACKGROUND_STATE_TABLE[STATE_MIDDLE][STATE_NORMAL];
+
+    @Override
+    public void showKeyPreview(final PointerTracker tracker) {
+        final KeyPreviewDrawParams previewParams = mKeyPreviewDrawParams;
+        final Keyboard keyboard = getKeyboard();
+        if (!mShowKeyPreviewPopup) {
+            previewParams.mPreviewVisibleOffset = -keyboard.mVerticalGap;
+            return;
+        }
+
+        final TextView previewText = getKeyPreviewText(tracker.mPointerId);
+        // If the key preview has no parent view yet, add it to the ViewGroup which can place
+        // key preview absolutely in SoftInputWindow.
+        if (previewText.getParent() == null) {
+            addKeyPreview(previewText);
+        }
+
+        mDrawingHandler.cancelDismissKeyPreview(tracker);
+        final Key key = tracker.getKey();
+        // If key is invalid or IME is already closed, we must not show key preview.
+        // Trying to show key preview while root window is closed causes
+        // WindowManager.BadTokenException.
+        if (key == null) {
+            return;
+        }
+
+        final KeyDrawParams drawParams = mKeyDrawParams;
+        previewText.setTextColor(drawParams.mPreviewTextColor);
+        final Drawable background = previewText.getBackground();
+        if (background != null) {
+            background.setState(KEY_PREVIEW_BACKGROUND_DEFAULT_STATE);
+            background.setAlpha(PREVIEW_ALPHA);
+        }
+        final String label = key.isShiftedLetterActivated() ? key.mHintLabel : key.mLabel;
+        // What we show as preview should match what we show on a key top in onDraw().
+        if (label != null) {
+            // TODO Should take care of temporaryShiftLabel here.
+            previewText.setCompoundDrawables(null, null, null, null);
+            if (StringUtils.codePointCount(label) > 1) {
+                previewText.setTextSize(TypedValue.COMPLEX_UNIT_PX, drawParams.mLetterSize);
+                previewText.setTypeface(Typeface.DEFAULT_BOLD);
+            } else {
+                previewText.setTextSize(TypedValue.COMPLEX_UNIT_PX, drawParams.mPreviewTextSize);
+                previewText.setTypeface(key.selectTypeface(drawParams));
+            }
+            previewText.setText(label);
+        } else {
+            previewText.setCompoundDrawables(null, null, null,
+                    key.getPreviewIcon(keyboard.mIconsSet));
+            previewText.setText(null);
+        }
+
+        previewText.measure(
+                ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT);
+        final int keyDrawWidth = key.getDrawWidth();
+        final int previewWidth = previewText.getMeasuredWidth();
+        final int previewHeight = mKeyPreviewHeight;
+        // The width and height of visible part of the key preview background. The content marker
+        // of the background 9-patch have to cover the visible part of the background.
+        previewParams.mPreviewVisibleWidth = previewWidth - previewText.getPaddingLeft()
+                - previewText.getPaddingRight();
+        previewParams.mPreviewVisibleHeight = previewHeight - previewText.getPaddingTop()
+                - previewText.getPaddingBottom();
+        // The distance between the top edge of the parent key and the bottom of the visible part
+        // of the key preview background.
+        previewParams.mPreviewVisibleOffset = mKeyPreviewOffset - previewText.getPaddingBottom();
+        getLocationInWindow(mOriginCoords);
+        // The key preview is horizontally aligned with the center of the visible part of the
+        // parent key. If it doesn't fit in this {@link KeyboardView}, it is moved inward to fit and
+        // the left/right background is used if such background is specified.
+        final int statePosition;
+        int previewX = key.getDrawX() - (previewWidth - keyDrawWidth) / 2
+                + CoordinateUtils.x(mOriginCoords);
+        if (previewX < 0) {
+            previewX = 0;
+            statePosition = STATE_LEFT;
+        } else if (previewX > getWidth() - previewWidth) {
+            previewX = getWidth() - previewWidth;
+            statePosition = STATE_RIGHT;
+        } else {
+            statePosition = STATE_MIDDLE;
+        }
+        // The key preview is placed vertically above the top edge of the parent key with an
+        // arbitrary offset.
+        final int previewY = key.mY - previewHeight + mKeyPreviewOffset
+                + CoordinateUtils.y(mOriginCoords);
+
+        if (background != null) {
+            final int hasMoreKeys = (key.mMoreKeys != null) ? STATE_HAS_MOREKEYS : STATE_NORMAL;
+            background.setState(KEY_PREVIEW_BACKGROUND_STATE_TABLE[statePosition][hasMoreKeys]);
+        }
+        ViewLayoutUtils.placeViewAt(
+                previewText, previewX, previewY, previewWidth, previewHeight);
+        previewText.setVisibility(VISIBLE);
+    }
+
+    @Override
+    public void dismissKeyPreview(final PointerTracker tracker) {
+        mDrawingHandler.dismissKeyPreview(mKeyPreviewLingerTimeout, tracker);
+    }
+
+    @Override
+    public void showSlidingKeyInputPreview(final PointerTracker tracker) {
+        locatePreviewPlacerView();
+        mPreviewPlacerView.showSlidingKeyInputPreview(tracker);
+    }
+
+    @Override
+    public void dismissSlidingKeyInputPreview() {
+        mPreviewPlacerView.dismissSlidingKeyInputPreview();
+    }
+
+    public void setGesturePreviewMode(final boolean drawsGesturePreviewTrail,
+            final boolean drawsGestureFloatingPreviewText) {
+        mPreviewPlacerView.setGesturePreviewMode(
+                drawsGesturePreviewTrail, drawsGestureFloatingPreviewText);
+    }
+
+    public void showGestureFloatingPreviewText(final SuggestedWords suggestedWords) {
+        locatePreviewPlacerView();
+        mPreviewPlacerView.setGestureFloatingPreviewText(suggestedWords);
+    }
+
+    public void dismissGestureFloatingPreviewText() {
+        locatePreviewPlacerView();
+        mDrawingHandler.dismissGestureFloatingPreviewText(mGestureFloatingPreviewTextLingerTimeout);
+    }
+
+    public void showGesturePreviewTrail(final PointerTracker tracker) {
+        locatePreviewPlacerView();
+        mPreviewPlacerView.invalidatePointer(tracker);
+    }
+
     // Note that this method is called from a non-UI thread.
     public void setMainDictionaryAvailability(final boolean mainDictionaryAvailable) {
         PointerTracker.setMainDictionaryAvailability(mainDictionaryAvailable);
@@ -561,6 +916,7 @@
     @Override
     protected void onDetachedFromWindow() {
         super.onDetachedFromWindow();
+        mPreviewPlacerView.removeAllViews();
         // Notify the research logger that the keyboard view has been detached.  This is needed
         // to invalidate the reference of {@link MainKeyboardView} to null.
         if (ProductionFlag.IS_EXPERIMENTAL) {
@@ -568,12 +924,6 @@
         }
     }
 
-    @Override
-    public void cancelAllMessages() {
-        mKeyTimerHandler.cancelAllMessages();
-        super.cancelAllMessages();
-    }
-
     private boolean openMoreKeysKeyboardIfRequired(final Key parentKey,
             final PointerTracker tracker) {
         // Check if we have a popup layout specified first.
@@ -591,8 +941,7 @@
         return onLongPress(parentKey, tracker);
     }
 
-    // This default implementation returns a more keys panel.
-    protected MoreKeysPanel onCreateMoreKeysPanel(final Key parentKey) {
+    private MoreKeysPanel onCreateMoreKeysPanel(final Key parentKey) {
         if (parentKey.mMoreKeys == null) {
             return null;
         }
@@ -604,7 +953,8 @@
 
         final MoreKeysKeyboardView moreKeysKeyboardView =
                 (MoreKeysKeyboardView)container.findViewById(R.id.more_keys_keyboard_view);
-        final Keyboard moreKeysKeyboard = new MoreKeysKeyboard.Builder(container, parentKey, this)
+        final Keyboard moreKeysKeyboard = new MoreKeysKeyboard.Builder(
+                container, parentKey, this, mKeyPreviewDrawParams)
                 .build();
         moreKeysKeyboardView.setKeyboard(moreKeysKeyboard);
         container.measure(ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT);
@@ -613,14 +963,13 @@
     }
 
     /**
-     * Called when a key is long pressed. By default this will open more keys keyboard associated
-     * with this key.
+     * Called when a key is long pressed.
      * @param parentKey the key that was long pressed
      * @param tracker the pointer tracker which pressed the parent key
      * @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(final Key parentKey, final PointerTracker tracker) {
+    private boolean onLongPress(final Key parentKey, final PointerTracker tracker) {
         if (ProductionFlag.IS_EXPERIMENTAL) {
             ResearchLogger.mainKeyboardView_onLongPress();
         }
@@ -697,6 +1046,38 @@
         return PointerTracker.isAnyInSlidingKeyInput();
     }
 
+    @Override
+    public void onShowMoreKeysPanel(final MoreKeysPanel panel) {
+        if (isShowingMoreKeysPanel()) {
+            onDismissMoreKeysPanel();
+        }
+        mMoreKeysPanel = panel;
+        mPreviewPlacerView.addView(mMoreKeysPanel.getContainerView());
+    }
+
+    public boolean isShowingMoreKeysPanel() {
+        return (mMoreKeysPanel != null);
+    }
+
+    @Override
+    public void onCancelMoreKeysPanel() {
+        if (isShowingMoreKeysPanel()) {
+            mMoreKeysPanel.dismissMoreKeysPanel();
+        }
+        PointerTracker.dismissAllMoreKeysPanels();
+    }
+
+    @Override
+    public boolean onDismissMoreKeysPanel() {
+        dimEntireKeyboard(false /* dimmed */);
+        if (isShowingMoreKeysPanel()) {
+            mPreviewPlacerView.removeView(mMoreKeysPanel.getContainerView());
+            mMoreKeysPanel = null;
+            return true;
+        }
+        return false;
+    }
+
     public int getPointerCount() {
         return mOldPointerCount;
     }
@@ -846,25 +1227,20 @@
                 eventTag + eventTime + "," + id + "," + x + "," + y + "," + size + "," + pressure);
     }
 
+    public void cancelAllMessages() {
+        mKeyTimerHandler.cancelAllMessages();
+        mDrawingHandler.cancelAllMessages();
+    }
+
     @Override
     public void closing() {
+        dismissAllKeyPreviews();
+        cancelAllMessages();
         super.closing();
         onCancelMoreKeysPanel();
         mMoreKeysPanelCache.clear();
     }
 
-    @Override
-    public void onCancelMoreKeysPanel() {
-        super.onCancelMoreKeysPanel();
-        PointerTracker.dismissAllMoreKeysPanels();
-    }
-
-    @Override
-    public boolean onDismissMoreKeysPanel() {
-        dimEntireKeyboard(false /* dimmed */);
-        return super.onDismissMoreKeysPanel();
-    }
-
     /**
      * Receives hover events from the input framework.
      *
@@ -937,6 +1313,24 @@
         invalidateKey(mSpaceKey);
     }
 
+    public void dimEntireKeyboard(final boolean dimmed) {
+        final boolean needsRedrawing = mNeedsToDimEntireKeyboard != dimmed;
+        mNeedsToDimEntireKeyboard = dimmed;
+        if (needsRedrawing) {
+            invalidateAllKeys();
+        }
+    }
+
+    @Override
+    protected void onDraw(final Canvas canvas) {
+        super.onDraw(canvas);
+
+        // Overlay a dark rectangle to dim.
+        if (mNeedsToDimEntireKeyboard) {
+            canvas.drawRect(0, 0, getWidth(), getHeight(), mBackgroundDimAlphaPaint);
+        }
+    }
+
     @Override
     protected void onDrawKeyTopVisuals(final Key key, final Canvas canvas, final Paint paint,
             final KeyDrawParams params) {
diff --git a/java/src/com/android/inputmethod/keyboard/MoreKeysKeyboard.java b/java/src/com/android/inputmethod/keyboard/MoreKeysKeyboard.java
index 3826a39..5c15c4a 100644
--- a/java/src/com/android/inputmethod/keyboard/MoreKeysKeyboard.java
+++ b/java/src/com/android/inputmethod/keyboard/MoreKeysKeyboard.java
@@ -21,6 +21,7 @@
 import android.view.View;
 
 import com.android.inputmethod.annotations.UsedForTesting;
+import com.android.inputmethod.keyboard.internal.KeyPreviewDrawParams;
 import com.android.inputmethod.keyboard.internal.KeyboardBuilder;
 import com.android.inputmethod.keyboard.internal.KeyboardIconsSet;
 import com.android.inputmethod.keyboard.internal.KeyboardParams;
@@ -262,9 +263,11 @@
          * @param containerView the container of {@link MoreKeysKeyboardView}.
          * @param parentKey the {@link Key} that invokes more keys keyboard.
          * @param parentKeyboardView the {@link KeyboardView} that contains the parentKey.
+         * @param keyPreviewDrawParams the parameter to place key preview.
          */
         public Builder(final View containerView, final Key parentKey,
-                final KeyboardView parentKeyboardView) {
+                final MainKeyboardView parentKeyboardView,
+                final KeyPreviewDrawParams keyPreviewDrawParams) {
             super(containerView.getContext(), new MoreKeysKeyboardParams());
             final Keyboard parentKeyboard = parentKeyboardView.getKeyboard();
             load(parentKeyboard.mMoreKeysTemplate, parentKeyboard.mId);
@@ -285,8 +288,8 @@
                 // left/right/top paddings. The bottom paddings of both backgrounds don't need to
                 // be considered because the vertical positions of both backgrounds were already
                 // adjusted with their bottom paddings deducted.
-                width = parentKeyboardView.mKeyPreviewDrawParams.mPreviewVisibleWidth;
-                height = parentKeyboardView.mKeyPreviewDrawParams.mPreviewVisibleHeight
+                width = keyPreviewDrawParams.mPreviewVisibleWidth;
+                height = keyPreviewDrawParams.mPreviewVisibleHeight
                         + mParams.mVerticalGap;
             } else {
                 width = getMaxKeyWidth(parentKeyboardView, parentKey, mParams.mDefaultKeyWidth);
diff --git a/java/src/com/android/inputmethod/keyboard/MoreKeysKeyboardView.java b/java/src/com/android/inputmethod/keyboard/MoreKeysKeyboardView.java
index 8a5b7da..0d42ab2 100644
--- a/java/src/com/android/inputmethod/keyboard/MoreKeysKeyboardView.java
+++ b/java/src/com/android/inputmethod/keyboard/MoreKeysKeyboardView.java
@@ -53,7 +53,6 @@
         final Resources res = context.getResources();
         mKeyDetector = new MoreKeysDetector(
                 res.getDimension(R.dimen.more_keys_keyboard_slide_allowance));
-        setKeyPreviewPopupEnabled(false, 0);
     }
 
     @Override
@@ -76,13 +75,6 @@
     }
 
     @Override
-    public void setKeyPreviewPopupEnabled(final boolean previewEnabled, final int delay) {
-        // More keys 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
     public void showMoreKeysPanel(final View parentView, final Controller controller,
             final int pointX, final int pointY, final KeyboardActionListener listener) {
         mController = controller;
diff --git a/java/src/com/android/inputmethod/keyboard/PointerTracker.java b/java/src/com/android/inputmethod/keyboard/PointerTracker.java
index 036372c..469076f 100644
--- a/java/src/com/android/inputmethod/keyboard/PointerTracker.java
+++ b/java/src/com/android/inputmethod/keyboard/PointerTracker.java
@@ -83,7 +83,7 @@
         public void dismissKeyPreview(PointerTracker tracker);
         public void showSlidingKeyInputPreview(PointerTracker tracker);
         public void dismissSlidingKeyInputPreview();
-        public void showGesturePreviewTrail(PointerTracker tracker, boolean isOldestTracker);
+        public void showGesturePreviewTrail(PointerTracker tracker);
     }
 
     public interface TimerProxy {
@@ -709,8 +709,8 @@
         return sPointerTrackerQueue.size();
     }
 
-    private static boolean isOldestTrackerInQueue(final PointerTracker tracker) {
-        return sPointerTrackerQueue.getOldestElement() == tracker;
+    public boolean isOldestTrackerInQueue() {
+        return sPointerTrackerQueue.getOldestElement() == this;
     }
 
     private void mayStartBatchInput(final Key key) {
@@ -732,7 +732,7 @@
             dismissAllMoreKeysPanels();
         }
         mTimerProxy.cancelLongPressTimer();
-        mDrawingProxy.showGesturePreviewTrail(this, isOldestTrackerInQueue(this));
+        mDrawingProxy.showGesturePreviewTrail(this);
     }
 
     public void updateBatchInputByTimer(final long eventTime) {
@@ -748,7 +748,7 @@
         if (mIsTrackingCanceled) {
             return;
         }
-        mDrawingProxy.showGesturePreviewTrail(this, isOldestTrackerInQueue(this));
+        mDrawingProxy.showGesturePreviewTrail(this);
     }
 
     private void updateBatchInput(final long eventTime) {
@@ -789,7 +789,7 @@
         if (mIsTrackingCanceled) {
             return;
         }
-        mDrawingProxy.showGesturePreviewTrail(this, isOldestTrackerInQueue(this));
+        mDrawingProxy.showGesturePreviewTrail(this);
     }
 
     private void cancelBatchInput() {
diff --git a/java/src/com/android/inputmethod/keyboard/internal/AbstractDrawingPreview.java b/java/src/com/android/inputmethod/keyboard/internal/AbstractDrawingPreview.java
index 8a3f064..cf47b14 100644
--- a/java/src/com/android/inputmethod/keyboard/internal/AbstractDrawingPreview.java
+++ b/java/src/com/android/inputmethod/keyboard/internal/AbstractDrawingPreview.java
@@ -17,6 +17,7 @@
 package com.android.inputmethod.keyboard.internal;
 
 import android.graphics.Canvas;
+import android.view.View;
 
 import com.android.inputmethod.keyboard.PointerTracker;
 
@@ -25,9 +26,18 @@
  * GestureFloatingPrevewText, GestureTrail, and SlidingKeyInputPreview.
  */
 public abstract class AbstractDrawingPreview {
+    private final View mDrawingView;
     private boolean mPreviewEnabled;
 
-    public void setPreviewEnabled(final boolean enabled) {
+    protected AbstractDrawingPreview(final View drawingView) {
+        mDrawingView = drawingView;
+    }
+
+    public final View getDrawingView() {
+        return mDrawingView;
+    }
+
+    public final void setPreviewEnabled(final boolean enabled) {
         mPreviewEnabled = enabled;
     }
 
@@ -35,15 +45,23 @@
         return mPreviewEnabled;
     }
 
+    public void setKeyboardGeometry(final int[] originCoords, final int width, final int height) {
+        // Default implementation is empty.
+    }
+
+    public void onDetachFromWindow() {
+        // Default implementation is empty.
+    }
+
     /**
      * Draws the preview
      * @param canvas The canvas where the preview is drawn.
      */
-    public abstract void onDraw(final Canvas canvas);
+    public abstract void drawPreview(final Canvas canvas);
 
     /**
      * Set the position of the preview.
-     * @param pt The new location of the preview is based on the points in PointerTracker pt.
+     * @param tracker The new location of the preview is based on the points in PointerTracker.
      */
-    public abstract void setPreviewPosition(final PointerTracker pt);
+    public abstract void setPreviewPosition(final PointerTracker tracker);
 }
diff --git a/java/src/com/android/inputmethod/keyboard/internal/GestureFloatingPreviewText.java b/java/src/com/android/inputmethod/keyboard/internal/GestureFloatingPreviewText.java
index aed23a4..e21f86d 100644
--- a/java/src/com/android/inputmethod/keyboard/internal/GestureFloatingPreviewText.java
+++ b/java/src/com/android/inputmethod/keyboard/internal/GestureFloatingPreviewText.java
@@ -16,7 +16,6 @@
 
 package com.android.inputmethod.keyboard.internal;
 
-import android.content.Context;
 import android.content.res.TypedArray;
 import android.graphics.Canvas;
 import android.graphics.Paint;
@@ -24,6 +23,7 @@
 import android.graphics.Rect;
 import android.graphics.RectF;
 import android.text.TextUtils;
+import android.view.View;
 
 import com.android.inputmethod.keyboard.PointerTracker;
 import com.android.inputmethod.latin.CoordinateUtils;
@@ -34,6 +34,14 @@
 /**
  * The class for single gesture preview text. The class for multiple gesture preview text will be
  * derived from it.
+ *
+ * @attr ref R.styleable#KeyboardView_gestureFloatingPreviewTextSize
+ * @attr ref R.styleable#KeyboardView_gestureFloatingPreviewTextColor
+ * @attr ref R.styleable#KeyboardView_gestureFloatingPreviewTextOffset
+ * @attr ref R.styleable#KeyboardView_gestureFloatingPreviewColor
+ * @attr ref R.styleable#KeyboardView_gestureFloatingPreviewHorizontalPadding
+ * @attr ref R.styleable#KeyboardView_gestureFloatingPreviewVerticalPadding
+ * @attr ref R.styleable#KeyboardView_gestureFloatingPreviewRoundRadius
  */
 public class GestureFloatingPreviewText extends AbstractDrawingPreview {
     private static final class GesturePreviewTextParams {
@@ -49,21 +57,21 @@
 
         private static final char[] TEXT_HEIGHT_REFERENCE_CHAR = { 'M' };
 
-        public GesturePreviewTextParams(final TypedArray keyboardViewAttr) {
-            mGesturePreviewTextSize = keyboardViewAttr.getDimensionPixelSize(
-                    R.styleable.KeyboardView_gestureFloatingPreviewTextSize, 0);
-            mGesturePreviewTextColor = keyboardViewAttr.getColor(
-                    R.styleable.KeyboardView_gestureFloatingPreviewTextColor, 0);
-            mGesturePreviewTextOffset = keyboardViewAttr.getDimensionPixelOffset(
-                    R.styleable.KeyboardView_gestureFloatingPreviewTextOffset, 0);
-            mGesturePreviewColor = keyboardViewAttr.getColor(
-                    R.styleable.KeyboardView_gestureFloatingPreviewColor, 0);
-            mGesturePreviewHorizontalPadding = keyboardViewAttr.getDimension(
-                    R.styleable.KeyboardView_gestureFloatingPreviewHorizontalPadding, 0.0f);
-            mGesturePreviewVerticalPadding = keyboardViewAttr.getDimension(
-                    R.styleable.KeyboardView_gestureFloatingPreviewVerticalPadding, 0.0f);
-            mGesturePreviewRoundRadius = keyboardViewAttr.getDimension(
-                    R.styleable.KeyboardView_gestureFloatingPreviewRoundRadius, 0.0f);
+        public GesturePreviewTextParams(final TypedArray mainKeyboardViewAttr) {
+            mGesturePreviewTextSize = mainKeyboardViewAttr.getDimensionPixelSize(
+                    R.styleable.MainKeyboardView_gestureFloatingPreviewTextSize, 0);
+            mGesturePreviewTextColor = mainKeyboardViewAttr.getColor(
+                    R.styleable.MainKeyboardView_gestureFloatingPreviewTextColor, 0);
+            mGesturePreviewTextOffset = mainKeyboardViewAttr.getDimensionPixelOffset(
+                    R.styleable.MainKeyboardView_gestureFloatingPreviewTextOffset, 0);
+            mGesturePreviewColor = mainKeyboardViewAttr.getColor(
+                    R.styleable.MainKeyboardView_gestureFloatingPreviewColor, 0);
+            mGesturePreviewHorizontalPadding = mainKeyboardViewAttr.getDimension(
+                    R.styleable.MainKeyboardView_gestureFloatingPreviewHorizontalPadding, 0.0f);
+            mGesturePreviewVerticalPadding = mainKeyboardViewAttr.getDimension(
+                    R.styleable.MainKeyboardView_gestureFloatingPreviewVerticalPadding, 0.0f);
+            mGesturePreviewRoundRadius = mainKeyboardViewAttr.getDimension(
+                    R.styleable.MainKeyboardView_gestureFloatingPreviewRoundRadius, 0.0f);
 
             final Paint textPaint = new Paint();
             textPaint.setAntiAlias(true);
@@ -90,16 +98,18 @@
             PREVIEW_TEXT_ARRAY_CAPACITY);
 
     protected SuggestedWords mSuggestedWords = SuggestedWords.EMPTY;
-    protected final Context mContext;
     public final int[] mLastPointerCoords = CoordinateUtils.newInstance();
 
-    public GestureFloatingPreviewText(final TypedArray typedArray, final Context context) {
+    public GestureFloatingPreviewText(final View drawingView, final TypedArray typedArray) {
+        super(drawingView);
         mParams = new GesturePreviewTextParams(typedArray);
         mHighlightedWordIndex = 0;
-        mContext = context;
     }
 
     public void setSuggetedWords(final SuggestedWords suggestedWords) {
+        if (!isPreviewEnabled()) {
+            return;
+        }
         mSuggestedWords = suggestedWords;
         updatePreviewPosition();
     }
@@ -112,8 +122,13 @@
     }
 
     @Override
-    public void setPreviewPosition(final PointerTracker pt) {
-        pt.getLastCoordinates(mLastPointerCoords);
+    public void setPreviewPosition(final PointerTracker tracker) {
+        final boolean needsToUpdateLastPointer =
+                tracker.isOldestTrackerInQueue() && isPreviewEnabled();
+        if (!needsToUpdateLastPointer) {
+            return;
+        }
+        tracker.getLastCoordinates(mLastPointerCoords);
         updatePreviewPosition();
     }
 
@@ -122,7 +137,7 @@
      * @param canvas The canvas where preview text is drawn.
      */
     @Override
-    public void onDraw(final Canvas canvas) {
+    public void drawPreview(final Canvas canvas) {
         if (!isPreviewEnabled() || mSuggestedWords.isEmpty()
                 || TextUtils.isEmpty(mSuggestedWords.getWord(0))) {
             return;
@@ -156,7 +171,7 @@
         final float rectWidth = textWidth + hPad * 2.0f;
         final float rectHeight = textHeight + vPad * 2.0f;
 
-        final int displayWidth = mContext.getResources().getDisplayMetrics().widthPixels;
+        final int displayWidth = getDrawingView().getResources().getDisplayMetrics().widthPixels;
         final float rectX = Math.min(
                 Math.max(CoordinateUtils.x(mLastPointerCoords) - rectWidth / 2.0f, 0.0f),
                 displayWidth - rectWidth);
@@ -168,5 +183,7 @@
         final int textY = (int)(rectY + vPad) + textHeight;
         mPreviewTextXArray.add(0, textX);
         mPreviewTextYArray.add(0, textY);
+        // TODO: Should narrow the invalidate region.
+        getDrawingView().invalidate();
     }
 }
diff --git a/java/src/com/android/inputmethod/keyboard/internal/GesturePreviewTrail.java b/java/src/com/android/inputmethod/keyboard/internal/GesturePreviewTrail.java
index 9f6945d..a74c323 100644
--- a/java/src/com/android/inputmethod/keyboard/internal/GesturePreviewTrail.java
+++ b/java/src/com/android/inputmethod/keyboard/internal/GesturePreviewTrail.java
@@ -25,6 +25,13 @@
 import com.android.inputmethod.latin.R;
 import com.android.inputmethod.latin.ResizableIntArray;
 
+/*
+ * @attr ref R.styleable#MainKeyboardView_gesturePreviewTrailFadeoutStartDelay
+ * @attr ref R.styleable#MainKeyboardView_gesturePreviewTrailFadeoutDuration
+ * @attr ref R.styleable#MainKeyboardView_gesturePreviewTrailUpdateInterval
+ * @attr ref R.styleable#MainKeyboardView_gesturePreviewTrailColor
+ * @attr ref R.styleable#MainKeyboardView_gesturePreviewTrailWidth
+ */
 final class GesturePreviewTrail {
     private static final int DEFAULT_CAPACITY = GestureStrokeWithPreviewPoints.PREVIEW_CAPACITY;
 
@@ -46,20 +53,20 @@
 
         public final int mTrailLingerDuration;
 
-        public Params(final TypedArray keyboardViewAttr) {
-            mTrailColor = keyboardViewAttr.getColor(
-                    R.styleable.KeyboardView_gesturePreviewTrailColor, 0);
-            mTrailStartWidth = keyboardViewAttr.getDimension(
-                    R.styleable.KeyboardView_gesturePreviewTrailStartWidth, 0.0f);
-            mTrailEndWidth = keyboardViewAttr.getDimension(
-                    R.styleable.KeyboardView_gesturePreviewTrailEndWidth, 0.0f);
-            mFadeoutStartDelay = keyboardViewAttr.getInt(
-                    R.styleable.KeyboardView_gesturePreviewTrailFadeoutStartDelay, 0);
-            mFadeoutDuration = keyboardViewAttr.getInt(
-                    R.styleable.KeyboardView_gesturePreviewTrailFadeoutDuration, 0);
+        public Params(final TypedArray mainKeyboardViewAttr) {
+            mTrailColor = mainKeyboardViewAttr.getColor(
+                    R.styleable.MainKeyboardView_gesturePreviewTrailColor, 0);
+            mTrailStartWidth = mainKeyboardViewAttr.getDimension(
+                    R.styleable.MainKeyboardView_gesturePreviewTrailStartWidth, 0.0f);
+            mTrailEndWidth = mainKeyboardViewAttr.getDimension(
+                    R.styleable.MainKeyboardView_gesturePreviewTrailEndWidth, 0.0f);
+            mFadeoutStartDelay = mainKeyboardViewAttr.getInt(
+                    R.styleable.MainKeyboardView_gesturePreviewTrailFadeoutStartDelay, 0);
+            mFadeoutDuration = mainKeyboardViewAttr.getInt(
+                    R.styleable.MainKeyboardView_gesturePreviewTrailFadeoutDuration, 0);
             mTrailLingerDuration = mFadeoutStartDelay + mFadeoutDuration;
-            mUpdateInterval = keyboardViewAttr.getInt(
-                    R.styleable.KeyboardView_gesturePreviewTrailUpdateInterval, 0);
+            mUpdateInterval = mainKeyboardViewAttr.getInt(
+                    R.styleable.MainKeyboardView_gesturePreviewTrailUpdateInterval, 0);
         }
     }
 
diff --git a/java/src/com/android/inputmethod/keyboard/internal/PreviewPlacerView.java b/java/src/com/android/inputmethod/keyboard/internal/PreviewPlacerView.java
index 7c87467..4b0d4fb 100644
--- a/java/src/com/android/inputmethod/keyboard/internal/PreviewPlacerView.java
+++ b/java/src/com/android/inputmethod/keyboard/internal/PreviewPlacerView.java
@@ -41,7 +41,7 @@
 public final class PreviewPlacerView extends RelativeLayout {
     private final int[] mKeyboardViewOrigin = CoordinateUtils.newInstance();
 
-    // TODO: Consolidate gesture preview trail with {@link KeyboardView}
+    // TODO: Separate gesture preview trail drawing code into separate class.
     private final SparseArray<GesturePreviewTrail> mGesturePreviewTrails =
             CollectionUtils.newSparseArray();
     private final Params mGesturePreviewTrailParams;
@@ -55,6 +55,7 @@
     private final Rect mOffscreenSrcRect = new Rect();
     private final Rect mDirtyRect = new Rect();
     private final Rect mGesturePreviewTrailBoundsRect = new Rect(); // per trail
+    // TODO: Move these AbstractDrawingPvreiew objects to MainKeyboardView.
     private final GestureFloatingPreviewText mGestureFloatingPreviewText;
     private boolean mShowSlidingKeyInputPreview;
     private final int[] mRubberBandFrom = CoordinateUtils.newInstance();
@@ -100,13 +101,13 @@
         super(context);
         setWillNotDraw(false);
 
-        final TypedArray keyboardViewAttr = context.obtainStyledAttributes(
-                attrs, R.styleable.KeyboardView, defStyle, R.style.KeyboardView);
+        final TypedArray mainKeyboardViewAttr = context.obtainStyledAttributes(
+                attrs, R.styleable.MainKeyboardView, defStyle, R.style.MainKeyboardView);
         // TODO: mGestureFloatingPreviewText could be an instance of GestureFloatingPreviewText or
         // MultiGesturePreviewText, depending on the user's choice in the settings.
-        mGestureFloatingPreviewText = new GestureFloatingPreviewText(keyboardViewAttr, context);
-        mGesturePreviewTrailParams = new Params(keyboardViewAttr);
-        keyboardViewAttr.recycle();
+        mGestureFloatingPreviewText = new GestureFloatingPreviewText(this, mainKeyboardViewAttr);
+        mGesturePreviewTrailParams = new Params(mainKeyboardViewAttr);
+        mainKeyboardViewAttr.recycle();
 
         mDrawingHandler = new DrawingHandler(this, mGesturePreviewTrailParams);
 
@@ -120,11 +121,14 @@
         setLayerType(LAYER_TYPE_HARDWARE, layerPaint);
     }
 
-    public void setKeyboardViewGeometry(final int[] originCoords, final int w, final int h) {
+    public void setKeyboardViewGeometry(final int[] originCoords, final int width,
+            final int height) {
         CoordinateUtils.copy(mKeyboardViewOrigin, originCoords);
-        mOffscreenOffsetY = (int)(h * GestureStroke.EXTRA_GESTURE_TRAIL_AREA_ABOVE_KEYBOARD_RATIO);
-        mOffscreenWidth = w;
-        mOffscreenHeight = mOffscreenOffsetY + h;
+        mGestureFloatingPreviewText.setKeyboardGeometry(originCoords, width, height);
+        mOffscreenOffsetY = (int)(
+                height * GestureStroke.EXTRA_GESTURE_TRAIL_AREA_ABOVE_KEYBOARD_RATIO);
+        mOffscreenWidth = width;
+        mOffscreenHeight = mOffscreenOffsetY + height;
     }
 
     public void setGesturePreviewMode(final boolean drawsGesturePreviewTrail,
@@ -133,12 +137,8 @@
         mGestureFloatingPreviewText.setPreviewEnabled(drawsGestureFloatingPreviewText);
     }
 
-    public void invalidatePointer(final PointerTracker tracker, final boolean isOldestTracker) {
-        final boolean needsToUpdateLastPointer =
-                isOldestTracker && mGestureFloatingPreviewText.isPreviewEnabled();
-        if (needsToUpdateLastPointer) {
-            mGestureFloatingPreviewText.setPreviewPosition(tracker);
-        }
+    public void invalidatePointer(final PointerTracker tracker) {
+        mGestureFloatingPreviewText.setPreviewPosition(tracker);
 
         if (mDrawsGesturePreviewTrail) {
             GesturePreviewTrail trail;
@@ -150,10 +150,8 @@
                 }
             }
             trail.addStroke(tracker.getGestureStrokeWithPreviewPoints(), tracker.getDownTime());
-        }
 
-        // TODO: Should narrow the invalidate region.
-        if (mDrawsGesturePreviewTrail || needsToUpdateLastPointer) {
+            // TODO: Should narrow the invalidate region.
             invalidate();
         }
     }
@@ -175,6 +173,7 @@
 
     @Override
     protected void onDetachedFromWindow() {
+        mGestureFloatingPreviewText.onDetachFromWindow();
         freeOffscreenBuffer();
     }
 
@@ -220,7 +219,7 @@
                 // rectangle on the canvas.
             }
         }
-        mGestureFloatingPreviewText.onDraw(canvas);
+        mGestureFloatingPreviewText.drawPreview(canvas);
         if (mShowSlidingKeyInputPreview) {
             drawSlidingKeyInputPreview(canvas);
         }
@@ -254,9 +253,7 @@
     }
 
     public void setGestureFloatingPreviewText(final SuggestedWords suggestedWords) {
-        if (!mGestureFloatingPreviewText.isPreviewEnabled()) return;
         mGestureFloatingPreviewText.setSuggetedWords(suggestedWords);
-        invalidate();
     }
 
     private void drawSlidingKeyInputPreview(final Canvas canvas) {
diff --git a/java/src/com/android/inputmethod/latin/BinaryDictionaryFileDumper.java b/java/src/com/android/inputmethod/latin/BinaryDictionaryFileDumper.java
index 5eab292..d725b9f 100644
--- a/java/src/com/android/inputmethod/latin/BinaryDictionaryFileDumper.java
+++ b/java/src/com/android/inputmethod/latin/BinaryDictionaryFileDumper.java
@@ -25,6 +25,7 @@
 import android.util.Log;
 
 import java.io.BufferedInputStream;
+import java.io.BufferedOutputStream;
 import java.io.File;
 import java.io.FileNotFoundException;
 import java.io.FileOutputStream;
@@ -162,9 +163,9 @@
             InputStream inputStream = null;
             InputStream uncompressedStream = null;
             InputStream decryptedStream = null;
-            BufferedInputStream bufferedStream = null;
+            BufferedInputStream bufferedInputStream = null;
             File outputFile = null;
-            FileOutputStream outputStream = null;
+            BufferedOutputStream bufferedOutputStream = null;
             AssetFileDescriptor afd = null;
             final Uri wordListUri = wordListUriBuilder.build();
             try {
@@ -178,7 +179,6 @@
                 // Just to be sure, delete the file. This may fail silently, and return false: this
                 // is the right thing to do, as we just want to continue anyway.
                 outputFile.delete();
-                outputStream = new FileOutputStream(outputFile);
                 // Get the appropriate decryption method for this try
                 switch (mode) {
                     case COMPRESSED_CRYPTED_COMPRESSED:
@@ -206,10 +206,11 @@
                         inputStream = originalSourceStream;
                         break;
                 }
-                bufferedStream = new BufferedInputStream(inputStream);
-                checkMagicAndCopyFileTo(bufferedStream, outputStream);
-                outputStream.flush();
-                outputStream.close();
+                bufferedInputStream = new BufferedInputStream(inputStream);
+                bufferedOutputStream = new BufferedOutputStream(new FileOutputStream(outputFile));
+                checkMagicAndCopyFileTo(bufferedInputStream, bufferedOutputStream);
+                bufferedOutputStream.flush();
+                bufferedOutputStream.close();
                 final File finalFile = new File(finalFileName);
                 finalFile.delete();
                 if (!outputFile.renameTo(finalFile)) {
@@ -241,12 +242,12 @@
                     if (null != inputStream) inputStream.close();
                     if (null != uncompressedStream) uncompressedStream.close();
                     if (null != decryptedStream) decryptedStream.close();
-                    if (null != bufferedStream) bufferedStream.close();
+                    if (null != bufferedInputStream) bufferedInputStream.close();
                 } catch (Exception e) {
                     Log.e(TAG, "Exception while closing a file descriptor : " + e);
                 }
                 try {
-                    if (null != outputStream) outputStream.close();
+                    if (null != bufferedOutputStream) bufferedOutputStream.close();
                 } catch (Exception e) {
                     Log.e(TAG, "Exception while closing a file : " + e);
                 }
@@ -301,9 +302,8 @@
      * @param input the stream to be copied.
      * @param output an output stream to copy the data to.
      */
-    // TODO: make output a BufferedOutputStream
-    private static void checkMagicAndCopyFileTo(final BufferedInputStream input,
-            final FileOutputStream output) throws FileNotFoundException, IOException {
+    public static void checkMagicAndCopyFileTo(final BufferedInputStream input,
+            final BufferedOutputStream output) throws FileNotFoundException, IOException {
         // Check the magic number
         final int length = MAGIC_NUMBER_VERSION_2.length;
         final byte[] magicNumberBuffer = new byte[length];
diff --git a/java/src/com/android/inputmethod/latin/BinaryDictionaryGetter.java b/java/src/com/android/inputmethod/latin/BinaryDictionaryGetter.java
index 83dabbe..ecb63e8 100644
--- a/java/src/com/android/inputmethod/latin/BinaryDictionaryGetter.java
+++ b/java/src/com/android/inputmethod/latin/BinaryDictionaryGetter.java
@@ -56,7 +56,7 @@
     private static final String COMMON_PREFERENCES_NAME = "LatinImeDictPrefs";
 
     // Name of the category for the main dictionary
-    private static final String MAIN_DICTIONARY_CATEGORY = "main";
+    public static final String MAIN_DICTIONARY_CATEGORY = "main";
     public static final String ID_CATEGORY_SEPARATOR = ":";
 
     // The key considered to read the version attribute in a dictionary file.
diff --git a/java/src/com/android/inputmethod/latin/DebugSettings.java b/java/src/com/android/inputmethod/latin/DebugSettings.java
index 9468071..ad52adf 100644
--- a/java/src/com/android/inputmethod/latin/DebugSettings.java
+++ b/java/src/com/android/inputmethod/latin/DebugSettings.java
@@ -75,7 +75,9 @@
                     new Preference.OnPreferenceClickListener() {
                         @Override
                         public boolean onPreferenceClick(final Preference arg0) {
-                            // TODO: actually read the dictionary
+                            ExternalDictionaryGetterForDebug.chooseAndInstallDictionary(
+                                    getActivity());
+                            mServiceNeedsRestart = true;
                             return true;
                         }
                     });
diff --git a/java/src/com/android/inputmethod/latin/ExternalDictionaryGetterForDebug.java b/java/src/com/android/inputmethod/latin/ExternalDictionaryGetterForDebug.java
new file mode 100644
index 0000000..03e8763
--- /dev/null
+++ b/java/src/com/android/inputmethod/latin/ExternalDictionaryGetterForDebug.java
@@ -0,0 +1,181 @@
+/*
+ * Copyright (C) 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+
+package com.android.inputmethod.latin;
+
+import android.app.AlertDialog;
+import android.content.Context;
+import android.content.DialogInterface;
+import android.content.DialogInterface.OnClickListener;
+import android.os.Environment;
+import android.util.Log;
+
+import com.android.inputmethod.latin.makedict.BinaryDictIOUtils;
+import com.android.inputmethod.latin.makedict.FormatSpec.FileHeader;
+import com.android.inputmethod.latin.makedict.UnsupportedFormatException;
+
+import java.io.BufferedInputStream;
+import java.io.BufferedOutputStream;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Locale;
+
+/**
+ * A class to read a local file as a dictionary for debugging purposes.
+ */
+public class ExternalDictionaryGetterForDebug {
+    private static final String SOURCE_FOLDER = Environment.getExternalStorageDirectory().getPath()
+            + "/Download";
+    private static final String DICTIONARY_LOCALE_ATTRIBUTE = "locale";
+
+    private static FileHeader getDictionaryFileHeaderOrNull(final File file) {
+        try {
+            final FileHeader header = BinaryDictIOUtils.getDictionaryFileHeader(file);
+            return header;
+        } catch (UnsupportedFormatException e) {
+            return null;
+        } catch (IOException e) {
+            return null;
+        }
+    }
+
+    private static String[] findDictionariesInTheDownloadedFolder() {
+        final File[] files = new File(SOURCE_FOLDER).listFiles();
+        final ArrayList<String> eligibleList = CollectionUtils.newArrayList();
+        for (File f : files) {
+            final FileHeader header = getDictionaryFileHeaderOrNull(f);
+            if (null == header) continue;
+            eligibleList.add(f.getName());
+        }
+        return eligibleList.toArray(new String[0]);
+    }
+
+    public static void chooseAndInstallDictionary(final Context context) {
+        final String[] fileNames = findDictionariesInTheDownloadedFolder();
+        if (0 == fileNames.length) {
+            showNoFileDialog(context);
+        } else if (1 == fileNames.length) {
+            askInstallFile(context, fileNames[0]);
+        } else {
+            showChooseFileDialog(context, fileNames);
+        }
+    }
+
+    private static void showNoFileDialog(final Context context) {
+        new AlertDialog.Builder(context)
+                .setMessage(R.string.read_external_dictionary_no_files_message)
+                .setPositiveButton(android.R.string.ok, new OnClickListener() {
+                    @Override
+                    public void onClick(final DialogInterface dialog, final int which) {
+                        dialog.dismiss();
+                    }
+                }).create().show();
+    }
+
+    private static void showChooseFileDialog(final Context context, final String[] fileNames) {
+        final AlertDialog.Builder builder = new AlertDialog.Builder(context);
+        builder.setTitle(R.string.read_external_dictionary_multiple_files_title)
+                .setItems(fileNames, new OnClickListener() {
+                    @Override
+                    public void onClick(final DialogInterface dialog, final int which) {
+                        askInstallFile(context, fileNames[which]);
+                    }
+                })
+                .create().show();
+    }
+
+    private static void askInstallFile(final Context context, final String fileName) {
+        final File file = new File(SOURCE_FOLDER, fileName.toString());
+        final FileHeader header = getDictionaryFileHeaderOrNull(file);
+        final StringBuilder message = new StringBuilder();
+        final String locale =
+                header.mDictionaryOptions.mAttributes.get(DICTIONARY_LOCALE_ATTRIBUTE);
+        for (String key : header.mDictionaryOptions.mAttributes.keySet()) {
+            message.append(key + " = " + header.mDictionaryOptions.mAttributes.get(key));
+            message.append("\n");
+        }
+        final String languageName = LocaleUtils.constructLocaleFromString(locale)
+                .getDisplayName(Locale.getDefault());
+        final String title = String.format(
+                context.getString(R.string.read_external_dictionary_confirm_install_message),
+                languageName);
+        new AlertDialog.Builder(context)
+                .setTitle(title)
+                .setMessage(message)
+                .setNegativeButton(android.R.string.cancel, new OnClickListener() {
+                    @Override
+                    public void onClick(final DialogInterface dialog, final int which) {
+                        dialog.dismiss();
+                    }
+                }).setPositiveButton(android.R.string.ok, new OnClickListener() {
+                    @Override
+                    public void onClick(final DialogInterface dialog, final int which) {
+                        installFile(context, file, header);
+                        dialog.dismiss();
+                    }
+                }).create().show();
+    }
+
+    private static void installFile(final Context context, final File file,
+            final FileHeader header) {
+        BufferedOutputStream outputStream = null;
+        File tempFile = null;
+        try {
+            final String locale =
+                    header.mDictionaryOptions.mAttributes.get(DICTIONARY_LOCALE_ATTRIBUTE);
+            // Create the id for a main dictionary for this locale
+            final String id = BinaryDictionaryGetter.MAIN_DICTIONARY_CATEGORY
+                    + BinaryDictionaryGetter.ID_CATEGORY_SEPARATOR + locale;
+            final String finalFileName =
+                    BinaryDictionaryGetter.getCacheFileName(id, locale, context);
+            final String tempFileName = BinaryDictionaryGetter.getTempFileName(id, context);
+            tempFile = new File(tempFileName);
+            tempFile.delete();
+            outputStream = new BufferedOutputStream(new FileOutputStream(tempFile));
+            final BufferedInputStream bufferedStream = new BufferedInputStream(
+                    new FileInputStream(file));
+            BinaryDictionaryFileDumper.checkMagicAndCopyFileTo(bufferedStream, outputStream);
+            outputStream.flush();
+            final File finalFile = new File(finalFileName);
+            finalFile.delete();
+            if (!tempFile.renameTo(finalFile)) {
+                throw new IOException("Can't move the file to its final name");
+            }
+        } catch (IOException e) {
+            // There was an error: show a dialog
+            new AlertDialog.Builder(context)
+                    .setTitle(R.string.error)
+                    .setMessage(e.toString())
+                    .setPositiveButton(android.R.string.ok, new OnClickListener() {
+                        @Override
+                        public void onClick(final DialogInterface dialog, final int which) {
+                            dialog.dismiss();
+                        }
+                    }).create().show();
+            return;
+        } finally {
+            try {
+                if (null != outputStream) outputStream.close();
+                if (null != tempFile) tempFile.delete();
+            } catch (IOException e) {
+                // Don't do anything
+            }
+        }
+    }
+}
diff --git a/java/src/com/android/inputmethod/latin/LatinIME.java b/java/src/com/android/inputmethod/latin/LatinIME.java
index d9e63da..75bf3f2 100644
--- a/java/src/com/android/inputmethod/latin/LatinIME.java
+++ b/java/src/com/android/inputmethod/latin/LatinIME.java
@@ -1617,7 +1617,7 @@
     private void showGesturePreviewAndSuggestionStrip(final SuggestedWords suggestedWords,
             final boolean dismissGestureFloatingPreviewText) {
         showSuggestionStrip(suggestedWords, null);
-        final KeyboardView mainKeyboardView = mKeyboardSwitcher.getMainKeyboardView();
+        final MainKeyboardView mainKeyboardView = mKeyboardSwitcher.getMainKeyboardView();
         mainKeyboardView.showGestureFloatingPreviewText(suggestedWords);
         if (dismissGestureFloatingPreviewText) {
             mainKeyboardView.dismissGestureFloatingPreviewText();
diff --git a/java/src/com/android/inputmethod/latin/makedict/FormatSpec.java b/java/src/com/android/inputmethod/latin/makedict/FormatSpec.java
index 705f664..d55fe1c 100644
--- a/java/src/com/android/inputmethod/latin/makedict/FormatSpec.java
+++ b/java/src/com/android/inputmethod/latin/makedict/FormatSpec.java
@@ -252,7 +252,7 @@
     /**
      * Class representing file header.
      */
-    static final class FileHeader {
+    public static final class FileHeader {
         public final int mHeaderSize;
         public final DictionaryOptions mDictionaryOptions;
         public final FormatOptions mFormatOptions;
diff --git a/java/src/com/android/inputmethod/latin/suggestions/SuggestionStripView.java b/java/src/com/android/inputmethod/latin/suggestions/SuggestionStripView.java
index 14bb95b..5b440c3 100644
--- a/java/src/com/android/inputmethod/latin/suggestions/SuggestionStripView.java
+++ b/java/src/com/android/inputmethod/latin/suggestions/SuggestionStripView.java
@@ -52,7 +52,7 @@
 import com.android.inputmethod.keyboard.Keyboard;
 import com.android.inputmethod.keyboard.KeyboardActionListener;
 import com.android.inputmethod.keyboard.KeyboardSwitcher;
-import com.android.inputmethod.keyboard.KeyboardView;
+import com.android.inputmethod.keyboard.MainKeyboardView;
 import com.android.inputmethod.keyboard.MoreKeysPanel;
 import com.android.inputmethod.keyboard.ViewLayoutUtils;
 import com.android.inputmethod.latin.AutoCorrection;
@@ -81,7 +81,7 @@
     static final boolean DBG = LatinImeLogger.sDBG;
 
     private final ViewGroup mSuggestionsStrip;
-    KeyboardView mKeyboardView;
+    MainKeyboardView mMainKeyboardView;
 
     private final View mMoreSuggestionsContainer;
     private final MoreSuggestionsView mMoreSuggestionsView;
@@ -610,7 +610,7 @@
      */
     public void setListener(final Listener listener, final View inputView) {
         mListener = listener;
-        mKeyboardView = (KeyboardView)inputView.findViewById(R.id.keyboard_view);
+        mMainKeyboardView = (MainKeyboardView)inputView.findViewById(R.id.keyboard_view);
     }
 
     public void setSuggestions(final SuggestedWords suggestedWords) {
@@ -676,13 +676,13 @@
             new MoreKeysPanel.Controller() {
         @Override
         public boolean onDismissMoreKeysPanel() {
-            mKeyboardView.dimEntireKeyboard(false /* dimmed */);
-            return mKeyboardView.onDismissMoreKeysPanel();
+            mMainKeyboardView.dimEntireKeyboard(false /* dimmed */);
+            return mMainKeyboardView.onDismissMoreKeysPanel();
         }
 
         @Override
         public void onShowMoreKeysPanel(MoreKeysPanel panel) {
-            mKeyboardView.onShowMoreKeysPanel(panel);
+            mMainKeyboardView.onShowMoreKeysPanel(panel);
         }
 
         @Override
@@ -728,7 +728,7 @@
         mMoreSuggestionsMode = MORE_SUGGESTIONS_CHECKING_MODAL_OR_SLIDING;
         mOriginX = mLastX;
         mOriginY = mLastY;
-        mKeyboardView.dimEntireKeyboard(true /* dimmed */);
+        mMainKeyboardView.dimEntireKeyboard(true /* dimmed */);
         for (int i = 0; i < params.mSuggestionsCountInStrip; i++) {
             mWords.get(i).setPressed(false);
         }