diff --git a/java/res/layout/key_preview.xml b/java/res/layout/key_preview.xml
deleted file mode 100644
index 16d4c72c3cf8c6408fe8460df6d23eae8193a941..0000000000000000000000000000000000000000
--- a/java/res/layout/key_preview.xml
+++ /dev/null
@@ -1,27 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
-/*
-**
-** Copyright 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.
-*/
--->
-
-<TextView xmlns:android="http://schemas.android.com/apk/res/android"
-    android:layout_width="wrap_content"
-    android:layout_height="wrap_content"
-    android:minWidth="32dp"
-    android:gravity="center"
-    style="?attr/keyPreviewTextViewStyle"
-/>
diff --git a/java/res/values/attrs.xml b/java/res/values/attrs.xml
index a1f478bd5e6920df544327d5032e5be631fb789c..fcb919d0b000ada5d64932436a47c3a94c5d26c7 100644
--- a/java/res/values/attrs.xml
+++ b/java/res/values/attrs.xml
@@ -26,8 +26,6 @@
         <attr name="keyboardViewStyle" format="reference" />
         <!-- MainKeyboardView style -->
         <attr name="mainKeyboardViewStyle" format="reference" />
-        <!-- Key preview text view style -->
-        <attr name="keyPreviewTextViewStyle" format="reference"/>
         <!-- EmojiPalettesView style -->
         <attr name="emojiPalettesViewStyle" format="reference" />
         <!-- MoreKeysKeyboard style -->
@@ -106,8 +104,8 @@
         <attr name="longPressShiftLockTimeout" 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" />
+        <!-- Background resource for key press feedback.-->
+        <attr name="keyPreviewBackground" format="reference" />
         <!-- Vertical offset of the key press feedback from the key. -->
         <attr name="keyPreviewOffset" format="dimension" />
         <!-- Height of the key press feedback popup. -->
diff --git a/java/res/values/themes-common.xml b/java/res/values/themes-common.xml
index 02a93ca828848a2050049d31b7739a9253aecbe8..2b2a80aededaca2eb5b1e26d56bfb39f30f6eef9 100644
--- a/java/res/values/themes-common.xml
+++ b/java/res/values/themes-common.xml
@@ -75,7 +75,6 @@
         <item name="keyRepeatInterval">@integer/config_key_repeat_interval</item>
         <item name="longPressShiftLockTimeout">@integer/config_longpress_shift_lock_timeout</item>
         <item name="ignoreAltCodeKeyTimeout">@integer/config_ignore_alt_code_key_timeout</item>
-        <item name="keyPreviewLayout">@layout/key_preview</item>
         <item name="keyPreviewHeight">@dimen/config_key_preview_height</item>
         <!-- TODO: consolidate key preview linger timeout with the key preview animation parameters. -->
         <item name="keyPreviewLingerTimeout">@integer/config_key_preview_linger_timeout</item>
@@ -106,7 +105,6 @@
     <style
         name="MainKeyboardView"
         parent="KeyboardView" />
-    <style name="KeyPreviewTextView" />
     <!-- Though {@link EmojiPalettesView} doesn't extend {@link KeyboardView}, some views inside it,
          for instance delete button, need themed {@link KeyboardView} attributes. -->
     <style name="EmojiPalettesView" />
diff --git a/java/res/values/themes-ics.xml b/java/res/values/themes-ics.xml
index 319b4aeed3cdb12db634082247f5fb3f68540ec5..073ae90b922bafe20521dd5a22245c3de3144fed 100644
--- a/java/res/values/themes-ics.xml
+++ b/java/res/values/themes-ics.xml
@@ -23,7 +23,6 @@
         <item name="keyboardStyle">@style/Keyboard.ICS</item>
         <item name="keyboardViewStyle">@style/KeyboardView.ICS</item>
         <item name="mainKeyboardViewStyle">@style/MainKeyboardView.ICS</item>
-        <item name="keyPreviewTextViewStyle">@style/KeyPreviewTextView.ICS</item>
         <item name="emojiPalettesViewStyle">@style/EmojiPalettesView.ICS</item>
         <item name="moreKeysKeyboardStyle">@style/MoreKeysKeyboard.ICS</item>
         <item name="moreKeysKeyboardViewStyle">@style/MoreKeysKeyboardView.ICS</item>
