Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit c7dc673c authored by Tadashi G. Takaoka's avatar Tadashi G. Takaoka
Browse files

Use Path to draw gesture preview trail

This change also reduces the number of points to use as preview trail.

Bug: 7167303
Change-Id: I0cf4908efa44b17b42d4fddd6725238236ac2654
parent 567204a1
Loading
Loading
Loading
Loading
+25 −22
Original line number Diff line number Diff line
@@ -23,7 +23,7 @@ import android.view.MotionEvent;

import com.android.inputmethod.accessibility.AccessibilityUtils;
import com.android.inputmethod.keyboard.internal.GestureStroke;
import com.android.inputmethod.keyboard.internal.GestureStrokeWithPreviewTrail;
import com.android.inputmethod.keyboard.internal.GestureStrokeWithPreviewPoints;
import com.android.inputmethod.keyboard.internal.PointerTrackerQueue;
import com.android.inputmethod.latin.CollectionUtils;
import com.android.inputmethod.latin.InputPointers;
@@ -208,7 +208,7 @@ public class PointerTracker implements PointerTrackerQueue.Element {
    private static final KeyboardActionListener EMPTY_LISTENER =
            new KeyboardActionListener.Adapter();

    private final GestureStrokeWithPreviewTrail mGestureStrokeWithPreviewTrail;
    private final GestureStrokeWithPreviewPoints mGestureStrokeWithPreviewPoints;

    public static void init(boolean hasDistinctMultitouch,
            boolean needsPhantomSuddenMoveEventHack) {
@@ -293,7 +293,7 @@ public class PointerTracker implements PointerTrackerQueue.Element {
            throw new NullPointerException();
        }
        mPointerId = id;
        mGestureStrokeWithPreviewTrail = new GestureStrokeWithPreviewTrail(id);
        mGestureStrokeWithPreviewPoints = new GestureStrokeWithPreviewPoints(id);
        setKeyDetectorInner(handler.getKeyDetector());
        mListener = handler.getKeyboardActionListener();
        mDrawingProxy = handler.getDrawingProxy();
@@ -392,7 +392,7 @@ public class PointerTracker implements PointerTrackerQueue.Element {
    private void setKeyDetectorInner(final KeyDetector keyDetector) {
        mKeyDetector = keyDetector;
        mKeyboard = keyDetector.getKeyboard();
        mGestureStrokeWithPreviewTrail.setGestureSampleLength(mKeyboard.mMostCommonKeyWidth);
        mGestureStrokeWithPreviewPoints.setKeyboardGeometry(mKeyboard.mMostCommonKeyWidth);
        final Key newKey = mKeyDetector.detectHitKey(mKeyX, mKeyY);
        if (newKey != mCurrentKey) {
            if (mDrawingProxy != null) {
@@ -502,8 +502,8 @@ public class PointerTracker implements PointerTrackerQueue.Element {
        mDrawingProxy.invalidateKey(key);
    }

    public GestureStrokeWithPreviewTrail getGestureStrokeWithPreviewTrail() {
        return mGestureStrokeWithPreviewTrail;
    public GestureStrokeWithPreviewPoints getGestureStrokeWithPreviewPoints() {
        return mGestureStrokeWithPreviewPoints;
    }

    public int getLastX() {
@@ -544,8 +544,8 @@ public class PointerTracker implements PointerTrackerQueue.Element {
        return (sPointerTrackerQueue == null) ? 1 : sPointerTrackerQueue.size();
    }

    private void startBatchInput() {
        if (sInGesture || !mGestureStrokeWithPreviewTrail.isStartOfAGesture()) {
    private void mayStartBatchInput() {
        if (sInGesture || !mGestureStrokeWithPreviewPoints.isStartOfAGesture()) {
            return;
        }
        if (DEBUG_LISTENER) {
@@ -559,7 +559,7 @@ public class PointerTracker implements PointerTrackerQueue.Element {

    private void updateBatchInput(final long eventTime) {
        synchronized (sAggregratedPointers) {
            mGestureStrokeWithPreviewTrail.appendIncrementalBatchPoints(sAggregratedPointers);
            mGestureStrokeWithPreviewPoints.appendIncrementalBatchPoints(sAggregratedPointers);
            final int size = sAggregratedPointers.getPointerSize();
            if (size > sLastRecognitionPointSize
                    && eventTime > sLastRecognitionTime + MIN_GESTURE_RECOGNITION_TIME) {
@@ -575,10 +575,10 @@ public class PointerTracker implements PointerTrackerQueue.Element {
        mDrawingProxy.showGesturePreviewTrail(this, isOldestTracker);
    }

    private void endBatchInput() {
    private void mayEndBatchInput() {
        synchronized (sAggregratedPointers) {
            mGestureStrokeWithPreviewTrail.appendAllBatchPoints(sAggregratedPointers);
            mGestureStrokeWithPreviewTrail.reset();
            mGestureStrokeWithPreviewPoints.appendAllBatchPoints(sAggregratedPointers);
            mGestureStrokeWithPreviewPoints.reset();
            if (getActivePointerTrackerCount() == 1) {
                if (DEBUG_LISTENER) {
                    Log.d(TAG, "onEndBatchInput: batchPoints="
@@ -601,7 +601,7 @@ public class PointerTracker implements PointerTrackerQueue.Element {
        final int trackersSize = sTrackers.size();
        for (int i = 0; i < trackersSize; ++i) {
            final PointerTracker tracker = sTrackers.get(i);
            tracker.mGestureStrokeWithPreviewTrail.reset();
            tracker.mGestureStrokeWithPreviewPoints.reset();
        }
        sAggregratedPointers.reset();
        sLastRecognitionPointSize = 0;
@@ -678,17 +678,20 @@ public class PointerTracker implements PointerTrackerQueue.Element {
                    && mKeyboard.mId.isAlphabetKeyboard();
            if (isAlphabetKeyboard && !mIsShowingMoreKeysPanel && key != null
                    && Keyboard.isLetterCode(key.mCode)) {
                mIsDetectingGesture = true;
                sGestureFirstDownTime = eventTime;
                mGestureStrokeWithPreviewTrail.addPoint(x, y, 0, false /* isHistorical */);
                onGestureDownEvent(x, y, eventTime);
            }
        } else if (sInGesture && activePointerTrackerCount > 1) {
            onGestureDownEvent(x, y, eventTime);
        }
    }

    private void onGestureDownEvent(final int x, final int y, final long eventTime) {
        mIsDetectingGesture = true;
        final int elapsedTimeFromFirstDown = (int)(eventTime - sGestureFirstDownTime);
            mGestureStrokeWithPreviewTrail.addPoint(x, y, elapsedTimeFromFirstDown,
        mGestureStrokeWithPreviewPoints.addPoint(x, y, elapsedTimeFromFirstDown,
                false /* isHistorical */);
    }
    }

    private void onDownEventInternal(final int x, final int y, final long eventTime) {
        Key key = onDownKey(x, y, eventTime);
@@ -726,8 +729,8 @@ public class PointerTracker implements PointerTrackerQueue.Element {
            final boolean isHistorical, final Key key) {
        final int gestureTime = (int)(eventTime - sGestureFirstDownTime);
        if (mIsDetectingGesture) {
            mGestureStrokeWithPreviewTrail.addPoint(x, y, gestureTime, isHistorical);
            startBatchInput();
            mGestureStrokeWithPreviewPoints.addPoint(x, y, gestureTime, isHistorical);
            mayStartBatchInput();
            if (sInGesture && key != null) {
                updateBatchInput(eventTime);
            }
@@ -919,7 +922,7 @@ public class PointerTracker implements PointerTrackerQueue.Element {
            if (currentKey != null) {
                callListenerOnRelease(currentKey, currentKey.mCode, true);
            }
            endBatchInput();
            mayEndBatchInput();
            return;
        }
        // This event will be recognized as a regular code input. Clear unused possible batch points
+119 −16
Original line number Diff line number Diff line
@@ -17,7 +17,9 @@ package com.android.inputmethod.keyboard.internal;
import android.content.res.TypedArray;
import android.graphics.Canvas;
import android.graphics.Paint;
import android.graphics.Path;
import android.graphics.Rect;
import android.graphics.RectF;
import android.os.SystemClock;

import com.android.inputmethod.latin.Constants;
@@ -25,7 +27,7 @@ import com.android.inputmethod.latin.R;
import com.android.inputmethod.latin.ResizableIntArray;

final class GesturePreviewTrail {
    private static final int DEFAULT_CAPACITY = GestureStrokeWithPreviewTrail.PREVIEW_CAPACITY;
    private static final int DEFAULT_CAPACITY = GestureStrokeWithPreviewPoints.PREVIEW_CAPACITY;

    private final ResizableIntArray mXCoordinates = new ResizableIntArray(DEFAULT_CAPACITY);
    private final ResizableIntArray mYCoordinates = new ResizableIntArray(DEFAULT_CAPACITY);
@@ -78,7 +80,7 @@ final class GesturePreviewTrail {
                ? DOWN_EVENT_MARKER - xCoordOrMark : xCoordOrMark;
    }

    public void addStroke(final GestureStrokeWithPreviewTrail stroke, final long downTime) {
    public void addStroke(final GestureStrokeWithPreviewPoints stroke, final long downTime) {
        final int trailSize = mEventTimes.getLength();
        stroke.appendPreviewStroke(mEventTimes, mXCoordinates, mYCoordinates);
        if (mEventTimes.getLength() == trailSize) {
@@ -116,6 +118,99 @@ final class GesturePreviewTrail {
                / params.mTrailLingerDuration, 0.0f);
    }

    static final class WorkingSet {
        // Input
        // Previous point (P1) coordinates and trail radius.
        public float p1x, p1y;
        public float r1;
        // Current point (P2) coordinates and trail radius.
        public float p2x, p2y;
        public float r2;

        // Output
        // Closing point of arc at P1.
        public float p1ax, p1ay;
        // Opening point of arc at P1.
        public float p1bx, p1by;
        // Opening point of arc at P2.
        public float p2ax, p2ay;
        // Closing point of arc at P2.
        public float p2bx, p2by;
        // Start angle of the trail arcs.
        public float aa;
        // Sweep angle of the trail arc at P1.
        public float a1;
        public RectF arc1 = new RectF();
        // Sweep angle of the trail arc at P2.
        public float a2;
        public RectF arc2 = new RectF();
    }

    private static final float RIGHT_ANGLE = (float)(Math.PI / 2.0d);
    private static final float RADIAN_TO_DEGREE = (float)(180.0d / Math.PI);

    private static boolean calculatePathPoints(final WorkingSet w) {
        final float dx = w.p2x - w.p1x;
        final float dy = w.p2y - w.p1y;
        // Distance of the points.
        final double l = Math.hypot(dx, dy);
        if (Double.compare(0.0d, l) == 0) {
            return false;
        }
        // Angle of the line p1-p2
        final float a = (float)Math.atan2(dy, dx);
        // Difference of trail cap radius.
        final float dr = w.r2 - w.r1;
        // Variation of angle at trail cap.
        final float ar = (float)Math.asin(dr / l);
        // The start angle of trail cap arc at P1.
        final float aa = a - (RIGHT_ANGLE + ar);
        // The end angle of trail cap arc at P2.
        final float ab = a + (RIGHT_ANGLE + ar);
        final float cosa = (float)Math.cos(aa);
        final float sina = (float)Math.sin(aa);
        final float cosb = (float)Math.cos(ab);
        final float sinb = (float)Math.sin(ab);
        w.p1ax = w.p1x + w.r1 * cosa;
        w.p1ay = w.p1y + w.r1 * sina;
        w.p1bx = w.p1x + w.r1 * cosb;
        w.p1by = w.p1y + w.r1 * sinb;
        w.p2ax = w.p2x + w.r2 * cosa;
        w.p2ay = w.p2y + w.r2 * sina;
        w.p2bx = w.p2x + w.r2 * cosb;
        w.p2by = w.p2y + w.r2 * sinb;
        w.aa = aa * RADIAN_TO_DEGREE;
        final float ar2degree = ar * 2.0f * RADIAN_TO_DEGREE;
        w.a1 = -180.0f + ar2degree;
        w.a2 = 180.0f + ar2degree;
        w.arc1.set(w.p1x, w.p1y, w.p1x, w.p1y);
        w.arc1.inset(-w.r1, -w.r1);
        w.arc2.set(w.p2x, w.p2y, w.p2x, w.p2y);
        w.arc2.inset(-w.r2, -w.r2);
        return true;
    }

    private static void createPath(final Path path, final WorkingSet w) {
        path.rewind();
        // Trail cap at P1.
        path.moveTo(w.p1x, w.p1y);
        path.arcTo(w.arc1, w.aa, w.a1);
        // Trail cap at P2.
        path.moveTo(w.p2x, w.p2y);
        path.arcTo(w.arc2, w.aa, w.a2);
        // Two trapezoids connecting P1 and P2.
        path.moveTo(w.p1ax, w.p1ay);
        path.lineTo(w.p1x, w.p1y);
        path.lineTo(w.p1bx, w.p1by);
        path.lineTo(w.p2bx, w.p2by);
        path.lineTo(w.p2x, w.p2y);
        path.lineTo(w.p2ax, w.p2ay);
        path.close();
    }

    private final WorkingSet mWorkingSet = new WorkingSet();
    private final Path mPath = new Path();

    /**
     * Draw gesture preview trail
     * @param canvas The canvas to draw the gesture preview trail
@@ -147,30 +242,38 @@ final class GesturePreviewTrail {

        if (startIndex < trailSize) {
            paint.setColor(params.mTrailColor);
            paint.setStyle(Paint.Style.STROKE);
            paint.setStrokeCap(Paint.Cap.ROUND);
            int lastX = getXCoordValue(xCoords[startIndex]);
            int lastY = yCoords[startIndex];
            float maxWidth = getWidth(sinceDown - eventTimes[startIndex], params);
            paint.setStyle(Paint.Style.FILL);
            final Path path = mPath;
            final WorkingSet w = mWorkingSet;
            w.p1x = getXCoordValue(xCoords[startIndex]);
            w.p1y = yCoords[startIndex];
            int lastTime = sinceDown - eventTimes[startIndex];
            float maxWidth = getWidth(lastTime, params);
            w.r1 = maxWidth / 2.0f;
            // Initialize bounds rectangle.
            outBoundsRect.set(lastX, lastY, lastX, lastY);
            outBoundsRect.set((int)w.p1x, (int)w.p1y, (int)w.p1x, (int)w.p1y);
            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];
                w.p2x = getXCoordValue(xCoords[i]);
                w.p2y = yCoords[i];
                // Draw trail line only when the current point isn't a down point.
                if (!isDownEventXCoord(x)) {
                if (!isDownEventXCoord(xCoords[i])) {
                    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);
                    w.r2 = width / 2.0f;
                    if (calculatePathPoints(w)) {
                        createPath(path, w);
                        canvas.drawPath(path, paint);
                        outBoundsRect.union((int)w.p2x, (int)w.p2y);
                    }
                    // Take union for the bounds.
                    outBoundsRect.union(x, y);
                    maxWidth = Math.max(maxWidth, width);
                }
                lastX = getXCoordValue(x);
                lastY = y;
                w.p1x = w.p2x;
                w.p1y = w.p2y;
                w.r1 = w.r2;
                lastTime = elapsedTime;
            }
            // Take care of trail line width.
            final int inset = -((int)maxWidth + 1);
+1 −1
Original line number Diff line number Diff line
@@ -44,7 +44,7 @@ public class GestureStroke {
        mPointerId = pointerId;
    }

    public void setGestureSampleLength(final int keyWidth) {
    public void setKeyboardGeometry(final int keyWidth) {
        // TODO: Find an appropriate base metric for these length. Maybe diagonal length of the key?
        mMinGestureLength = (int)(keyWidth * MIN_GESTURE_LENGTH_RATIO_TO_KEY_WIDTH);
        mMinGestureSampleLength = (int)(keyWidth * MIN_GESTURE_SAMPLING_RATIO_TO_KEY_WIDTH);
+32 −5
Original line number Diff line number Diff line
@@ -16,7 +16,7 @@ package com.android.inputmethod.keyboard.internal;

import com.android.inputmethod.latin.ResizableIntArray;

public class GestureStrokeWithPreviewTrail extends GestureStroke {
public class GestureStrokeWithPreviewPoints extends GestureStroke {
    public static final int PREVIEW_CAPACITY = 256;

    private final ResizableIntArray mPreviewEventTimes = new ResizableIntArray(PREVIEW_CAPACITY);
@@ -26,7 +26,14 @@ public class GestureStrokeWithPreviewTrail extends GestureStroke {
    private int mStrokeId;
    private int mLastPreviewSize;

    public GestureStrokeWithPreviewTrail(final int pointerId) {
    private int mMinPreviewSampleLengthSquare;
    private int mLastX;
    private int mLastY;

    // TODO: Move this to resource.
    private static final float MIN_PREVIEW_SAMPLE_LENGTH_RATIO_TO_KEY_WIDTH = 0.1f;

    public GestureStrokeWithPreviewPoints(final int pointerId) {
        super(pointerId);
    }

@@ -48,13 +55,33 @@ public class GestureStrokeWithPreviewTrail extends GestureStroke {
        return mPreviewEventTimes.getLength();
    }

    @Override
    public void setKeyboardGeometry(final int keyWidth) {
        super.setKeyboardGeometry(keyWidth);
        final float sampleLength = keyWidth * MIN_PREVIEW_SAMPLE_LENGTH_RATIO_TO_KEY_WIDTH;
        mMinPreviewSampleLengthSquare = (int)(sampleLength * sampleLength);
    }

    private boolean needsSampling(final int x, final int y) {
        final int dx = x - mLastX;
        final int dy = y - mLastY;
        final boolean needsSampling = (dx * dx + dy * dy >= mMinPreviewSampleLengthSquare);
        if (needsSampling) {
            mLastX = x;
            mLastY = y;
        }
        return needsSampling;
    }

    @Override
    public void addPoint(final int x, final int y, final int time, final boolean isHistorical) {
        super.addPoint(x, y, time, isHistorical);
        if (mPreviewEventTimes.getLength() == 0 || isHistorical || needsSampling(x, y)) {
            mPreviewEventTimes.add(time);
            mPreviewXCoordinates.add(x);
            mPreviewYCoordinates.add(y);
        }
    }

    public void appendPreviewStroke(final ResizableIntArray eventTimes,
            final ResizableIntArray xCoords, final ResizableIntArray yCoords) {
+1 −1
Original line number Diff line number Diff line
@@ -205,7 +205,7 @@ public class PreviewPlacerView extends RelativeLayout {
                    mGesturePreviewTrails.put(tracker.mPointerId, trail);
                }
            }
            trail.addStroke(tracker.getGestureStrokeWithPreviewTrail(), tracker.getDownTime());
            trail.addStroke(tracker.getGestureStrokeWithPreviewPoints(), tracker.getDownTime());
        }

        // TODO: Should narrow the invalidate region.