Revise gesture preview trail design

Bug: 7042741
Change-Id: I99e3b3a6fc52afaee3cc5daf371131c3afebb3ae
diff --git a/java/res/values/attrs.xml b/java/res/values/attrs.xml
index e601397..7e8c77e 100644
--- a/java/res/values/attrs.xml
+++ b/java/res/values/attrs.xml
@@ -90,7 +90,8 @@
         <!-- Interval of updating gesture preview trail in millisecond. -->
         <attr name="gesturePreviewTrailUpdateInterval" format="integer" />
         <attr name="gesturePreviewTrailColor" format="color" />
-        <attr name="gesturePreviewTrailWidth" format="dimension" />
+        <attr name="gesturePreviewTrailStartWidth" format="dimension" />
+        <attr name="gesturePreviewTrailEndWidth" format="dimension" />
     </declare-styleable>
 
     <declare-styleable name="MainKeyboardView">
diff --git a/java/res/values/dimens.xml b/java/res/values/dimens.xml
index 9ccaaaf..d92a715 100644
--- a/java/res/values/dimens.xml
+++ b/java/res/values/dimens.xml
@@ -100,7 +100,8 @@
     <fraction name="center_suggestion_percentile">36%</fraction>
 
     <!-- Gesture preview trail parameters -->
-    <dimen name="gesture_preview_trail_width">2.5dp</dimen>
+    <dimen name="gesture_preview_trail_start_width">18.0dp</dimen>
+    <dimen name="gesture_preview_trail_end_width">2.5dp</dimen>
     <!-- Gesture floating preview text parameters -->
     <dimen name="gesture_floating_preview_text_size">24dp</dimen>
     <dimen name="gesture_floating_preview_text_offset">73dp</dimen>
diff --git a/java/res/values/styles.xml b/java/res/values/styles.xml
index 94fdbeb..ed92440 100644
--- a/java/res/values/styles.xml
+++ b/java/res/values/styles.xml
@@ -77,7 +77,8 @@
         <item name="gesturePreviewTrailFadeoutDuration">@integer/config_gesture_preview_trail_fadeout_duration</item>
         <item name="gesturePreviewTrailUpdateInterval">@integer/config_gesture_preview_trail_update_interval</item>
         <item name="gesturePreviewTrailColor">@android:color/holo_blue_light</item>
-        <item name="gesturePreviewTrailWidth">@dimen/gesture_preview_trail_width</item>
+        <item name="gesturePreviewTrailStartWidth">@dimen/gesture_preview_trail_start_width</item>
+        <item name="gesturePreviewTrailEndWidth">@dimen/gesture_preview_trail_end_width</item>
         <!-- Common attributes of MainKeyboardView -->
         <item name="keyHysteresisDistance">@dimen/config_key_hysteresis_distance</item>
         <item name="touchNoiseThresholdTime">@integer/config_touch_noise_threshold_time</item>
diff --git a/java/src/com/android/inputmethod/keyboard/internal/GesturePreviewTrail.java b/java/src/com/android/inputmethod/keyboard/internal/GesturePreviewTrail.java
index e814d80..7442b7f 100644
--- a/java/src/com/android/inputmethod/keyboard/internal/GesturePreviewTrail.java
+++ b/java/src/com/android/inputmethod/keyboard/internal/GesturePreviewTrail.java
@@ -17,16 +17,18 @@
 import android.content.res.TypedArray;
 import android.graphics.Canvas;
 import android.graphics.Paint;
+import android.graphics.PorterDuff;
+import android.graphics.PorterDuffXfermode;
+import android.graphics.Xfermode;
 import android.os.SystemClock;
 
 import com.android.inputmethod.latin.Constants;
 import com.android.inputmethod.latin.R;
 import com.android.inputmethod.latin.ResizableIntArray;
 