@@ -66,6 +65,7 @@
         name="MainKeyboardView.ICS"
         parent="KeyboardView.ICS"
     >
+        <item name="keyPreviewBackground">@drawable/keyboard_key_feedback_ics</item>
         <item name="keyPreviewOffset">@dimen/config_key_preview_offset_holo</item>
         <item name="gestureFloatingPreviewTextColor">@color/highlight_color_ics</item>
         <item name="gestureFloatingPreviewColor">@color/gesture_floating_preview_color_holo</item>
@@ -75,12 +75,6 @@
         <item name="languageOnSpacebarTextShadowRadius">1.0</item>
         <item name="languageOnSpacebarTextShadowColor">@color/spacebar_text_shadow_color_holo</item>
     </style>
-    <style
-        name="KeyPreviewTextView.ICS"
-        parent="KeyPreviewTextView"
-    >
-        <item name="android:background">@drawable/keyboard_key_feedback_ics</item>
-    </style>
     <!-- Though {@link EmojiPalettesView} doesn't extend {@link KeyboardView}, some views inside it,
          for instance delete button, need themed {@link KeyboardView} attributes. -->
     <style
diff --git a/java/res/values/themes-klp.xml b/java/res/values/themes-klp.xml
index 208723dd3bcefb89c28ce856c22ba05348cda553..f895de572080c8e987505261abad2f84e9be0e06 100644
--- a/java/res/values/themes-klp.xml
+++ b/java/res/values/themes-klp.xml
@@ -23,7 +23,6 @@
         <item name="keyboardStyle">@style/Keyboard.KLP</item>
         <item name="keyboardViewStyle">@style/KeyboardView.KLP</item>
         <item name="mainKeyboardViewStyle">@style/MainKeyboardView.KLP</item>
-        <item name="keyPreviewTextViewStyle">@style/KeyPreviewTextView.KLP</item>
         <item name="emojiPalettesViewStyle">@style/EmojiPalettesView.KLP</item>
         <item name="moreKeysKeyboardStyle">@style/MoreKeysKeyboard.KLP</item>
         <item name="moreKeysKeyboardViewStyle">@style/MoreKeysKeyboardView.KLP</item>
@@ -66,6 +65,7 @@
         name="MainKeyboardView.KLP"
         parent="KeyboardView.KLP"
     >
+        <item name="keyPreviewBackground">@drawable/keyboard_key_feedback_klp</item>
         <item name="keyPreviewOffset">@dimen/config_key_preview_offset_holo</item>
         <item name="gestureFloatingPreviewTextColor">@color/highlight_color_klp</item>
         <item name="gestureFloatingPreviewColor">@color/gesture_floating_preview_color_holo</item>
@@ -75,12 +75,6 @@
         <item name="languageOnSpacebarTextShadowRadius">1.0</item>
         <item name="languageOnSpacebarTextShadowColor">@color/spacebar_text_shadow_color_holo</item>
     </style>
-    <style
-        name="KeyPreviewTextView.KLP"
-        parent="KeyPreviewTextView"
-    >
-        <item name="android:background">@drawable/keyboard_key_feedback_klp</item>
-    </style>
     <!-- Though {@link EmojiPalettesView} doesn't extend {@link KeyboardView}, some views inside it,
          for instance delete button, need themed {@link KeyboardView} attributes. -->
     <style
diff --git a/java/res/values/themes-lxx-dark.xml b/java/res/values/themes-lxx-dark.xml
index e9a295c67df7581e262d1ab1d5eb5a90e6a5de66..1db8f428eec63d5109bc88c023a32cc7fea50979 100644
--- a/java/res/values/themes-lxx-dark.xml
+++ b/java/res/values/themes-lxx-dark.xml
@@ -23,7 +23,6 @@
         <item name="keyboardStyle">@style/Keyboard.LXX_Dark</item>
         <item name="keyboardViewStyle">@style/KeyboardView.LXX_Dark</item>
         <item name="mainKeyboardViewStyle">@style/MainKeyboardView.LXX_Dark</item>
