diff --git a/java/src/com/android/inputmethod/keyboard/KeyboardView.java b/java/src/com/android/inputmethod/keyboard/KeyboardView.java
index b05cc70b795f36e714f7567d4d630427d2d361f4..721931e98567ede16051d8c87b07059a9c7f3a19 100644
--- a/java/src/com/android/inputmethod/keyboard/KeyboardView.java
+++ b/java/src/com/android/inputmethod/keyboard/KeyboardView.java
@@ -38,6 +38,7 @@ import android.view.ViewGroup;
 import android.widget.RelativeLayout;
 import android.widget.TextView;
 
+import com.android.inputmethod.latin.Constants;
 import com.android.inputmethod.latin.LatinImeLogger;
 import com.android.inputmethod.latin.R;
 import com.android.inputmethod.latin.StaticInnerHandlerWrapper;
@@ -94,7 +95,8 @@ public class KeyboardView extends View implements PointerTracker.DrawingProxy {
     // The maximum key label width in the proportion to the key width.
     private static final float MAX_LABEL_RATIO = 0.90f;
 
-    public final static int ALPHA_OPAQUE = 255;
+    private final static int GESTURE_DRAWING_WIDTH = 5;
+    private final static int GESTURE_DRAWING_COLOR = 0xff33b5e5;
 
     // Main keyboard
     private Keyboard mKeyboard;
@@ -118,11 +120,14 @@ public class KeyboardView extends View implements PointerTracker.DrawingProxy {
     private final HashSet<Key> mInvalidatedKeys = new HashSet<Key>();
     /** The region of invalidated keys */
     private final Rect mInvalidatedKeysRect = new Rect();
+    /** The region of invalidated gestures */
+    private final Rect mInvalidatedGesturesRect = new Rect();
     /** The keyboard bitmap buffer for faster updates */
     private Bitmap mBuffer;
     /** The canvas for the above mutable keyboard bitmap */
     private Canvas mCanvas;
     private final Paint mPaint = new Paint();
+    private final Paint mGesturePaint = new Paint();
     private final Paint.FontMetrics mFontMetrics = new Paint.FontMetrics();
     // This sparse array caches key label text height in pixel indexed by key label text size.
     private static final SparseArray<Float> sTextHeightCache = new SparseArray<Float>();
@@ -264,7 +269,7 @@ public class KeyboardView extends View implements PointerTracker.DrawingProxy {
 
         public void blendAlpha(Paint paint) {
             final int color = paint.getColor();
-            paint.setARGB((paint.getAlpha() * mAnimAlpha) / ALPHA_OPAQUE,
+            paint.setARGB((paint.getAlpha() * mAnimAlpha) / Constants.Color.ALPHA_OPAQUE,
                     Color.red(color), Color.green(color), Color.blue(color));
         }
     }
@@ -372,6 +377,13 @@ public class KeyboardView extends View implements PointerTracker.DrawingProxy {
         mDelayAfterPreview = mKeyPreviewDrawParams.mLingerTimeout;
 
         mPaint.setAntiAlias(true);
+
+        // TODO: These paint parameters should be specified via attribute of the view and styleable.
+        mGesturePaint.setAntiAlias(true);
+        mGesturePaint.setStyle(Paint.Style.STROKE);
+        mGesturePaint.setStrokeJoin(Paint.Join.ROUND);
+        mGesturePaint.setColor(GESTURE_DRAWING_COLOR);
+        mGesturePaint.setStrokeWidth(GESTURE_DRAWING_WIDTH);
     }
 
     // Read fraction value in TypedArray as float.
@@ -517,7 +529,7 @@ public class KeyboardView extends View implements PointerTracker.DrawingProxy {
         final int keyDrawY = key.mY + getPaddingTop();
         canvas.translate(keyDrawX, keyDrawY);
 
-        params.mAnimAlpha = ALPHA_OPAQUE;
+        params.mAnimAlpha = Constants.Color.ALPHA_OPAQUE;
         if (!key.isSpacer()) {
             onDrawKeyBackground(key, canvas, params);
         }
@@ -860,17 +872,60 @@ public class KeyboardView extends View implements PointerTracker.DrawingProxy {
         mDrawingHandler.dismissKeyPreview(mDelayAfterPreview, tracker);
     }
 
+    private static class PreviewView extends RelativeLayout {
+        KeyPreviewDrawParams mParams;
+        Paint mGesturePaint;
+
+        public PreviewView(Context context, KeyPreviewDrawParams params, Paint gesturePaint) {
+            super(context);
+            setWillNotDraw(false);
+            mParams = params;
+            mGesturePaint = gesturePaint;
+        }
+
+        @Override
+        public void onDraw(Canvas canvas) {
+            super.onDraw(canvas);
+            canvas.translate(mParams.mCoordinates[0], mParams.mCoordinates[1]);
+            PointerTracker.drawGestureTrailForAllPointerTrackers(canvas, mGesturePaint);
+        }
+    }
+
     private void addKeyPreview(TextView keyPreview) {
         if (mPreviewPlacer == null) {
-            mPreviewPlacer = new RelativeLayout(getContext());
-            final ViewGroup windowContentView =
-                    (ViewGroup)getRootView().findViewById(android.R.id.content);
-            windowContentView.addView(mPreviewPlacer);
+            createPreviewPlacer();
         }
         mPreviewPlacer.addView(
                 keyPreview, ViewLayoutUtils.newLayoutParam(mPreviewPlacer, 0, 0));
     }
 
+    private void createPreviewPlacer() {
+        mPreviewPlacer = new PreviewView(getContext(), mKeyPreviewDrawParams, mGesturePaint);
+        final ViewGroup windowContentView =
+                (ViewGroup)getRootView().findViewById(android.R.id.content);
+        windowContentView.addView(mPreviewPlacer);
+    }
+
+    @Override
+    public void showGestureTrail(PointerTracker tracker) {
+        if (mPreviewPlacer == null) {
+            createPreviewPlacer();
+        }
+        final Rect r = tracker.getDrawingRect();
+        if (!r.isEmpty()) {
+            // Invalidate the rectangular region encompassing the gesture. This is needed because
+            // past points along the gesture will fade and gradually disappear.
+            final KeyPreviewDrawParams params = mKeyPreviewDrawParams;
+            mInvalidatedGesturesRect.set(r.left + params.mCoordinates[0] - GESTURE_DRAWING_WIDTH,
+                    r.top + params.mCoordinates[1] - GESTURE_DRAWING_WIDTH,
+                    r.right + params.mCoordinates[0] + GESTURE_DRAWING_WIDTH,
+                    r.bottom + params.mCoordinates[1] + GESTURE_DRAWING_WIDTH);
+            mPreviewPlacer.invalidate(mInvalidatedGesturesRect);
+        } else {
+            mPreviewPlacer.invalidate();
+        }
+    }
+
     @SuppressWarnings("deprecation") // setBackgroundDrawable is replaced by setBackground in API16
     @Override
     public void showKeyPreview(PointerTracker tracker) {
diff --git a/java/src/com/android/inputmethod/keyboard/LatinKeyboardView.java b/java/src/com/android/inputmethod/keyboard/LatinKeyboardView.java
index 36d3664ded47ac702660afd5cf7c0c147e44528d..8e904c6f9d2cd080df5a0afb1f61272c635a8b55 100644
--- a/java/src/com/android/inputmethod/keyboard/LatinKeyboardView.java
+++ b/java/src/com/android/inputmethod/keyboard/LatinKeyboardView.java
@@ -43,6 +43,7 @@ import com.android.inputmethod.accessibility.AccessibilityUtils;
 import com.android.inputmethod.accessibility.AccessibleKeyboardViewProxy;
 import com.android.inputmethod.keyboard.PointerTracker.DrawingProxy;
 import com.android.inputmethod.keyboard.PointerTracker.TimerProxy;
+import com.android.inputmethod.latin.Constants;
 import com.android.inputmethod.latin.LatinIME;
 import com.android.inputmethod.latin.LatinImeLogger;
 import com.android.inputmethod.latin.R;
@@ -82,7 +83,7 @@ public class LatinKeyboardView extends KeyboardView implements PointerTracker.Ke
     private ObjectAnimator mLanguageOnSpacebarFadeoutAnimator;
     private boolean mNeedsToDisplayLanguage;
     private boolean mHasMultipleEnabledIMEsOrSubtypes;
-    private int mLanguageOnSpacebarAnimAlpha = ALPHA_OPAQUE;
+    private int mLanguageOnSpacebarAnimAlpha = Constants.Color.ALPHA_OPAQUE;
     private final float mSpacebarTextRatio;
     private float mSpacebarTextSize;
     private final int mSpacebarTextColor;
@@ -98,7 +99,7 @@ public class LatinKeyboardView extends KeyboardView implements PointerTracker.Ke
     // Stuff to draw altCodeWhileTyping keys.
     private ObjectAnimator mAltCodeKeyWhileTypingFadeoutAnimator;
     private ObjectAnimator mAltCodeKeyWhileTypingFadeinAnimator;
-    private int mAltCodeKeyWhileTypingAnimAlpha = ALPHA_OPAQUE;
+    private int mAltCodeKeyWhileTypingAnimAlpha = Constants.Color.ALPHA_OPAQUE;
 
     // More keys keyboard
     private PopupWindow mMoreKeysWindow;
@@ -361,7 +362,8 @@ public class LatinKeyboardView extends KeyboardView implements PointerTracker.Ke
         mSpacebarTextShadowColor = a.getColor(
                 R.styleable.LatinKeyboardView_spacebarTextShadowColor, 0);
         mLanguageOnSpacebarFinalAlpha = a.getInt(
-                R.styleable.LatinKeyboardView_languageOnSpacebarFinalAlpha, ALPHA_OPAQUE);
+                R.styleable.LatinKeyboardView_languageOnSpacebarFinalAlpha,
+                Constants.Color.ALPHA_OPAQUE);
         final int languageOnSpacebarFadeoutAnimatorResId = a.getResourceId(
                 R.styleable.LatinKeyboardView_languageOnSpacebarFadeoutAnimator, 0);
         final int altCodeKeyWhileTypingFadeoutAnimatorResId = a.getResourceId(
@@ -468,7 +470,7 @@ public class LatinKeyboardView extends KeyboardView implements PointerTracker.Ke
 
         mSpaceKey = keyboard.getKey(Keyboard.CODE_SPACE);
         mSpaceIcon = (mSpaceKey != null)
-                ? mSpaceKey.getIcon(keyboard.mIconsSet, ALPHA_OPAQUE) : null;
+                ? mSpaceKey.getIcon(keyboard.mIconsSet, Constants.Color.ALPHA_OPAQUE) : null;
         final int keyHeight = keyboard.mMostCommonKeyHeight - keyboard.mVerticalGap;
         mSpacebarTextSize = keyHeight * mSpacebarTextRatio;
         if (ProductionFlag.IS_EXPERIMENTAL) {
@@ -870,7 +872,7 @@ public class LatinKeyboardView extends KeyboardView implements PointerTracker.Ke
             mNeedsToDisplayLanguage = false;
         } else {
             if (subtypeChanged && needsToDisplayLanguage) {
-                setLanguageOnSpacebarAnimAlpha(ALPHA_OPAQUE);
+                setLanguageOnSpacebarAnimAlpha(Constants.Color.ALPHA_OPAQUE);
                 if (animator.isStarted()) {
                     animator.cancel();
                 }
diff --git a/java/src/com/android/inputmethod/keyboard/PointerTracker.java b/java/src/com/android/inputmethod/keyboard/PointerTracker.java
index b002ae992d73ebaf105daaf728ec387f9afe0ac5..29aaa1b530c7bce0135b2e123bd1357cfe04390e 100644
--- a/java/src/com/android/inputmethod/keyboard/PointerTracker.java
+++ b/java/src/com/android/inputmethod/keyboard/PointerTracker.java
@@ -16,6 +16,9 @@
 
 package com.android.inputmethod.keyboard;
 
+import android.graphics.Canvas;
+import android.graphics.Paint;
+import android.graphics.Rect;
 import android.os.SystemClock;
 import android.util.Log;
 import android.view.MotionEvent;
@@ -76,6 +79,7 @@ public class PointerTracker {
         public TextView inflateKeyPreviewText();
         public void showKeyPreview(PointerTracker tracker);
         public void dismissKeyPreview(PointerTracker tracker);
+        public void showGestureTrail(PointerTracker tracker);
     }
 
     public interface TimerProxy {
@@ -283,6 +287,15 @@ public class PointerTracker {
         sAggregratedPointers.reset();
     }
 
+    // TODO: To handle multi-touch gestures we may want to move this method to
+    // {@link PointerTrackerQueue}.
+    public static void drawGestureTrailForAllPointerTrackers(Canvas canvas, Paint paint) {
+        for (final PointerTracker tracker : sTrackers) {
+            tracker.mGestureStroke.drawGestureTrail(canvas, paint, tracker.getLastX(),
+                    tracker.getLastY());
+        }
+    }
+
     private PointerTracker(int id, KeyEventHandler handler) {
         if (handler == null)
             throw new NullPointerException();
@@ -511,6 +524,9 @@ public class PointerTracker {
     public long getDownTime() {
         return mDownTime;
     }
+    public Rect getDrawingRect() {
+        return mGestureStroke.getDrawingRect();
+    }
 
     private Key onDownKey(int x, int y, long eventTime) {
         mDownTime = eventTime;
@@ -696,6 +712,7 @@ public class PointerTracker {
 
         if (key != null && mInGesture) {
             final InputPointers batchPoints = getIncrementalBatchPoints();
+            mDrawingProxy.showGestureTrail(this);
             if (updateBatchInputRecognitionState(eventTime, batchPoints.getPointerSize())) {
                 updateBatchInput(batchPoints);
             }
@@ -868,6 +885,7 @@ public class PointerTracker {
                 callListenerOnRelease(mCurrentKey, mCurrentKey.mCode, true);
                 mCurrentKey = null;
             }
+            mDrawingProxy.showGestureTrail(this);
             return;
         }
         // This event will be recognized as a regular code input. Clear unused batch points so they
diff --git a/java/src/com/android/inputmethod/keyboard/internal/GestureStroke.java b/java/src/com/android/inputmethod/keyboard/internal/GestureStroke.java
index 16c8410df895bb46b40235ed2cadfc7de675cdb4..7b100d72969e25655ab6db0b890197fe79bf74fd 100644
--- a/java/src/com/android/inputmethod/keyboard/internal/GestureStroke.java
+++ b/java/src/com/android/inputmethod/keyboard/internal/GestureStroke.java
@@ -14,8 +14,12 @@
 
 package com.android.inputmethod.keyboard.internal;
 
+import android.graphics.Canvas;
+import android.graphics.Paint;
+import android.graphics.Rect;
 import android.util.FloatMath;
 
+import com.android.inputmethod.latin.Constants;
 import com.android.inputmethod.latin.InputPointers;
 
 public class GestureStroke {
@@ -35,6 +39,7 @@ public class GestureStroke {
     private int mMinGestureLength;
     private int mMinGestureLengthWhileInGesture;
     private int mMinGestureSampleLength;
+    private final Rect mDrawingRect = new Rect();
 
     // TODO: Move some of these to resource.
     private static final float MIN_GESTURE_LENGTH_RATIO_TO_KEY_WIDTH = 1.0f;
@@ -47,6 +52,10 @@ public class GestureStroke {
 
     private static final float DOUBLE_PI = (float)(2 * Math.PI);
 
+    // Fade based on number of gesture samples, see MIN_GESTURE_SAMPLING_RATIO_TO_KEY_HEIGHT
+    private static final int DRAWING_GESTURE_FADE_START = 10;
+    private static final int DRAWING_GESTURE_FADE_RATE = 6;
+
     public GestureStroke(int pointerId) {
         mPointerId = pointerId;
         reset();
@@ -77,6 +86,7 @@ public class GestureStroke {
         mLastIncrementalBatchSize = 0;
         mLastPointTime = 0;
         mInputPointers.reset();
+        mDrawingRect.setEmpty();
     }
 
     private void updateLastPoint(final int x, final int y, final int time) {
@@ -94,7 +104,6 @@ public class GestureStroke {
             }
             return;
         }
-
         final int[] xCoords = mInputPointers.getXCoordinates();
         final int[] yCoords = mInputPointers.getYCoordinates();
         final int lastX = xCoords[size - 1];
@@ -102,6 +111,11 @@ public class GestureStroke {
         final float dist = getDistance(lastX, lastY, x, y);
         if (dist > mMinGestureSampleLength) {
             mInputPointers.addPointer(x, y, mPointerId, time);
+            if (mDrawingRect.isEmpty()) {
+                mDrawingRect.set(x - 1, y - 1, x + 1, y + 1);
+            } else {
+                mDrawingRect.union(x, y);
+            }
             mLength += dist;
             final float angle = getAngle(lastX, lastY, x, y);
             if (size > 1) {
@@ -161,4 +175,27 @@ public class GestureStroke {
         }
         return diff;
     }
+
+    public void drawGestureTrail(Canvas canvas, Paint paint, int lastX, int lastY) {
+        // TODO: These paint parameter interpolation should be tunable, possibly introduce an object
+        // that implements an interface such as Paint getPaint(int step, int strokePoints)
+        final int size = mInputPointers.getPointerSize();
+        int[] xCoords = mInputPointers.getXCoordinates();
+        int[] yCoords = mInputPointers.getYCoordinates();
+        int alpha = Constants.Color.ALPHA_OPAQUE;
+        for (int i = size - 1; i > 0 && alpha > 0; i--) {
+            paint.setAlpha(alpha);
+            if (size - i > DRAWING_GESTURE_FADE_START) {
+                alpha -= DRAWING_GESTURE_FADE_RATE;
+            }
+            canvas.drawLine(xCoords[i - 1], yCoords[i - 1], xCoords[i], yCoords[i], paint);
+            if (i == size - 1) {
+                canvas.drawLine(lastX, lastY, xCoords[i], yCoords[i], paint);
+            }
+        }
+    }
+
+    public Rect getDrawingRect() {
+        return mDrawingRect;
+    }
 }
diff --git a/java/src/com/android/inputmethod/latin/Constants.java b/java/src/com/android/inputmethod/latin/Constants.java
index e79db367c3f193cb4a6c04f33cbccc879435329e..1242967ad409c5ac20c1e7a2ad871535a6dbff96 100644
--- a/java/src/com/android/inputmethod/latin/Constants.java
+++ b/java/src/com/android/inputmethod/latin/Constants.java
@@ -19,6 +19,13 @@ package com.android.inputmethod.latin;
 import android.view.inputmethod.EditorInfo;
 
 public final class Constants {
+    public static final class Color {
+        /**
+         * The alpha value for fully opaque.
+         */
+        public final static int ALPHA_OPAQUE = 255;
+    }
+
     public static final class ImeOption {
         /**
          * The private IME option used to indicate that no microphone should be shown for a given