-class GesturePreviewTrail {
+final class GesturePreviewTrail {
     private static final int DEFAULT_CAPACITY = GestureStrokeWithPreviewTrail.PREVIEW_CAPACITY;
 
-    private final GesturePreviewTrailParams mPreviewParams;
     private final ResizableIntArray mXCoordinates = new ResizableIntArray(DEFAULT_CAPACITY);
     private final ResizableIntArray mYCoordinates = new ResizableIntArray(DEFAULT_CAPACITY);
     private final ResizableIntArray mEventTimes = new ResizableIntArray(DEFAULT_CAPACITY);
@@ -34,28 +36,39 @@
     private long mCurrentDownTime;
     private int mTrailStartIndex;
 
+    private final static Xfermode PORTER_DUFF_MODE_SRC =
+            new PorterDuffXfermode(PorterDuff.Mode.SRC);
+
     // Use this value as imaginary zero because x-coordinates may be zero.
     private static final int DOWN_EVENT_MARKER = -128;
 
-    static class GesturePreviewTrailParams {
+    static final class Params {
+        public final int mTrailColor;
+        public final float mTrailStartWidth;
+        public final float mTrailEndWidth;
         public final int mFadeoutStartDelay;
         public final int mFadeoutDuration;
         public final int mUpdateInterval;
 
-        public GesturePreviewTrailParams(final TypedArray keyboardViewAttr) {
+        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);
+            mTrailLingerDuration = mFadeoutStartDelay + mFadeoutDuration;
             mUpdateInterval = keyboardViewAttr.getInt(
                     R.styleable.KeyboardView_gesturePreviewTrailUpdateInterval, 0);
         }
     }
 
-    public GesturePreviewTrail(final GesturePreviewTrailParams params) {
-        mPreviewParams = params;
-    }
-
     private static int markAsDownEvent(final int xCoord) {
         return DOWN_EVENT_MARKER - xCoord;
     }
@@ -94,23 +107,30 @@
         }
     }
 