-        <item name="keyPreviewTextViewStyle">@style/KeyPreviewTextView.LXX_Dark</item>
         <item name="emojiPalettesViewStyle">@style/EmojiPalettesView.LXX_Dark</item>
         <item name="moreKeysKeyboardStyle">@style/MoreKeysKeyboard.LXX_Dark</item>
         <item name="moreKeysKeyboardViewStyle">@style/MoreKeysKeyboardView.LXX_Dark</item>
@@ -67,6 +66,7 @@
         name="MainKeyboardView.LXX_Dark"
         parent="KeyboardView.LXX_Dark"
     >
+        <item name="keyPreviewBackground">@drawable/keyboard_key_feedback_lxx_dark</item>
         <item name="keyPreviewOffset">@dimen/config_key_preview_offset_holo</item>
         <item name="gestureFloatingPreviewTextColor">@color/highlight_color_lxx_dark</item>
         <item name="gestureFloatingPreviewColor">@color/gesture_floating_preview_color_lxx_dark</item>
@@ -76,12 +76,6 @@
         <!-- A negative value to disable text shadow layer. -->
         <item name="languageOnSpacebarTextShadowRadius">-1.0</item>
     </style>
-    <style
-        name="KeyPreviewTextView.LXX_Dark"
-        parent="KeyPreviewTextView"
-    >
-        <item name="android:background">@drawable/keyboard_key_feedback_lxx_dark</item>
-    </style>
     <!-- Though {@link EmojiPalettesView} doesn't extend {@link KeyboardView}, some views inside it,
          for instance delete button, need themed {@link KeyboardView} attributes. -->
     <style
diff --git a/java/src/com/android/inputmethod/keyboard/internal/KeyPreviewChoreographer.java b/java/src/com/android/inputmethod/keyboard/internal/KeyPreviewChoreographer.java
index d4c671028d3116533ee2e3954579343365b074a6..6fc300bebcc644a08d61a05d8ca4990182ec3532 100644
--- a/java/src/com/android/inputmethod/keyboard/internal/KeyPreviewChoreographer.java
+++ b/java/src/com/android/inputmethod/keyboard/internal/KeyPreviewChoreographer.java
@@ -21,17 +21,12 @@ import android.animation.AnimatorListenerAdapter;
 import android.animation.AnimatorSet;
 import android.animation.ObjectAnimator;
 import android.content.Context;
-import android.graphics.drawable.Drawable;
-import android.util.TypedValue;
-import android.view.LayoutInflater;
 import android.view.View;
 import android.view.ViewGroup;
 import android.view.animation.AccelerateInterpolator;
 import android.view.animation.DecelerateInterpolator;
-import android.widget.TextView;
 
 import com.android.inputmethod.keyboard.Key;
-import com.android.inputmethod.latin.R;
 import com.android.inputmethod.latin.utils.CoordinateUtils;
 import com.android.inputmethod.latin.utils.ViewLayoutUtils;
 
