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

Commit 3088e0c9 authored by Tadashi G. Takaoka's avatar Tadashi G. Takaoka Committed by Android (Google) Code Review
Browse files

Merge "Fix gesture start detection algorithm" into jb-mr1-dev

parents 8ae8c761 02a67200
Loading
Loading
Loading
Loading
+5 −5
Original line number Diff line number Diff line
@@ -688,7 +688,7 @@ public class PointerTracker implements PointerTrackerQueue.Element {
        mIsDetectingGesture = true;
        final int elapsedTimeFromFirstDown = (int)(eventTime - sGestureFirstDownTime);
        mGestureStrokeWithPreviewPoints.addPoint(x, y, elapsedTimeFromFirstDown,
                false /* isHistorical */);
                true /* isMajorEvent */);
    }

    private void onDownEventInternal(final int x, final int y, final long eventTime) {
@@ -724,10 +724,10 @@ public class PointerTracker implements PointerTrackerQueue.Element {
    }

    private void onGestureMoveEvent(final int x, final int y, final long eventTime,
            final boolean isHistorical, final Key key) {
            final boolean isMajorEvent, final Key key) {
        final int gestureTime = (int)(eventTime - sGestureFirstDownTime);
        if (mIsDetectingGesture) {
            mGestureStrokeWithPreviewPoints.addPoint(x, y, gestureTime, isHistorical);
            mGestureStrokeWithPreviewPoints.addPoint(x, y, gestureTime, isMajorEvent);
            mayStartBatchInput();
            if (sInGesture && key != null) {
                updateBatchInput(eventTime);
@@ -752,7 +752,7 @@ public class PointerTracker implements PointerTrackerQueue.Element {
                final int historicalY = (int)me.getHistoricalY(pointerIndex, h);
                final long historicalTime = me.getHistoricalEventTime(h);
                onGestureMoveEvent(historicalX, historicalY, historicalTime,
                        true /* isHistorical */, null);
                        false /* isMajorEvent */, null);
            }
        }

@@ -767,7 +767,7 @@ public class PointerTracker implements PointerTrackerQueue.Element {

        if (sShouldHandleGesture) {
            // Register move event on gesture tracker.
            onGestureMoveEvent(x, y, eventTime, false /* isHistorical */, key);
            onGestureMoveEvent(x, y, eventTime, true /* isMajorEvent */, key);
            if (sInGesture) {
                mIgnoreModifierKey = true;
                mTimerProxy.cancelLongPressTimer();
+114 −47
Original line number Diff line number Diff line
@@ -14,34 +14,45 @@

package com.android.inputmethod.keyboard.internal;

import android.util.Log;

import com.android.inputmethod.latin.InputPointers;
import com.android.inputmethod.latin.ResizableIntArray;

public class GestureStroke {
    private static final String TAG = GestureStroke.class.getSimpleName();
    private static final boolean DEBUG = false;

    public static final int DEFAULT_CAPACITY = 128;

    private final int mPointerId;
    private final ResizableIntArray mEventTimes = new ResizableIntArray(DEFAULT_CAPACITY);
    private final ResizableIntArray mXCoordinates = new ResizableIntArray(DEFAULT_CAPACITY);
    private final ResizableIntArray mYCoordinates = new ResizableIntArray(DEFAULT_CAPACITY);
    private float mLength;
    private int mIncrementalRecognitionSize;
    private int mLastIncrementalBatchSize;
    private long mLastPointTime;
    private int mLastPointX;
    private int mLastPointY;

    private int mMinGestureLength; // pixel
    private int mMinGestureSampleLength; // pixel
    private int mGestureRecognitionThreshold; // pixel / sec
    private long mLastMajorEventTime;
    private int mLastMajorEventX;
    private int mLastMajorEventY;

    private int mKeyWidth;
    private int mStartGestureLengthThreshold; // pixel
    private int mMinGestureSamplingLength; // pixel
    private int mGestureRecognitionSpeedThreshold; // pixel / sec
    private int mDetectFastMoveSpeedThreshold; // pixel /sec
    private int mDetectFastMoveTime;
    private int mDetectFastMoveX;
    private int mDetectFastMoveY;

    // TODO: Move some of these to resource.
    private static final float MIN_GESTURE_LENGTH_RATIO_TO_KEY_WIDTH = 0.75f;
    private static final int MIN_GESTURE_START_DURATION = 100; // msec
    private static final float START_GESTURE_LENGTH_THRESHOLD_RATIO_TO_KEY_WIDTH = 0.75f;
    private static final int START_GESTURE_DURATION_THRESHOLD = 70; // msec
    private static final int MIN_GESTURE_RECOGNITION_TIME = 100; // msec
    private static final float MIN_GESTURE_SAMPLING_RATIO_TO_KEY_WIDTH = 1.0f / 6.0f;
    private static final float GESTURE_RECOGNITION_SPEED_THRESHOLD_RATIO_TO_KEY_WIDTH =
            5.5f; // keyWidth / sec
    private static final float DETECT_FAST_MOVE_SPEED_THRESHOLD_RATIO_TO_KEY_WIDTH =
            5.0f; // keyWidth / sec
    private static final int MSEC_PER_SEC = 1000;

    public static final boolean hasRecognitionTimePast(
@@ -54,65 +65,121 @@ public class GestureStroke {
    }

    public void setKeyboardGeometry(final int keyWidth) {
        mKeyWidth = 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);
        mGestureRecognitionThreshold =
        mStartGestureLengthThreshold =
                (int)(keyWidth * START_GESTURE_LENGTH_THRESHOLD_RATIO_TO_KEY_WIDTH);
        mMinGestureSamplingLength = (int)(keyWidth * MIN_GESTURE_SAMPLING_RATIO_TO_KEY_WIDTH);
        mGestureRecognitionSpeedThreshold =
                (int)(keyWidth * GESTURE_RECOGNITION_SPEED_THRESHOLD_RATIO_TO_KEY_WIDTH);
        mDetectFastMoveSpeedThreshold =
                (int)(keyWidth * DETECT_FAST_MOVE_SPEED_THRESHOLD_RATIO_TO_KEY_WIDTH);
        if (DEBUG) {
            Log.d(TAG, "setKeyboardGeometry: keyWidth=" + keyWidth);
        }
    }

    public boolean isStartOfAGesture() {
        if (mDetectFastMoveTime == 0) {
            return false;
        }
        final int size = mEventTimes.getLength();
        final int downDuration = (size > 0) ? mEventTimes.get(size - 1) : 0;
        return downDuration > MIN_GESTURE_START_DURATION && mLength > mMinGestureLength;
        if (size <= 0) {
            return false;
        }
        final int lastIndex = size - 1;
        final int deltaTime = mEventTimes.get(lastIndex) - mDetectFastMoveTime;
        final int deltaLength = getDistance(
                mXCoordinates.get(lastIndex), mYCoordinates.get(lastIndex),
                mDetectFastMoveX, mDetectFastMoveY);
        final boolean isStartOfAGesture = deltaTime > START_GESTURE_DURATION_THRESHOLD
                && deltaLength > mStartGestureLengthThreshold;
        if (DEBUG) {
            Log.d(TAG, "isStartOfAGesture: dT=" + deltaTime + " dL=" + deltaLength
                    + " points=" + size + (isStartOfAGesture ? " Detect start of a gesture" : ""));
        }
        return isStartOfAGesture;
    }

    public void reset() {
        mLength = 0;
        mIncrementalRecognitionSize = 0;
        mLastIncrementalBatchSize = 0;
        mLastPointTime = 0;
        mEventTimes.setLength(0);
        mXCoordinates.setLength(0);
        mYCoordinates.setLength(0);
        mLastMajorEventTime = 0;
        mDetectFastMoveTime = 0;
    }

    public void addPoint(final int x, final int y, final int time, final boolean isHistorical) {
        final boolean needsSampling;
    private void appendPoint(final int x, final int y, final int time) {
        mEventTimes.add(time);
        mXCoordinates.add(x);
        mYCoordinates.add(y);
    }

    private void updateMajorEvent(final int x, final int y, final int time) {
        mLastMajorEventTime = time;
        mLastMajorEventX = x;
        mLastMajorEventY = y;
    }

    private int detectFastMove(final int x, final int y, final int time) {
        final int size = mEventTimes.getLength();
        if (size == 0) {
            needsSampling = true;
        } else {
        final int lastIndex = size - 1;
        final int lastX = mXCoordinates.get(lastIndex);
        final int lastY = mYCoordinates.get(lastIndex);
            final float dist = getDistance(lastX, lastY, x, y);
            needsSampling = dist > mMinGestureSampleLength;
            mLength += dist;
        final int dist = getDistance(lastX, lastY, x, y);
        final int msecs = time - mEventTimes.get(lastIndex);
        if (msecs > 0) {
            final int pixels = getDistance(lastX, lastY, x, y);
            final int pixelsPerSec = pixels * MSEC_PER_SEC;
            if (DEBUG) {
                final float speed = (float)pixelsPerSec / msecs / mKeyWidth;
                Log.d(TAG, String.format("Speed=%.3f keyWidth/sec", speed));
            }
            // Equivalent to (pixels / msecs < mStartSpeedThreshold / MSEC_PER_SEC)
            if (mDetectFastMoveTime == 0 && pixelsPerSec > mDetectFastMoveSpeedThreshold * msecs) {
                if (DEBUG) {
                    Log.d(TAG, "Detect fast move: T=" + time + " points = " + size);
                }
                mDetectFastMoveTime = time;
                mDetectFastMoveX = x;
                mDetectFastMoveY = y;
            }
        }
        return dist;
    }

    public void addPoint(final int x, final int y, final int time, final boolean isMajorEvent) {
        final int size = mEventTimes.getLength();
        if (size <= 0) {
            // Down event
            appendPoint(x, y, time);
            updateMajorEvent(x, y, time);
        } else {
            final int dist = detectFastMove(x, y, time);
            if (dist > mMinGestureSamplingLength) {
                appendPoint(x, y, time);
            }
        if (needsSampling) {
            mEventTimes.add(time);
            mXCoordinates.add(x);
            mYCoordinates.add(y);
        }
        if (!isHistorical) {
        if (isMajorEvent) {
            updateIncrementalRecognitionSize(x, y, time);
            updateMajorEvent(x, y, time);
        }
    }

    private void updateIncrementalRecognitionSize(final int x, final int y, final int time) {
        final int msecs = (int)(time - mLastPointTime);
        if (msecs > 0) {
            final int pixels = (int)getDistance(mLastPointX, mLastPointY, x, y);
        final int msecs = (int)(time - mLastMajorEventTime);
        if (msecs <= 0) {
            return;
        }
        final int pixels = getDistance(mLastMajorEventX, mLastMajorEventY, x, y);
        final int pixelsPerSec = pixels * MSEC_PER_SEC;
        // Equivalent to (pixels / msecs < mGestureRecognitionThreshold / MSEC_PER_SEC)
            if (pixels * MSEC_PER_SEC < mGestureRecognitionThreshold * msecs) {
        if (pixelsPerSec < mGestureRecognitionSpeedThreshold * msecs) {
            mIncrementalRecognitionSize = mEventTimes.getLength();
        }
    }
        mLastPointTime = time;
        mLastPointX = x;
        mLastPointY = y;
    }

    public void appendAllBatchPoints(final InputPointers out) {
        appendBatchPoints(out, mEventTimes.getLength());
@@ -132,11 +199,11 @@ public class GestureStroke {
        mLastIncrementalBatchSize = size;
    }

    private static float getDistance(final int x1, final int y1, final int x2, final int y2) {
        final float dx = x1 - x2;
        final float dy = y1 - y2;
    private static int getDistance(final int x1, final int y1, final int x2, final int y2) {
        final int dx = x1 - x2;
        final int dy = y1 - y2;
        // Note that, in recent versions of Android, FloatMath is actually slower than
        // java.lang.Math due to the way the JIT optimizes java.lang.Math.
        return (float)Math.sqrt(dx * dx + dy * dy);
        return (int)Math.sqrt(dx * dx + dy * dy);
    }
}
+6 −9
Original line number Diff line number Diff line
@@ -65,21 +65,18 @@ public class GestureStrokeWithPreviewPoints extends GestureStroke {
    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;
        return dx * dx + dy * dy >= mMinPreviewSampleLengthSquare;
    }

    @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)) {
    public void addPoint(final int x, final int y, final int time, final boolean isMajorEvent) {
        super.addPoint(x, y, time, isMajorEvent);
        if (isMajorEvent || needsSampling(x, y)) {
            mPreviewEventTimes.add(time);
            mPreviewXCoordinates.add(x);
            mPreviewYCoordinates.add(y);
            mLastX = x;
            mLastY = y;
        }
    }

+1 −1
Original line number Diff line number Diff line
@@ -45,7 +45,7 @@ public class BinaryDictionary extends Dictionary {
    public static final int MAX_WORDS = 18;
    public static final int MAX_SPACES = 16;

    private static final String TAG = "BinaryDictionary";
    private static final String TAG = BinaryDictionary.class.getSimpleName();
    private static final int MAX_PREDICTIONS = 60;
    private static final int MAX_RESULTS = Math.max(MAX_PREDICTIONS, MAX_WORDS);

+1 −1
Original line number Diff line number Diff line
@@ -42,7 +42,7 @@ import java.util.concurrent.locks.ReentrantLock;
 * cancellation or manual picks. This allows the keyboard to adapt to the typist over time.
 */
public class UserHistoryDictionary extends ExpandableDictionary {
    private static final String TAG = "UserHistoryDictionary";
    private static final String TAG = UserHistoryDictionary.class.getSimpleName();
    public static final boolean DBG_SAVE_RESTORE = false;
    public static final boolean DBG_STRESS_TEST = false;
    public static final boolean DBG_ALWAYS_WRITE = false;