-    private int getAlpha(final int elapsedTime) {
-        if (elapsedTime < mPreviewParams.mFadeoutStartDelay) {
+    private static int getAlpha(final int elapsedTime, final Params params) {
+        if (elapsedTime < params.mFadeoutStartDelay) {
             return Constants.Color.ALPHA_OPAQUE;
         }
         final int decreasingAlpha = Constants.Color.ALPHA_OPAQUE
-                * (elapsedTime - mPreviewParams.mFadeoutStartDelay)
-                / mPreviewParams.mFadeoutDuration;
+                * (elapsedTime - params.mFadeoutStartDelay)
+                / params.mFadeoutDuration;
         return Constants.Color.ALPHA_OPAQUE - decreasingAlpha;
     }
 
+    private static float getWidth(final int elapsedTime, final Params params) {
+        return Math.max((params.mTrailLingerDuration - elapsedTime)
+                * (params.mTrailStartWidth - params.mTrailEndWidth)
+                / params.mTrailLingerDuration, 0.0f);
+    }
+
     /**
      * Draw gesture preview trail
      * @param canvas The canvas to draw the gesture preview trail
      * @param paint The paint object to be used to draw the gesture preview trail
+     * @param params The drawing parameters of gesture preview trail
      * @return true if some gesture preview trails remain to be drawn
      */
-    public boolean drawGestureTrail(final Canvas canvas, final Paint paint) {
+    public boolean drawGestureTrail(final Canvas canvas, final Paint paint, final Params params) {
         final int trailSize = mEventTimes.getLength();
         if (trailSize == 0) {
             return false;
@@ -120,13 +140,11 @@
         final int[] xCoords = mXCoordinates.getPrimitiveArray();
         final int[] yCoords = mYCoordinates.getPrimitiveArray();
         final int sinceDown = (int)(SystemClock.uptimeMillis() - mCurrentDownTime);
-        final int lingeringDuration = mPreviewParams.mFadeoutStartDelay
-                + mPreviewParams.mFadeoutDuration;
         int startIndex;
         for (startIndex = mTrailStartIndex; startIndex < trailSize; startIndex++) {
             final int elapsedTime = sinceDown - eventTimes[startIndex];
             // Skip too old trail points.
-            if (elapsedTime < lingeringDuration) {
+            if (elapsedTime < params.mTrailLingerDuration) {
                 break;
             }
         }
@@ -135,13 +153,20 @@
         if (startIndex < trailSize) {
             int lastX = getXCoordValue(xCoords[startIndex]);
             int lastY = yCoords[startIndex];
+            paint.setColor(params.mTrailColor);
+            paint.setStyle(Paint.Style.STROKE);
+            paint.setStrokeCap(Paint.Cap.ROUND);
+            paint.setXfermode(PORTER_DUFF_MODE_SRC);
             for (int i = startIndex + 1; i < trailSize - 1; i++) {
                 final int x = xCoords[i];
                 final int y = yCoords[i];
                 final int elapsedTime = sinceDown - eventTimes[i];
                 // Draw trail line only when the current point isn't a down point.
                 if (!isDownEventXCoord(x)) {
-                    paint.setAlpha(getAlpha(elapsedTime));
+                    final int alpha = getAlpha(elapsedTime, params);
+                    paint.setAlpha(alpha);
+                    final float width = getWidth(elapsedTime, params);
+                    paint.setStrokeWidth(width);
                     canvas.drawLine(lastX, lastY, x, y, paint);
                 }
                 lastX = getXCoordValue(x);
diff --git a/java/src/com/android/inputmethod/keyboard/internal/PreviewPlacerView.java b/java/src/com/android/inputmethod/keyboard/internal/PreviewPlacerView.java
index 641fadf..8dde4d6 100644
--- a/java/src/com/android/inputmethod/keyboard/internal/PreviewPlacerView.java
+++ b/java/src/com/android/inputmethod/keyboard/internal/PreviewPlacerView.java
@@ -21,6 +21,8 @@
 import android.graphics.Canvas;
 import android.graphics.Paint;
 import android.graphics.Paint.Align;
+import android.graphics.PorterDuff;
+import android.graphics.PorterDuffXfermode;
 import android.graphics.Rect;
 import android.graphics.RectF;
 import android.os.Message;
@@ -30,14 +32,12 @@
 import android.widget.RelativeLayout;
 
 import com.android.inputmethod.keyboard.PointerTracker;
-import com.android.inputmethod.keyboard.internal.GesturePreviewTrail.GesturePreviewTrailParams;
+import com.android.inputmethod.keyboard.internal.GesturePreviewTrail.Params;
 import com.android.inputmethod.latin.CollectionUtils;
 import com.android.inputmethod.latin.R;
 import com.android.inputmethod.latin.StaticInnerHandlerWrapper;
 
 public class PreviewPlacerView extends RelativeLayout {
-    private final Paint mGesturePaint;
-    private final Paint mTextPaint;
     private final int mGestureFloatingPreviewTextColor;
     private final int mGestureFloatingPreviewTextOffset;
     private final int mGestureFloatingPreviewColor;
@@ -51,8 +51,11 @@
 
     private final SparseArray<GesturePreviewTrail> mGesturePreviewTrails =
             CollectionUtils.newSparseArray();
-    private final GesturePreviewTrailParams mGesturePreviewTrailParams;
+    private final Params mGesturePreviewTrailParams;
+    private final Paint mGesturePaint;
+    private boolean mDrawsGesturePreviewTrail;
 
+    private final Paint mTextPaint;
     private String mGestureFloatingPreviewText;
     private final int mGestureFloatingPreviewTextHeight;
     // {@link RectF} is needed for {@link Canvas#drawRoundRect(RectF, float, float, Paint)}.
@@ -60,8 +63,6 @@
     private int mLastPointerX;
     private int mLastPointerY;
     private static final char[] TEXT_HEIGHT_REFERENCE_CHAR = { 'M' };
-
-    private boolean mDrawsGesturePreviewTrail;
     private boolean mDrawsGestureFloatingPreviewText;
 
     private final DrawingHandler mDrawingHandler;
@@ -70,10 +71,10 @@
         private static final int MSG_DISMISS_GESTURE_FLOATING_PREVIEW_TEXT = 0;
         private static final int MSG_UPDATE_GESTURE_PREVIEW_TRAIL = 1;
 
-        private final GesturePreviewTrailParams mGesturePreviewTrailParams;
+        private final Params mGesturePreviewTrailParams;
 
         public DrawingHandler(final PreviewPlacerView outerInstance,
-                final GesturePreviewTrailParams gesturePreviewTrailParams) {
+                final Params gesturePreviewTrailParams) {
             super(outerInstance);
             mGesturePreviewTrailParams = gesturePreviewTrailParams;
         }
@@ -146,21 +147,13 @@
                 R.styleable.KeyboardView_gestureFloatingPreviewRoundRadius, 0.0f);
         mGestureFloatingPreviewTextLingerTimeout = keyboardViewAttr.getInt(
                 R.styleable.KeyboardView_gestureFloatingPreviewTextLingerTimeout, 0);
-        final int gesturePreviewTrailColor = keyboardViewAttr.getColor(
-                R.styleable.KeyboardView_gesturePreviewTrailColor, 0);
-        final int gesturePreviewTrailWidth = keyboardViewAttr.getDimensionPixelSize(
-                R.styleable.KeyboardView_gesturePreviewTrailWidth, 0);
-        mGesturePreviewTrailParams = new GesturePreviewTrailParams(keyboardViewAttr);
+        mGesturePreviewTrailParams = new Params(keyboardViewAttr);
         keyboardViewAttr.recycle();
 
         mDrawingHandler = new DrawingHandler(this, mGesturePreviewTrailParams);
 
         final Paint gesturePaint = new Paint();
         gesturePaint.setAntiAlias(true);
-        gesturePaint.setStyle(Paint.Style.STROKE);
-        gesturePaint.setStrokeJoin(Paint.Join.ROUND);
-        gesturePaint.setColor(gesturePreviewTrailColor);
-        gesturePaint.setStrokeWidth(gesturePreviewTrailWidth);
         mGesturePaint = gesturePaint;
 
         final Paint textPaint = new Paint();
@@ -172,6 +165,10 @@
         final Rect textRect = new Rect();
         textPaint.getTextBounds(TEXT_HEIGHT_REFERENCE_CHAR, 0, 1, textRect);
         mGestureFloatingPreviewTextHeight = textRect.height();
+
+        final Paint layerPaint = new Paint();
+        layerPaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.SRC_OVER));
+        setLayerType(LAYER_TYPE_HARDWARE, layerPaint);
     }
 
     public void setOrigin(final int x, final int y) {
@@ -190,7 +187,7 @@
         synchronized (mGesturePreviewTrails) {
             trail = mGesturePreviewTrails.get(tracker.mPointerId);
             if (trail == null) {
-                trail = new GesturePreviewTrail(mGesturePreviewTrailParams);
+                trail = new GesturePreviewTrail();
                 mGesturePreviewTrails.put(tracker.mPointerId, trail);
             }
         }
@@ -214,7 +211,8 @@
                 for (int index = 0; index < trailsCount; index++) {
                     final GesturePreviewTrail trail = mGesturePreviewTrails.valueAt(index);
                     needsUpdatingGesturePreviewTrail |=
-                            trail.drawGestureTrail(canvas, mGesturePaint);
+                            trail.drawGestureTrail(canvas, mGesturePaint,
+                                    mGesturePreviewTrailParams);
                 }
             }
             if (needsUpdatingGesturePreviewTrail) {