@@ -46,10 +41,11 @@ import java.util.HashSet;
  * - how key previews should be shown and dismissed.
  */
 public final class KeyPreviewChoreographer {
-    // Free {@link TextView} pool that can be used for key preview.
-    private final ArrayDeque<TextView> mFreeKeyPreviewTextViews = new ArrayDeque<>();
-    // Map from {@link Key} to {@link TextView} that is currently being displayed as key preview.
-    private final HashMap<Key,TextView> mShowingKeyPreviewTextViews = new HashMap<>();
+    // Free {@link KeyPreviewView} pool that can be used for key preview.
+    private final ArrayDeque<KeyPreviewView> mFreeKeyPreviewViews = new ArrayDeque<>();
+    // Map from {@link Key} to {@link KeyPreviewView} that is currently being displayed as key
+    // preview.
+    private final HashMap<Key,KeyPreviewView> mShowingKeyPreviewViews = new HashMap<>();
 
     private final KeyPreviewDrawParams mParams;
 
@@ -57,32 +53,28 @@ public final class KeyPreviewChoreographer {
         mParams = params;
     }
 
-    private TextView getKeyPreviewTextView(final Key key, final ViewGroup placerView) {
-        TextView previewTextView = mShowingKeyPreviewTextViews.remove(key);
-        if (previewTextView != null) {
-            return previewTextView;
+    public KeyPreviewView getKeyPreviewView(final Key key, final ViewGroup placerView) {
+        KeyPreviewView keyPreviewView = mShowingKeyPreviewViews.remove(key);
+        if (keyPreviewView != null) {
+            return keyPreviewView;
         }
-        previewTextView = mFreeKeyPreviewTextViews.poll();
-        if (previewTextView != null) {
-            return previewTextView;
+        keyPreviewView = mFreeKeyPreviewViews.poll();
+        if (keyPreviewView != null) {
+            return keyPreviewView;
         }
         final Context context = placerView.getContext();
-        if (mParams.mLayoutId != 0) {
-            previewTextView = (TextView)LayoutInflater.from(context)
-                    .inflate(mParams.mLayoutId, null);
-        } else {
-            previewTextView = new TextView(context);
-        }
-        placerView.addView(previewTextView, ViewLayoutUtils.newLayoutParam(placerView, 0, 0));
-        return previewTextView;
+        keyPreviewView = new KeyPreviewView(context, null /* attrs */);
+        keyPreviewView.setBackgroundResource(mParams.mPreviewBackgroundResId);
+        placerView.addView(keyPreviewView, ViewLayoutUtils.newLayoutParam(placerView, 0, 0));
+        return keyPreviewView;
     }
 
     public boolean isShowingKeyPreview(final Key key) {
-        return mShowingKeyPreviewTextViews.containsKey(key);
+        return mShowingKeyPreviewViews.containsKey(key);
     }
 
     public void dismissAllKeyPreviews() {
-        for (final Key key : new HashSet<>(mShowingKeyPreviewTextViews.keySet())) {
+        for (final Key key : new HashSet<>(mShowingKeyPreviewViews.keySet())) {
             dismissKeyPreview(key, false /* withAnimation */);
         }
     }
@@ -91,11 +83,11 @@ public final class KeyPreviewChoreographer {
         if (key == null) {
             return;
         }
-        final TextView previewTextView = mShowingKeyPreviewTextViews.get(key);
-        if (previewTextView == null) {
+        final KeyPreviewView keyPreviewView = mShowingKeyPreviewViews.get(key);
+        if (keyPreviewView == null) {
             return;
         }
-        final Object tag = previewTextView.getTag();
+        final Object tag = keyPreviewView.getTag();
         if (withAnimation) {
             if (tag instanceof KeyPreviewAnimations) {
                 final KeyPreviewAnimations animation = (KeyPreviewAnimations)tag;
@@ -104,114 +96,76 @@ public final class KeyPreviewChoreographer {
             }
         }
         // Dismiss preview without animation.
-        mShowingKeyPreviewTextViews.remove(key);
+        mShowingKeyPreviewViews.remove(key);
         if (tag instanceof Animator) {
             ((Animator)tag).cancel();
         }
-        previewTextView.setTag(null);
-        previewTextView.setVisibility(View.INVISIBLE);
-        mFreeKeyPreviewTextViews.add(previewTextView);
+        keyPreviewView.setTag(null);
+        keyPreviewView.setVisibility(View.INVISIBLE);
+        mFreeKeyPreviewViews.add(keyPreviewView);
     }
 
-    // Background state set
-    private static final int[][][] KEY_PREVIEW_BACKGROUND_STATE_TABLE = {
-        { // STATE_MIDDLE
-            {},
-            { 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;
-
     public void placeKeyPreviewAndShow(final Key key, final KeyboardIconsSet iconsSet,
             final KeyDrawParams drawParams, final int keyboardViewWidth, final int[] keyboardOrigin,
             final ViewGroup placerView, final boolean withAnimation) {
-        final TextView previewTextView = getKeyPreviewTextView(key, placerView);
+        final KeyPreviewView keyPreviewView = getKeyPreviewView(key, placerView);
         placeKeyPreview(
-                key, previewTextView, iconsSet, drawParams, keyboardViewWidth, keyboardOrigin);
-        showKeyPreview(key, previewTextView, withAnimation);
+                key, keyPreviewView, iconsSet, drawParams, keyboardViewWidth, keyboardOrigin);
+        showKeyPreview(key, keyPreviewView, withAnimation);
     }
 
-    private void placeKeyPreview(final Key key, final TextView previewTextView,
+    private void placeKeyPreview(final Key key, final KeyPreviewView keyPreviewView,
             final KeyboardIconsSet iconsSet, final KeyDrawParams drawParams,
             final int keyboardViewWidth, final int[] originCoords) {
-        previewTextView.setTextColor(drawParams.mPreviewTextColor);
-        final Drawable background = previewTextView.getBackground();
-        final String label = key.getPreviewLabel();
-        // 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.
-            previewTextView.setCompoundDrawables(null, null, null, null);
-            previewTextView.setTextSize(TypedValue.COMPLEX_UNIT_PX,
-                    key.selectPreviewTextSize(drawParams));
-            previewTextView.setTypeface(key.selectPreviewTypeface(drawParams));
-            previewTextView.setText(label);
-        } else {
-            previewTextView.setCompoundDrawables(null, null, null, key.getPreviewIcon(iconsSet));
-            previewTextView.setText(null);
-        }
-
-        previewTextView.measure(
+        keyPreviewView.setPreviewVisual(key, iconsSet, drawParams);
+        keyPreviewView.measure(
                 ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT);
-        mParams.setGeometry(previewTextView);
-        final int previewWidth = previewTextView.getMeasuredWidth();
+        mParams.setGeometry(keyPreviewView);
+        final int previewWidth = keyPreviewView.getMeasuredWidth();
         final int previewHeight = mParams.mPreviewHeight;
         final int keyDrawWidth = key.getDrawWidth();
         // 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;
+        final int keyPreviewPosition;
         int previewX = key.getDrawX() - (previewWidth - keyDrawWidth) / 2
                 + CoordinateUtils.x(originCoords);
         if (previewX < 0) {
             previewX = 0;
-            statePosition = STATE_LEFT;
+            keyPreviewPosition = KeyPreviewView.POSITION_LEFT;
         } else if (previewX > keyboardViewWidth - previewWidth) {
             previewX = keyboardViewWidth - previewWidth;
-            statePosition = STATE_RIGHT;
+            keyPreviewPosition = KeyPreviewView.POSITION_RIGHT;
         } else {
-            statePosition = STATE_MIDDLE;
+            keyPreviewPosition = KeyPreviewView.POSITION_MIDDLE;
         }
+        final boolean hasMoreKeys = (key.getMoreKeys() != null);
+        keyPreviewView.setPreviewBackground(hasMoreKeys, keyPreviewPosition);
         // The key preview is placed vertically above the top edge of the parent key with an
         // arbitrary offset.
         final int previewY = key.getY() - previewHeight + mParams.mPreviewOffset
                 + CoordinateUtils.y(originCoords);
 
-        if (background != null) {
-            final int hasMoreKeys = (key.getMoreKeys() != null) ? STATE_HAS_MOREKEYS : STATE_NORMAL;
-            background.setState(KEY_PREVIEW_BACKGROUND_STATE_TABLE[statePosition][hasMoreKeys]);
-        }
         ViewLayoutUtils.placeViewAt(
-                previewTextView, previewX, previewY, previewWidth, previewHeight);
-        previewTextView.setPivotX(previewWidth / 2.0f);
-        previewTextView.setPivotY(previewHeight);
+                keyPreviewView, previewX, previewY, previewWidth, previewHeight);
+        keyPreviewView.setPivotX(previewWidth / 2.0f);
+        keyPreviewView.setPivotY(previewHeight);
     }
 
-    private void showKeyPreview(final Key key, final TextView previewTextView,
+    private void showKeyPreview(final Key key, final KeyPreviewView keyPreviewView,
             final boolean withAnimation) {
         if (!withAnimation) {
-            previewTextView.setVisibility(View.VISIBLE);
-            mShowingKeyPreviewTextViews.put(key, previewTextView);
+            keyPreviewView.setVisibility(View.VISIBLE);
+            mShowingKeyPreviewViews.put(key, keyPreviewView);
             return;
         }
 
         // Show preview with animation.
-        final Animator showUpAnimation = createShowUpAniation(key, previewTextView);
-        final Animator dismissAnimation = createDismissAnimation(key, previewTextView);
+        final Animator showUpAnimation = createShowUpAniation(key, keyPreviewView);
+        final Animator dismissAnimation = createDismissAnimation(key, keyPreviewView);
         final KeyPreviewAnimations animation = new KeyPreviewAnimations(
                 showUpAnimation, dismissAnimation);
-        previewTextView.setTag(animation);
+        keyPreviewView.setTag(animation);
         animation.startShowUp();
     }
 
@@ -221,13 +175,13 @@ public final class KeyPreviewChoreographer {
     private static final DecelerateInterpolator DECELERATE_INTERPOLATOR =
             new DecelerateInterpolator();
 
-    private Animator createShowUpAniation(final Key key, final TextView previewTextView) {
+    private Animator createShowUpAniation(final Key key, final KeyPreviewView keyPreviewView) {
         // TODO: Optimization for no scale animation and no duration.
         final ObjectAnimator scaleXAnimation = ObjectAnimator.ofFloat(
-                previewTextView, View.SCALE_X, mParams.getShowUpStartScale(),
+                keyPreviewView, View.SCALE_X, mParams.getShowUpStartScale(),
                 KEY_PREVIEW_SHOW_UP_END_SCALE);
         final ObjectAnimator scaleYAnimation = ObjectAnimator.ofFloat(
-                previewTextView, View.SCALE_Y, mParams.getShowUpStartScale(),
+                keyPreviewView, View.SCALE_Y, mParams.getShowUpStartScale(),
                 KEY_PREVIEW_SHOW_UP_END_SCALE);
         final AnimatorSet showUpAnimation = new AnimatorSet();
         showUpAnimation.play(scaleXAnimation).with(scaleYAnimation);
@@ -236,18 +190,18 @@ public final class KeyPreviewChoreographer {
         showUpAnimation.addListener(new AnimatorListenerAdapter() {
             @Override
             public void onAnimationStart(final Animator animation) {
-                showKeyPreview(key, previewTextView, false /* withAnimation */);
+                showKeyPreview(key, keyPreviewView, false /* withAnimation */);
             }
         });
         return showUpAnimation;
     }
 
-    private Animator createDismissAnimation(final Key key, final TextView previewTextView) {
+    private Animator createDismissAnimation(final Key key, final KeyPreviewView keyPreviewView) {
         // TODO: Optimization for no scale animation and no duration.
         final ObjectAnimator scaleXAnimation = ObjectAnimator.ofFloat(
-                previewTextView, View.SCALE_X, mParams.getDismissEndScale());
+                keyPreviewView, View.SCALE_X, mParams.getDismissEndScale());
         final ObjectAnimator scaleYAnimation = ObjectAnimator.ofFloat(
-                previewTextView, View.SCALE_Y, mParams.getDismissEndScale());
+                keyPreviewView, View.SCALE_Y, mParams.getDismissEndScale());
         final AnimatorSet dismissAnimation = new AnimatorSet();
         dismissAnimation.play(scaleXAnimation).with(scaleYAnimation);
         final int dismissDuration = Math.min(
diff --git a/java/src/com/android/inputmethod/keyboard/internal/KeyPreviewDrawParams.java b/java/src/com/android/inputmethod/keyboard/internal/KeyPreviewDrawParams.java
index 37e5c889d7fa31115e2d55445010de0c2cb8e3a6..68c9831fa4ca5dd96517633744869953e2539348 100644
--- a/java/src/com/android/inputmethod/keyboard/internal/KeyPreviewDrawParams.java
+++ b/java/src/com/android/inputmethod/keyboard/internal/KeyPreviewDrawParams.java
@@ -23,9 +23,9 @@ import com.android.inputmethod.latin.R;
 
 public final class KeyPreviewDrawParams {
     // XML attributes of {@link MainKeyboardView}.
-    public final int mLayoutId;
     public final int mPreviewOffset;
     public final int mPreviewHeight;
+    public final int mPreviewBackgroundResId;
     private int mShowUpDuration;
     private int mDismissDuration;
     private float mShowUpStartScale;
@@ -63,13 +63,10 @@ public final class KeyPreviewDrawParams {
                 R.styleable.MainKeyboardView_keyPreviewOffset, 0);
         mPreviewHeight = mainKeyboardViewAttr.getDimensionPixelSize(
                 R.styleable.MainKeyboardView_keyPreviewHeight, 0);
+        mPreviewBackgroundResId = mainKeyboardViewAttr.getResourceId(
+                R.styleable.MainKeyboardView_keyPreviewBackground, 0);
         mLingerTimeout = mainKeyboardViewAttr.getInt(
                 R.styleable.MainKeyboardView_keyPreviewLingerTimeout, 0);
-        mLayoutId = mainKeyboardViewAttr.getResourceId(
-                R.styleable.MainKeyboardView_keyPreviewLayout, 0);
-        if (mLayoutId == 0) {
-            mShowPopup = false;
-        }
     }
 
     public void setVisibleOffset(final int previewVisibleOffset) {
diff --git a/java/src/com/android/inputmethod/keyboard/internal/KeyPreviewView.java b/java/src/com/android/inputmethod/keyboard/internal/KeyPreviewView.java
new file mode 100644
index 0000000000000000000000000000000000000000..360faf8296bb5cd47cbf5589dcede8ae5b6bcd63
--- /dev/null
+++ b/java/src/com/android/inputmethod/keyboard/internal/KeyPreviewView.java
@@ -0,0 +1,90 @@
+/*
+ * Copyright (C) 2014 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.keyboard.internal;
+
+import android.content.Context;
+import android.graphics.drawable.Drawable;
+import android.util.AttributeSet;
+import android.util.TypedValue;
+import android.view.Gravity;
+import android.widget.TextView;
+
+import com.android.inputmethod.keyboard.Key;
+import com.android.inputmethod.latin.R;
+
+/**
+ * The pop up key preview view.
+ */
+public class KeyPreviewView extends TextView {
+    public static final int POSITION_MIDDLE = 0;
+    public static final int POSITION_LEFT = 1;
+    public static final int POSITION_RIGHT = 2;
+
+    public KeyPreviewView(final Context context, final AttributeSet attrs) {
+        this(context, attrs, 0);
+    }
+
+    public KeyPreviewView(final Context context, final AttributeSet attrs, final int defStyleAttr) {
+        super(context, attrs, defStyleAttr);
+        setGravity(Gravity.CENTER);
+    }
+
+    public void setPreviewVisual(final Key key, final KeyboardIconsSet iconsSet,
+            final KeyDrawParams drawParams) {
+        // What we show as preview should match what we show on a key top in onDraw().
+        final int iconId = key.getIconId();
+        if (iconId != KeyboardIconsSet.ICON_UNDEFINED) {
+            setCompoundDrawables(null, null, null, key.getPreviewIcon(iconsSet));
+            setText(null);
+            return;
+        }
+
+        setCompoundDrawables(null, null, null, null);
+        setTextColor(drawParams.mPreviewTextColor);
+        setTextSize(TypedValue.COMPLEX_UNIT_PX, key.selectPreviewTextSize(drawParams));
+        setTypeface(key.selectPreviewTypeface(drawParams));
+        // TODO Should take care of temporaryShiftLabel here.
+        setText(key.getPreviewLabel());
+    }
+
+    // Background state set
+    private static final int[][][] KEY_PREVIEW_BACKGROUND_STATE_TABLE = {
+        { // POSITION_MIDDLE
+            {},
+            { R.attr.state_has_morekeys }
+        },
+        { // POSITION_LEFT
+            { R.attr.state_left_edge },
+            { R.attr.state_left_edge, R.attr.state_has_morekeys }
+        },
+        { // POSITION_RIGHT
+            { R.attr.state_right_edge },
+            { R.attr.state_right_edge, R.attr.state_has_morekeys }
+        }
+    };
+    private static final int STATE_NORMAL = 0;
+    private static final int STATE_HAS_MOREKEYS = 1;
+
+    public void setPreviewBackground(final boolean hasMoreKeys, final int position) {
+        final Drawable background = getBackground();
+        if (background == null) {
+            return;
+        }
+        final int hasMoreKeysState = hasMoreKeys ? STATE_HAS_MOREKEYS : STATE_NORMAL;
+        background.setState(KEY_PREVIEW_BACKGROUND_STATE_TABLE[position][hasMoreKeysState]);
+    }
+}