From e2a6253cb581f9ab70cfb723d32b14f9ac7d2ab7 Mon Sep 17 00:00:00 2001 From: "Tadashi G. Takaoka" Date: Tue, 24 Dec 2013 18:31:49 +0900 Subject: Rename gesture related classes Change-Id: I5cb03576bb7221f1864e157857d872880a0a58f8 --- .../inputmethod/keyboard/PointerTracker.java | 48 +-- .../keyboard/internal/GestureStroke.java | 316 -------------------- .../internal/GestureStrokeDrawingParams.java | 58 ++++ .../internal/GestureStrokeDrawingPoints.java | 204 +++++++++++++ .../keyboard/internal/GestureStrokeParams.java | 93 ------ .../internal/GestureStrokePreviewParams.java | 50 ---- .../internal/GestureStrokeRecognitionParams.java | 109 +++++++ .../internal/GestureStrokeRecognitionPoints.java | 322 +++++++++++++++++++++ .../internal/GestureStrokeWithPreviewPoints.java | 198 ------------- .../keyboard/internal/GestureTrail.java | 318 -------------------- .../internal/GestureTrailDrawingParams.java | 79 +++++ .../internal/GestureTrailDrawingPoints.java | 276 ++++++++++++++++++ .../internal/GestureTrailsDrawingPreview.java | 34 +-- 13 files changed, 1089 insertions(+), 1016 deletions(-) delete mode 100644 java/src/com/android/inputmethod/keyboard/internal/GestureStroke.java create mode 100644 java/src/com/android/inputmethod/keyboard/internal/GestureStrokeDrawingParams.java create mode 100644 java/src/com/android/inputmethod/keyboard/internal/GestureStrokeDrawingPoints.java delete mode 100644 java/src/com/android/inputmethod/keyboard/internal/GestureStrokeParams.java delete mode 100644 java/src/com/android/inputmethod/keyboard/internal/GestureStrokePreviewParams.java create mode 100644 java/src/com/android/inputmethod/keyboard/internal/GestureStrokeRecognitionParams.java create mode 100644 java/src/com/android/inputmethod/keyboard/internal/GestureStrokeRecognitionPoints.java delete mode 100644 java/src/com/android/inputmethod/keyboard/internal/GestureStrokeWithPreviewPoints.java delete mode 100644 java/src/com/android/inputmethod/keyboard/internal/GestureTrail.java create mode 100644 java/src/com/android/inputmethod/keyboard/internal/GestureTrailDrawingParams.java create mode 100644 java/src/com/android/inputmethod/keyboard/internal/GestureTrailDrawingPoints.java (limited to 'java/src') diff --git a/java/src/com/android/inputmethod/keyboard/PointerTracker.java b/java/src/com/android/inputmethod/keyboard/PointerTracker.java index 8f43dce95..3fee6c6f9 100644 --- a/java/src/com/android/inputmethod/keyboard/PointerTracker.java +++ b/java/src/com/android/inputmethod/keyboard/PointerTracker.java @@ -24,10 +24,10 @@ import android.view.MotionEvent; import com.android.inputmethod.keyboard.internal.BogusMoveEventDetector; import com.android.inputmethod.keyboard.internal.GestureEnabler; -import com.android.inputmethod.keyboard.internal.GestureStroke; -import com.android.inputmethod.keyboard.internal.GestureStrokeParams; -import com.android.inputmethod.keyboard.internal.GestureStrokePreviewParams; -import com.android.inputmethod.keyboard.internal.GestureStrokeWithPreviewPoints; +import com.android.inputmethod.keyboard.internal.GestureStrokeDrawingParams; +import com.android.inputmethod.keyboard.internal.GestureStrokeDrawingPoints; +import com.android.inputmethod.keyboard.internal.GestureStrokeRecognitionParams; +import com.android.inputmethod.keyboard.internal.GestureStrokeRecognitionPoints; import com.android.inputmethod.keyboard.internal.PointerTrackerQueue; import com.android.inputmethod.keyboard.internal.TypingTimeRecorder; import com.android.inputmethod.latin.Constants; @@ -135,8 +135,8 @@ public final class PointerTracker implements PointerTrackerQueue.Element { // Parameters for pointer handling. private static PointerTrackerParams sParams; - private static GestureStrokeParams sGestureStrokeParams; - private static GestureStrokePreviewParams sGesturePreviewParams; + private static GestureStrokeRecognitionParams sGestureStrokeRecognitionParams; + private static GestureStrokeDrawingParams sGestureStrokeDrawingParams; private static boolean sNeedsPhantomSuddenMoveEventHack; // Move this threshold to resource. // TODO: Device specific parameter would be better for device specific hack? @@ -163,7 +163,7 @@ public final class PointerTracker implements PointerTrackerQueue.Element { private static long sGestureFirstDownTime; private static TypingTimeRecorder sTypingTimeRecorder; private static final InputPointers sAggregatedPointers = new InputPointers( - GestureStroke.DEFAULT_CAPACITY); + GestureStrokeRecognitionPoints.DEFAULT_CAPACITY); private static int sLastRecognitionPointSize = 0; // synchronized using sAggregatedPointers private static long sLastRecognitionTime = 0; // synchronized using sAggregatedPointers @@ -203,16 +203,16 @@ public final class PointerTracker implements PointerTrackerQueue.Element { // true if dragging finger is allowed. private boolean mIsAllowedDraggingFinger; - private final GestureStrokeWithPreviewPoints mGestureStrokeWithPreviewPoints; + private final GestureStrokeDrawingPoints mGestureStrokeDrawingPoints; // TODO: Add PointerTrackerFactory singleton and move some class static methods into it. public static void init(final TypedArray mainKeyboardViewAttr, final TimerProxy timerProxy, final DrawingProxy drawingProxy) { sParams = new PointerTrackerParams(mainKeyboardViewAttr); - sGestureStrokeParams = new GestureStrokeParams(mainKeyboardViewAttr); - sGesturePreviewParams = new GestureStrokePreviewParams(mainKeyboardViewAttr); + sGestureStrokeRecognitionParams = new GestureStrokeRecognitionParams(mainKeyboardViewAttr); + sGestureStrokeDrawingParams = new GestureStrokeDrawingParams(mainKeyboardViewAttr); sTypingTimeRecorder = new TypingTimeRecorder( - sGestureStrokeParams.mStaticTimeThresholdAfterFastTyping, + sGestureStrokeRecognitionParams.mStaticTimeThresholdAfterFastTyping, sParams.mSuppressKeyPreviewAfterBatchInputDuration); final Resources res = mainKeyboardViewAttr.getResources(); @@ -286,8 +286,8 @@ public final class PointerTracker implements PointerTrackerQueue.Element { private PointerTracker(final int id) { mPointerId = id; - mGestureStrokeWithPreviewPoints = new GestureStrokeWithPreviewPoints( - id, sGestureStrokeParams, sGesturePreviewParams); + mGestureStrokeDrawingPoints = new GestureStrokeDrawingPoints( + id, sGestureStrokeRecognitionParams, sGestureStrokeDrawingParams); } // Returns true if keyboard has been changed by this callback. @@ -408,7 +408,7 @@ public final class PointerTracker implements PointerTrackerQueue.Element { mKeyboardLayoutHasBeenChanged = true; final int keyWidth = mKeyboard.mMostCommonKeyWidth; final int keyHeight = mKeyboard.mMostCommonKeyHeight; - mGestureStrokeWithPreviewPoints.setKeyboardGeometry(keyWidth, mKeyboard.mOccupiedHeight); + mGestureStrokeDrawingPoints.setKeyboardGeometry(keyWidth, mKeyboard.mOccupiedHeight); final Key newKey = mKeyDetector.detectHitKey(mKeyX, mKeyY); if (newKey != mCurrentKey) { if (sDrawingProxy != null) { @@ -523,8 +523,8 @@ public final class PointerTracker implements PointerTrackerQueue.Element { sDrawingProxy.invalidateKey(key); } - public GestureStrokeWithPreviewPoints getGestureStrokeWithPreviewPoints() { - return mGestureStrokeWithPreviewPoints; + public GestureStrokeDrawingPoints getGestureStrokeDrawingPoints() { + return mGestureStrokeDrawingPoints; } public void getLastCoordinates(final int[] outCoords) { @@ -581,7 +581,7 @@ public final class PointerTracker implements PointerTrackerQueue.Element { * @return true if the batch input has started successfully. */ private boolean mayStartBatchInput() { - if (!mGestureStrokeWithPreviewPoints.isStartOfAGesture()) { + if (!mGestureStrokeDrawingPoints.isStartOfAGesture()) { return false; } if (DEBUG_LISTENER) { @@ -609,13 +609,13 @@ public final class PointerTracker implements PointerTrackerQueue.Element { public void updateBatchInputByTimer(final long syntheticMoveEventTime) { final int gestureTime = (int)(syntheticMoveEventTime - sGestureFirstDownTime); - mGestureStrokeWithPreviewPoints.duplicateLastPointWith(gestureTime); + mGestureStrokeDrawingPoints.duplicateLastPointWith(gestureTime); updateBatchInput(syntheticMoveEventTime); } private void updateBatchInput(final long moveEventTime) { synchronized (sAggregatedPointers) { - final GestureStroke stroke = mGestureStrokeWithPreviewPoints; + final GestureStrokeRecognitionPoints stroke = mGestureStrokeDrawingPoints; stroke.appendIncrementalBatchPoints(sAggregatedPointers); final int size = sAggregatedPointers.getPointerSize(); if (size > sLastRecognitionPointSize @@ -642,7 +642,7 @@ public final class PointerTracker implements PointerTrackerQueue.Element { private boolean mayEndBatchInput(final long upEventTime) { boolean hasEndBatchInputSuccessfully = false; synchronized (sAggregatedPointers) { - mGestureStrokeWithPreviewPoints.appendAllBatchPoints(sAggregatedPointers); + mGestureStrokeDrawingPoints.appendAllBatchPoints(sAggregatedPointers); if (getActivePointerTrackerCount() == 1) { hasEndBatchInputSuccessfully = true; sTypingTimeRecorder.onEndBatchInput(upEventTime); @@ -754,7 +754,7 @@ public final class PointerTracker implements PointerTrackerQueue.Element { if (getActivePointerTrackerCount() == 1) { sGestureFirstDownTime = eventTime; } - mGestureStrokeWithPreviewPoints.onDownEvent(x, y, eventTime, sGestureFirstDownTime, + mGestureStrokeDrawingPoints.onDownEvent(x, y, eventTime, sGestureFirstDownTime, sTypingTimeRecorder.getLastLetterTypingTime()); } } @@ -814,11 +814,11 @@ public final class PointerTracker implements PointerTrackerQueue.Element { if (!mIsDetectingGesture) { return; } - final int beforeLength = mGestureStrokeWithPreviewPoints.getLength(); + final int beforeLength = mGestureStrokeDrawingPoints.getLength(); final int gestureTime = (int)(eventTime - sGestureFirstDownTime); - final boolean onValidArea = mGestureStrokeWithPreviewPoints.addPointOnKeyboard( + final boolean onValidArea = mGestureStrokeDrawingPoints.addPointOnKeyboard( x, y, gestureTime, isMajorEvent); - if (mGestureStrokeWithPreviewPoints.getLength() > beforeLength) { + if (mGestureStrokeDrawingPoints.getLength() > beforeLength) { sTimerProxy.startUpdateBatchInputTimer(this); } // If the move event goes out from valid batch input area, cancel batch input. diff --git a/java/src/com/android/inputmethod/keyboard/internal/GestureStroke.java b/java/src/com/android/inputmethod/keyboard/internal/GestureStroke.java deleted file mode 100644 index 605d53f82..000000000 --- a/java/src/com/android/inputmethod/keyboard/internal/GestureStroke.java +++ /dev/null @@ -1,316 +0,0 @@ -/* - * Copyright (C) 2012 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.util.Log; - -import com.android.inputmethod.latin.InputPointers; -import com.android.inputmethod.latin.utils.ResizableIntArray; - -public class GestureStroke { - private static final String TAG = GestureStroke.class.getSimpleName(); - private static final boolean DEBUG = false; - private static final boolean DEBUG_SPEED = false; - - // The height of extra area above the keyboard to draw gesture trails. - // Proportional to the keyboard height. - public static final float EXTRA_GESTURE_TRAIL_AREA_ABOVE_KEYBOARD_RATIO = 0.25f; - - 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 final GestureStrokeParams mParams; - - private int mKeyWidth; // pixel - private int mMinYCoordinate; // pixel - private int mMaxYCoordinate; // pixel - // Static threshold for starting gesture detection - private int mDetectFastMoveSpeedThreshold; // pixel /sec - private int mDetectFastMoveTime; - private int mDetectFastMoveX; - private int mDetectFastMoveY; - // Dynamic threshold for gesture after fast typing - private boolean mAfterFastTyping; - private int mGestureDynamicDistanceThresholdFrom; // pixel - private int mGestureDynamicDistanceThresholdTo; // pixel - // Variables for gesture sampling - private int mGestureSamplingMinimumDistance; // pixel - private long mLastMajorEventTime; - private int mLastMajorEventX; - private int mLastMajorEventY; - // Variables for gesture recognition - private int mGestureRecognitionSpeedThreshold; // pixel / sec - private int mIncrementalRecognitionSize; - private int mLastIncrementalBatchSize; - - private static final int MSEC_PER_SEC = 1000; - - public GestureStroke(final int pointerId, final GestureStrokeParams params) { - mPointerId = pointerId; - mParams = params; - } - - public void setKeyboardGeometry(final int keyWidth, final int keyboardHeight) { - mKeyWidth = keyWidth; - mMinYCoordinate = -(int)(keyboardHeight * EXTRA_GESTURE_TRAIL_AREA_ABOVE_KEYBOARD_RATIO); - mMaxYCoordinate = keyboardHeight; - // TODO: Find an appropriate base metric for these length. Maybe diagonal length of the key? - mDetectFastMoveSpeedThreshold = (int)(keyWidth * mParams.mDetectFastMoveSpeedThreshold); - mGestureDynamicDistanceThresholdFrom = - (int)(keyWidth * mParams.mDynamicDistanceThresholdFrom); - mGestureDynamicDistanceThresholdTo = (int)(keyWidth * mParams.mDynamicDistanceThresholdTo); - mGestureSamplingMinimumDistance = (int)(keyWidth * mParams.mSamplingMinimumDistance); - mGestureRecognitionSpeedThreshold = (int)(keyWidth * mParams.mRecognitionSpeedThreshold); - if (DEBUG) { - Log.d(TAG, String.format( - "[%d] setKeyboardGeometry: keyWidth=%3d tT=%3d >> %3d tD=%3d >> %3d", - mPointerId, keyWidth, - mParams.mDynamicTimeThresholdFrom, - mParams.mDynamicTimeThresholdTo, - mGestureDynamicDistanceThresholdFrom, - mGestureDynamicDistanceThresholdTo)); - } - } - - public int getLength() { - return mEventTimes.getLength(); - } - - public void onDownEvent(final int x, final int y, final long downTime, - final long gestureFirstDownTime, final long lastTypingTime) { - reset(); - final long elapsedTimeAfterTyping = downTime - lastTypingTime; - if (elapsedTimeAfterTyping < mParams.mStaticTimeThresholdAfterFastTyping) { - mAfterFastTyping = true; - } - if (DEBUG) { - Log.d(TAG, String.format("[%d] onDownEvent: dT=%3d%s", mPointerId, - elapsedTimeAfterTyping, mAfterFastTyping ? " afterFastTyping" : "")); - } - final int elapsedTimeFromFirstDown = (int)(downTime - gestureFirstDownTime); - addPointOnKeyboard(x, y, elapsedTimeFromFirstDown, true /* isMajorEvent */); - } - - private int getGestureDynamicDistanceThreshold(final int deltaTime) { - if (!mAfterFastTyping || deltaTime >= mParams.mDynamicThresholdDecayDuration) { - return mGestureDynamicDistanceThresholdTo; - } - final int decayedThreshold = - (mGestureDynamicDistanceThresholdFrom - mGestureDynamicDistanceThresholdTo) - * deltaTime / mParams.mDynamicThresholdDecayDuration; - return mGestureDynamicDistanceThresholdFrom - decayedThreshold; - } - - private int getGestureDynamicTimeThreshold(final int deltaTime) { - if (!mAfterFastTyping || deltaTime >= mParams.mDynamicThresholdDecayDuration) { - return mParams.mDynamicTimeThresholdTo; - } - final int decayedThreshold = - (mParams.mDynamicTimeThresholdFrom - mParams.mDynamicTimeThresholdTo) - * deltaTime / mParams.mDynamicThresholdDecayDuration; - return mParams.mDynamicTimeThresholdFrom - decayedThreshold; - } - - public final boolean isStartOfAGesture() { - if (!hasDetectedFastMove()) { - return false; - } - final int size = getLength(); - if (size <= 0) { - return false; - } - final int lastIndex = size - 1; - final int deltaTime = mEventTimes.get(lastIndex) - mDetectFastMoveTime; - if (deltaTime < 0) { - return false; - } - final int deltaDistance = getDistance( - mXCoordinates.get(lastIndex), mYCoordinates.get(lastIndex), - mDetectFastMoveX, mDetectFastMoveY); - final int distanceThreshold = getGestureDynamicDistanceThreshold(deltaTime); - final int timeThreshold = getGestureDynamicTimeThreshold(deltaTime); - final boolean isStartOfAGesture = deltaTime >= timeThreshold - && deltaDistance >= distanceThreshold; - if (DEBUG) { - Log.d(TAG, String.format("[%d] isStartOfAGesture: dT=%3d tT=%3d dD=%3d tD=%3d%s%s", - mPointerId, deltaTime, timeThreshold, - deltaDistance, distanceThreshold, - mAfterFastTyping ? " afterFastTyping" : "", - isStartOfAGesture ? " startOfAGesture" : "")); - } - return isStartOfAGesture; - } - - public void duplicateLastPointWith(final int time) { - final int lastIndex = getLength() - 1; - if (lastIndex >= 0) { - final int x = mXCoordinates.get(lastIndex); - final int y = mYCoordinates.get(lastIndex); - if (DEBUG) { - Log.d(TAG, String.format("[%d] duplicateLastPointWith: %d,%d|%d", mPointerId, - x, y, time)); - } - // TODO: Have appendMajorPoint() - appendPoint(x, y, time); - updateIncrementalRecognitionSize(x, y, time); - } - } - - protected void reset() { - mIncrementalRecognitionSize = 0; - mLastIncrementalBatchSize = 0; - mEventTimes.setLength(0); - mXCoordinates.setLength(0); - mYCoordinates.setLength(0); - mLastMajorEventTime = 0; - mDetectFastMoveTime = 0; - mAfterFastTyping = false; - } - - private void appendPoint(final int x, final int y, final int time) { - final int lastIndex = getLength() - 1; - // The point that is created by {@link duplicateLastPointWith(int)} may have later event - // time than the next {@link MotionEvent}. To maintain the monotonicity of the event time, - // drop the successive point here. - if (lastIndex >= 0 && mEventTimes.get(lastIndex) > time) { - Log.w(TAG, String.format("[%d] drop stale event: %d,%d|%d last: %d,%d|%d", mPointerId, - x, y, time, mXCoordinates.get(lastIndex), mYCoordinates.get(lastIndex), - mEventTimes.get(lastIndex))); - return; - } - 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 final boolean hasDetectedFastMove() { - return mDetectFastMoveTime > 0; - } - - private int detectFastMove(final int x, final int y, final int time) { - final int size = getLength(); - final int lastIndex = size - 1; - final int lastX = mXCoordinates.get(lastIndex); - final int lastY = mYCoordinates.get(lastIndex); - 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_SPEED) { - final float speed = (float)pixelsPerSec / msecs / mKeyWidth; - Log.d(TAG, String.format("[%d] detectFastMove: speed=%5.2f", mPointerId, speed)); - } - // Equivalent to (pixels / msecs < mStartSpeedThreshold / MSEC_PER_SEC) - if (!hasDetectedFastMove() && pixelsPerSec > mDetectFastMoveSpeedThreshold * msecs) { - if (DEBUG) { - final float speed = (float)pixelsPerSec / msecs / mKeyWidth; - Log.d(TAG, String.format( - "[%d] detectFastMove: speed=%5.2f T=%3d points=%3d fastMove", - mPointerId, speed, time, size)); - } - mDetectFastMoveTime = time; - mDetectFastMoveX = x; - mDetectFastMoveY = y; - } - } - return dist; - } - - /** - * Add a touch event as a gesture point. Returns true if the touch event is on the valid - * gesture area. - * @param x the x-coordinate of the touch event - * @param y the y-coordinate of the touch event - * @param time the elapsed time in millisecond from the first gesture down - * @param isMajorEvent false if this is a historical move event - * @return true if the touch event is on the valid gesture area - */ - public boolean addPointOnKeyboard(final int x, final int y, final int time, - final boolean isMajorEvent) { - final int size = getLength(); - if (size <= 0) { - // Down event - appendPoint(x, y, time); - updateMajorEvent(x, y, time); - } else { - final int distance = detectFastMove(x, y, time); - if (distance > mGestureSamplingMinimumDistance) { - appendPoint(x, y, time); - } - } - if (isMajorEvent) { - updateIncrementalRecognitionSize(x, y, time); - updateMajorEvent(x, y, time); - } - return y >= mMinYCoordinate && y < mMaxYCoordinate; - } - - private void updateIncrementalRecognitionSize(final int x, final int y, final int time) { - 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 (pixelsPerSec < mGestureRecognitionSpeedThreshold * msecs) { - mIncrementalRecognitionSize = getLength(); - } - } - - public final boolean hasRecognitionTimePast( - final long currentTime, final long lastRecognitionTime) { - return currentTime > lastRecognitionTime + mParams.mRecognitionMinimumTime; - } - - public final void appendAllBatchPoints(final InputPointers out) { - appendBatchPoints(out, getLength()); - } - - public final void appendIncrementalBatchPoints(final InputPointers out) { - appendBatchPoints(out, mIncrementalRecognitionSize); - } - - private void appendBatchPoints(final InputPointers out, final int size) { - final int length = size - mLastIncrementalBatchSize; - if (length <= 0) { - return; - } - out.append(mPointerId, mEventTimes, mXCoordinates, mYCoordinates, - mLastIncrementalBatchSize, length); - mLastIncrementalBatchSize = size; - } - - 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 (int)Math.sqrt(dx * dx + dy * dy); - } -} diff --git a/java/src/com/android/inputmethod/keyboard/internal/GestureStrokeDrawingParams.java b/java/src/com/android/inputmethod/keyboard/internal/GestureStrokeDrawingParams.java new file mode 100644 index 000000000..478639d2d --- /dev/null +++ b/java/src/com/android/inputmethod/keyboard/internal/GestureStrokeDrawingParams.java @@ -0,0 +1,58 @@ +/* + * Copyright (C) 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. + */ + +package com.android.inputmethod.keyboard.internal; + +import android.content.res.TypedArray; + +import com.android.inputmethod.latin.R; + +/** + * This class holds parameters to control how a gesture stroke is sampled and drawn on the screen. + * + * @attr ref R.styleable#MainKeyboardView_gestureTrailMinSamplingDistance + * @attr ref R.styleable#MainKeyboardView_gestureTrailMaxInterpolationAngularThreshold + * @attr ref R.styleable#MainKeyboardView_gestureTrailMaxInterpolationDistanceThreshold + * @attr ref R.styleable#MainKeyboardView_gestureTrailMaxInterpolationSegments + */ +public final class GestureStrokeDrawingParams { + public final double mMinSamplingDistance; // in pixel + public final double mMaxInterpolationAngularThreshold; // in radian + public final double mMaxInterpolationDistanceThreshold; // in pixel + public final int mMaxInterpolationSegments; + + private static final float DEFAULT_MIN_SAMPLING_DISTANCE = 0.0f; // dp + private static final int DEFAULT_MAX_INTERPOLATION_ANGULAR_THRESHOLD = 15; // in degree + private static final float DEFAULT_MAX_INTERPOLATION_DISTANCE_THRESHOLD = 0.0f; // dp + private static final int DEFAULT_MAX_INTERPOLATION_SEGMENTS = 4; + + public GestureStrokeDrawingParams(final TypedArray mainKeyboardViewAttr) { + mMinSamplingDistance = mainKeyboardViewAttr.getDimension( + R.styleable.MainKeyboardView_gestureTrailMinSamplingDistance, + DEFAULT_MIN_SAMPLING_DISTANCE); + final int interpolationAngularDegree = mainKeyboardViewAttr.getInteger(R.styleable + .MainKeyboardView_gestureTrailMaxInterpolationAngularThreshold, 0); + mMaxInterpolationAngularThreshold = (interpolationAngularDegree <= 0) + ? Math.toRadians(DEFAULT_MAX_INTERPOLATION_ANGULAR_THRESHOLD) + : Math.toRadians(interpolationAngularDegree); + mMaxInterpolationDistanceThreshold = mainKeyboardViewAttr.getDimension(R.styleable + .MainKeyboardView_gestureTrailMaxInterpolationDistanceThreshold, + DEFAULT_MAX_INTERPOLATION_DISTANCE_THRESHOLD); + mMaxInterpolationSegments = mainKeyboardViewAttr.getInteger( + R.styleable.MainKeyboardView_gestureTrailMaxInterpolationSegments, + DEFAULT_MAX_INTERPOLATION_SEGMENTS); + } +} diff --git a/java/src/com/android/inputmethod/keyboard/internal/GestureStrokeDrawingPoints.java b/java/src/com/android/inputmethod/keyboard/internal/GestureStrokeDrawingPoints.java new file mode 100644 index 000000000..0c09cbcf7 --- /dev/null +++ b/java/src/com/android/inputmethod/keyboard/internal/GestureStrokeDrawingPoints.java @@ -0,0 +1,204 @@ +/* + * Copyright (C) 2012 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 com.android.inputmethod.latin.utils.ResizableIntArray; + +/** + * This class holds drawing points to represent a gesture stroke on the screen. + * TODO: Currently this class extends {@link GestureStrokeRecognitionPoints} that holds recognition + * points of a gesture stroke. This class should be independent from + * {@link GestureStrokeRecognitionPoints}. + */ +public final class GestureStrokeDrawingPoints extends GestureStrokeRecognitionPoints { + public static final int PREVIEW_CAPACITY = 256; + + private final ResizableIntArray mPreviewEventTimes = new ResizableIntArray(PREVIEW_CAPACITY); + private final ResizableIntArray mPreviewXCoordinates = new ResizableIntArray(PREVIEW_CAPACITY); + private final ResizableIntArray mPreviewYCoordinates = new ResizableIntArray(PREVIEW_CAPACITY); + + private final GestureStrokeDrawingParams mDrawingParams; + + private int mStrokeId; + private int mLastPreviewSize; + private final HermiteInterpolator mInterpolator = new HermiteInterpolator(); + private int mLastInterpolatedPreviewIndex; + + private int mLastX; + private int mLastY; + private double mDistanceFromLastSample; + + public GestureStrokeDrawingPoints(final int pointerId, + final GestureStrokeRecognitionParams recognitionParams, + final GestureStrokeDrawingParams drawingParams) { + super(pointerId, recognitionParams); + mDrawingParams = drawingParams; + } + + @Override + protected void reset() { + super.reset(); + mStrokeId++; + mLastPreviewSize = 0; + mLastInterpolatedPreviewIndex = 0; + mPreviewEventTimes.setLength(0); + mPreviewXCoordinates.setLength(0); + mPreviewYCoordinates.setLength(0); + } + + public int getGestureStrokeId() { + return mStrokeId; + } + + private boolean needsSampling(final int x, final int y) { + mDistanceFromLastSample += Math.hypot(x - mLastX, y - mLastY); + mLastX = x; + mLastY = y; + final boolean isDownEvent = (mPreviewEventTimes.getLength() == 0); + if (mDistanceFromLastSample >= mDrawingParams.mMinSamplingDistance || isDownEvent) { + mDistanceFromLastSample = 0.0d; + return true; + } + return false; + } + + @Override + public boolean addPointOnKeyboard(final int x, final int y, final int time, + final boolean isMajorEvent) { + if (needsSampling(x, y)) { + mPreviewEventTimes.add(time); + mPreviewXCoordinates.add(x); + mPreviewYCoordinates.add(y); + } + return super.addPointOnKeyboard(x, y, time, isMajorEvent); + + } + + /** + * Append sampled preview points. + * + * @param eventTimes the event time array of gesture trail to be drawn. + * @param xCoords the x-coordinates array of gesture trail to be drawn. + * @param yCoords the y-coordinates array of gesture trail to be drawn. + * @param types the point types array of gesture trail. This is valid only when + * {@link GestureTrailDrawingPoints#DEBUG_SHOW_POINTS} is true. + */ + public void appendPreviewStroke(final ResizableIntArray eventTimes, + final ResizableIntArray xCoords, final ResizableIntArray yCoords, + final ResizableIntArray types) { + final int length = mPreviewEventTimes.getLength() - mLastPreviewSize; + if (length <= 0) { + return; + } + eventTimes.append(mPreviewEventTimes, mLastPreviewSize, length); + xCoords.append(mPreviewXCoordinates, mLastPreviewSize, length); + yCoords.append(mPreviewYCoordinates, mLastPreviewSize, length); + if (GestureTrailDrawingPoints.DEBUG_SHOW_POINTS) { + types.fill(GestureTrailDrawingPoints.POINT_TYPE_SAMPLED, types.getLength(), length); + } + mLastPreviewSize = mPreviewEventTimes.getLength(); + } + + /** + * Calculate interpolated points between the last interpolated point and the end of the trail. + * And return the start index of the last interpolated segment of input arrays because it + * may need to recalculate the interpolated points in the segment if further segments are + * added to this stroke. + * + * @param lastInterpolatedIndex the start index of the last interpolated segment of + * eventTimes, xCoords, and yCoords. + * @param eventTimes the event time array of gesture trail to be drawn. + * @param xCoords the x-coordinates array of gesture trail to be drawn. + * @param yCoords the y-coordinates array of gesture trail to be drawn. + * @param types the point types array of gesture trail. This is valid only when + * {@link GestureTrailDrawingPoints#DEBUG_SHOW_POINTS} is true. + * @return the start index of the last interpolated segment of input arrays. + */ + public int interpolateStrokeAndReturnStartIndexOfLastSegment(final int lastInterpolatedIndex, + final ResizableIntArray eventTimes, final ResizableIntArray xCoords, + final ResizableIntArray yCoords, final ResizableIntArray types) { + final int size = mPreviewEventTimes.getLength(); + final int[] pt = mPreviewEventTimes.getPrimitiveArray(); + final int[] px = mPreviewXCoordinates.getPrimitiveArray(); + final int[] py = mPreviewYCoordinates.getPrimitiveArray(); + mInterpolator.reset(px, py, 0, size); + // The last segment of gesture stroke needs to be interpolated again because the slope of + // the tangent at the last point isn't determined. + int lastInterpolatedDrawIndex = lastInterpolatedIndex; + int d1 = lastInterpolatedIndex; + for (int p2 = mLastInterpolatedPreviewIndex + 1; p2 < size; p2++) { + final int p1 = p2 - 1; + final int p0 = p1 - 1; + final int p3 = p2 + 1; + mLastInterpolatedPreviewIndex = p1; + lastInterpolatedDrawIndex = d1; + mInterpolator.setInterval(p0, p1, p2, p3); + final double m1 = Math.atan2(mInterpolator.mSlope1Y, mInterpolator.mSlope1X); + final double m2 = Math.atan2(mInterpolator.mSlope2Y, mInterpolator.mSlope2X); + final double deltaAngle = Math.abs(angularDiff(m2, m1)); + final int segmentsByAngle = (int)Math.ceil( + deltaAngle / mDrawingParams.mMaxInterpolationAngularThreshold); + final double deltaDistance = Math.hypot(mInterpolator.mP1X - mInterpolator.mP2X, + mInterpolator.mP1Y - mInterpolator.mP2Y); + final int segmentsByDistance = (int)Math.ceil(deltaDistance + / mDrawingParams.mMaxInterpolationDistanceThreshold); + final int segments = Math.min(mDrawingParams.mMaxInterpolationSegments, + Math.max(segmentsByAngle, segmentsByDistance)); + final int t1 = eventTimes.get(d1); + final int dt = pt[p2] - pt[p1]; + d1++; + for (int i = 1; i < segments; i++) { + final float t = i / (float)segments; + mInterpolator.interpolate(t); + eventTimes.add(d1, (int)(dt * t) + t1); + xCoords.add(d1, (int)mInterpolator.mInterpolatedX); + yCoords.add(d1, (int)mInterpolator.mInterpolatedY); + if (GestureTrailDrawingPoints.DEBUG_SHOW_POINTS) { + types.add(d1, GestureTrailDrawingPoints.POINT_TYPE_INTERPOLATED); + } + d1++; + } + eventTimes.add(d1, pt[p2]); + xCoords.add(d1, px[p2]); + yCoords.add(d1, py[p2]); + if (GestureTrailDrawingPoints.DEBUG_SHOW_POINTS) { + types.add(d1, GestureTrailDrawingPoints.POINT_TYPE_SAMPLED); + } + } + return lastInterpolatedDrawIndex; + } + + private static final double TWO_PI = Math.PI * 2.0d; + + /** + * Calculate the angular of rotation from a0 to a1. + * + * @param a1 the angular to which the rotation ends. + * @param a0 the angular from which the rotation starts. + * @return the angular rotation value from a0 to a1, normalized to [-PI, +PI]. + */ + private static double angularDiff(final double a1, final double a0) { + double deltaAngle = a1 - a0; + while (deltaAngle > Math.PI) { + deltaAngle -= TWO_PI; + } + while (deltaAngle < -Math.PI) { + deltaAngle += TWO_PI; + } + return deltaAngle; + } +} diff --git a/java/src/com/android/inputmethod/keyboard/internal/GestureStrokeParams.java b/java/src/com/android/inputmethod/keyboard/internal/GestureStrokeParams.java deleted file mode 100644 index fdae5b98f..000000000 --- a/java/src/com/android/inputmethod/keyboard/internal/GestureStrokeParams.java +++ /dev/null @@ -1,93 +0,0 @@ -/* - * Copyright (C) 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. - */ - -package com.android.inputmethod.keyboard.internal; - -import android.content.res.TypedArray; - -import com.android.inputmethod.latin.R; -import com.android.inputmethod.latin.utils.ResourceUtils; - -public final class GestureStrokeParams { - // Static threshold for gesture after fast typing - public final int mStaticTimeThresholdAfterFastTyping; // msec - // Static threshold for starting gesture detection - public final float mDetectFastMoveSpeedThreshold; // keyWidth/sec - // Dynamic threshold for gesture after fast typing - public final int mDynamicThresholdDecayDuration; // msec - // Time based threshold values - public final int mDynamicTimeThresholdFrom; // msec - public final int mDynamicTimeThresholdTo; // msec - // Distance based threshold values - public final float mDynamicDistanceThresholdFrom; // keyWidth - public final float mDynamicDistanceThresholdTo; // keyWidth - // Parameters for gesture sampling - public final float mSamplingMinimumDistance; // keyWidth - // Parameters for gesture recognition - public final int mRecognitionMinimumTime; // msec - public final float mRecognitionSpeedThreshold; // keyWidth/sec - - // Default GestureStroke parameters. - public static final GestureStrokeParams DEFAULT = new GestureStrokeParams(); - - private GestureStrokeParams() { - // These parameter values are default and intended for testing. - mStaticTimeThresholdAfterFastTyping = 350; // msec - mDetectFastMoveSpeedThreshold = 1.5f; // keyWidth/sec - mDynamicThresholdDecayDuration = 450; // msec - mDynamicTimeThresholdFrom = 300; // msec - mDynamicTimeThresholdTo = 20; // msec - mDynamicDistanceThresholdFrom = 6.0f; // keyWidth - mDynamicDistanceThresholdTo = 0.35f; // keyWidth - // The following parameters' change will affect the result of regression test. - mSamplingMinimumDistance = 1.0f / 6.0f; // keyWidth - mRecognitionMinimumTime = 100; // msec - mRecognitionSpeedThreshold = 5.5f; // keyWidth/sec - } - - public GestureStrokeParams(final TypedArray mainKeyboardViewAttr) { - mStaticTimeThresholdAfterFastTyping = mainKeyboardViewAttr.getInt( - R.styleable.MainKeyboardView_gestureStaticTimeThresholdAfterFastTyping, - DEFAULT.mStaticTimeThresholdAfterFastTyping); - mDetectFastMoveSpeedThreshold = ResourceUtils.getFraction(mainKeyboardViewAttr, - R.styleable.MainKeyboardView_gestureDetectFastMoveSpeedThreshold, - DEFAULT.mDetectFastMoveSpeedThreshold); - mDynamicThresholdDecayDuration = mainKeyboardViewAttr.getInt( - R.styleable.MainKeyboardView_gestureDynamicThresholdDecayDuration, - DEFAULT.mDynamicThresholdDecayDuration); - mDynamicTimeThresholdFrom = mainKeyboardViewAttr.getInt( - R.styleable.MainKeyboardView_gestureDynamicTimeThresholdFrom, - DEFAULT.mDynamicTimeThresholdFrom); - mDynamicTimeThresholdTo = mainKeyboardViewAttr.getInt( - R.styleable.MainKeyboardView_gestureDynamicTimeThresholdTo, - DEFAULT.mDynamicTimeThresholdTo); - mDynamicDistanceThresholdFrom = ResourceUtils.getFraction(mainKeyboardViewAttr, - R.styleable.MainKeyboardView_gestureDynamicDistanceThresholdFrom, - DEFAULT.mDynamicDistanceThresholdFrom); - mDynamicDistanceThresholdTo = ResourceUtils.getFraction(mainKeyboardViewAttr, - R.styleable.MainKeyboardView_gestureDynamicDistanceThresholdTo, - DEFAULT.mDynamicDistanceThresholdTo); - mSamplingMinimumDistance = ResourceUtils.getFraction(mainKeyboardViewAttr, - R.styleable.MainKeyboardView_gestureSamplingMinimumDistance, - DEFAULT.mSamplingMinimumDistance); - mRecognitionMinimumTime = mainKeyboardViewAttr.getInt( - R.styleable.MainKeyboardView_gestureRecognitionMinimumTime, - DEFAULT.mRecognitionMinimumTime); - mRecognitionSpeedThreshold = ResourceUtils.getFraction(mainKeyboardViewAttr, - R.styleable.MainKeyboardView_gestureRecognitionSpeedThreshold, - DEFAULT.mRecognitionSpeedThreshold); - } -} diff --git a/java/src/com/android/inputmethod/keyboard/internal/GestureStrokePreviewParams.java b/java/src/com/android/inputmethod/keyboard/internal/GestureStrokePreviewParams.java deleted file mode 100644 index 6695476e1..000000000 --- a/java/src/com/android/inputmethod/keyboard/internal/GestureStrokePreviewParams.java +++ /dev/null @@ -1,50 +0,0 @@ -/* - * Copyright (C) 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. - */ - -package com.android.inputmethod.keyboard.internal; - -import android.content.res.TypedArray; - -import com.android.inputmethod.latin.R; - -public final class GestureStrokePreviewParams { - public final double mMinSamplingDistance; // in pixel - public final double mMaxInterpolationAngularThreshold; // in radian - public final double mMaxInterpolationDistanceThreshold; // in pixel - public final int mMaxInterpolationSegments; - - private static final float DEFAULT_MIN_SAMPLING_DISTANCE = 0.0f; // dp - private static final int DEFAULT_MAX_INTERPOLATION_ANGULAR_THRESHOLD = 15; // in degree - private static final float DEFAULT_MAX_INTERPOLATION_DISTANCE_THRESHOLD = 0.0f; // dp - private static final int DEFAULT_MAX_INTERPOLATION_SEGMENTS = 4; - - public GestureStrokePreviewParams(final TypedArray mainKeyboardViewAttr) { - mMinSamplingDistance = mainKeyboardViewAttr.getDimension( - R.styleable.MainKeyboardView_gestureTrailMinSamplingDistance, - DEFAULT_MIN_SAMPLING_DISTANCE); - final int interpolationAngularDegree = mainKeyboardViewAttr.getInteger(R.styleable - .MainKeyboardView_gestureTrailMaxInterpolationAngularThreshold, 0); - mMaxInterpolationAngularThreshold = (interpolationAngularDegree <= 0) - ? Math.toRadians(DEFAULT_MAX_INTERPOLATION_ANGULAR_THRESHOLD) - : Math.toRadians(interpolationAngularDegree); - mMaxInterpolationDistanceThreshold = mainKeyboardViewAttr.getDimension(R.styleable - .MainKeyboardView_gestureTrailMaxInterpolationDistanceThreshold, - DEFAULT_MAX_INTERPOLATION_DISTANCE_THRESHOLD); - mMaxInterpolationSegments = mainKeyboardViewAttr.getInteger( - R.styleable.MainKeyboardView_gestureTrailMaxInterpolationSegments, - DEFAULT_MAX_INTERPOLATION_SEGMENTS); - } -} diff --git a/java/src/com/android/inputmethod/keyboard/internal/GestureStrokeRecognitionParams.java b/java/src/com/android/inputmethod/keyboard/internal/GestureStrokeRecognitionParams.java new file mode 100644 index 000000000..07b14514c --- /dev/null +++ b/java/src/com/android/inputmethod/keyboard/internal/GestureStrokeRecognitionParams.java @@ -0,0 +1,109 @@ +/* + * Copyright (C) 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. + */ + +package com.android.inputmethod.keyboard.internal; + +import android.content.res.TypedArray; + +import com.android.inputmethod.latin.R; +import com.android.inputmethod.latin.utils.ResourceUtils; + +/** + * This class holds parameters to control how a gesture stroke is sampled and recognized. + * This class also has parameters to distinguish gesture input events from fast typing events. + * + * @attr ref R.styleable#MainKeyboardView_gestureStaticTimeThresholdAfterFastTyping + * @attr ref R.styleable#MainKeyboardView_gestureDetectFastMoveSpeedThreshold + * @attr ref R.styleable#MainKeyboardView_gestureDynamicThresholdDecayDuration + * @attr ref R.styleable#MainKeyboardView_gestureDynamicTimeThresholdFrom + * @attr ref R.styleable#MainKeyboardView_gestureDynamicTimeThresholdTo + * @attr ref R.styleable#MainKeyboardView_gestureDynamicDistanceThresholdFrom + * @attr ref R.styleable#MainKeyboardView_gestureDynamicDistanceThresholdTo + * @attr ref R.styleable#MainKeyboardView_gestureSamplingMinimumDistance + * @attr ref R.styleable#MainKeyboardView_gestureRecognitionMinimumTime + * @attr ref R.styleable#MainKeyboardView_gestureRecognitionSpeedThreshold + */ +public final class GestureStrokeRecognitionParams { + // Static threshold for gesture after fast typing + public final int mStaticTimeThresholdAfterFastTyping; // msec + // Static threshold for starting gesture detection + public final float mDetectFastMoveSpeedThreshold; // keyWidth/sec + // Dynamic threshold for gesture after fast typing + public final int mDynamicThresholdDecayDuration; // msec + // Time based threshold values + public final int mDynamicTimeThresholdFrom; // msec + public final int mDynamicTimeThresholdTo; // msec + // Distance based threshold values + public final float mDynamicDistanceThresholdFrom; // keyWidth + public final float mDynamicDistanceThresholdTo; // keyWidth + // Parameters for gesture sampling + public final float mSamplingMinimumDistance; // keyWidth + // Parameters for gesture recognition + public final int mRecognitionMinimumTime; // msec + public final float mRecognitionSpeedThreshold; // keyWidth/sec + + // Default GestureStrokeRecognitionPoints parameters. + public static final GestureStrokeRecognitionParams DEFAULT = + new GestureStrokeRecognitionParams(); + + private GestureStrokeRecognitionParams() { + // These parameter values are default and intended for testing. + mStaticTimeThresholdAfterFastTyping = 350; // msec + mDetectFastMoveSpeedThreshold = 1.5f; // keyWidth/sec + mDynamicThresholdDecayDuration = 450; // msec + mDynamicTimeThresholdFrom = 300; // msec + mDynamicTimeThresholdTo = 20; // msec + mDynamicDistanceThresholdFrom = 6.0f; // keyWidth + mDynamicDistanceThresholdTo = 0.35f; // keyWidth + // The following parameters' change will affect the result of regression test. + mSamplingMinimumDistance = 1.0f / 6.0f; // keyWidth + mRecognitionMinimumTime = 100; // msec + mRecognitionSpeedThreshold = 5.5f; // keyWidth/sec + } + + public GestureStrokeRecognitionParams(final TypedArray mainKeyboardViewAttr) { + mStaticTimeThresholdAfterFastTyping = mainKeyboardViewAttr.getInt( + R.styleable.MainKeyboardView_gestureStaticTimeThresholdAfterFastTyping, + DEFAULT.mStaticTimeThresholdAfterFastTyping); + mDetectFastMoveSpeedThreshold = ResourceUtils.getFraction(mainKeyboardViewAttr, + R.styleable.MainKeyboardView_gestureDetectFastMoveSpeedThreshold, + DEFAULT.mDetectFastMoveSpeedThreshold); + mDynamicThresholdDecayDuration = mainKeyboardViewAttr.getInt( + R.styleable.MainKeyboardView_gestureDynamicThresholdDecayDuration, + DEFAULT.mDynamicThresholdDecayDuration); + mDynamicTimeThresholdFrom = mainKeyboardViewAttr.getInt( + R.styleable.MainKeyboardView_gestureDynamicTimeThresholdFrom, + DEFAULT.mDynamicTimeThresholdFrom); + mDynamicTimeThresholdTo = mainKeyboardViewAttr.getInt( + R.styleable.MainKeyboardView_gestureDynamicTimeThresholdTo, + DEFAULT.mDynamicTimeThresholdTo); + mDynamicDistanceThresholdFrom = ResourceUtils.getFraction(mainKeyboardViewAttr, + R.styleable.MainKeyboardView_gestureDynamicDistanceThresholdFrom, + DEFAULT.mDynamicDistanceThresholdFrom); + mDynamicDistanceThresholdTo = ResourceUtils.getFraction(mainKeyboardViewAttr, + R.styleable.MainKeyboardView_gestureDynamicDistanceThresholdTo, + DEFAULT.mDynamicDistanceThresholdTo); + mSamplingMinimumDistance = ResourceUtils.getFraction(mainKeyboardViewAttr, + R.styleable.MainKeyboardView_gestureSamplingMinimumDistance, + DEFAULT.mSamplingMinimumDistance); + mRecognitionMinimumTime = mainKeyboardViewAttr.getInt( + R.styleable.MainKeyboardView_gestureRecognitionMinimumTime, + DEFAULT.mRecognitionMinimumTime); + mRecognitionSpeedThreshold = ResourceUtils.getFraction(mainKeyboardViewAttr, + R.styleable.MainKeyboardView_gestureRecognitionSpeedThreshold, + DEFAULT.mRecognitionSpeedThreshold); + } +} diff --git a/java/src/com/android/inputmethod/keyboard/internal/GestureStrokeRecognitionPoints.java b/java/src/com/android/inputmethod/keyboard/internal/GestureStrokeRecognitionPoints.java new file mode 100644 index 000000000..343c898f9 --- /dev/null +++ b/java/src/com/android/inputmethod/keyboard/internal/GestureStrokeRecognitionPoints.java @@ -0,0 +1,322 @@ +/* + * Copyright (C) 2012 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.util.Log; + +import com.android.inputmethod.latin.InputPointers; +import com.android.inputmethod.latin.utils.ResizableIntArray; + +/** + * This class holds event points to recognize a gesture stroke. + * TODO: Should be final and package private class. + */ +public class GestureStrokeRecognitionPoints { + private static final String TAG = GestureStrokeRecognitionPoints.class.getSimpleName(); + private static final boolean DEBUG = false; + private static final boolean DEBUG_SPEED = false; + + // The height of extra area above the keyboard to draw gesture trails. + // Proportional to the keyboard height. + public static final float EXTRA_GESTURE_TRAIL_AREA_ABOVE_KEYBOARD_RATIO = 0.25f; + + 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 final GestureStrokeRecognitionParams mRecognitionParams; + + private int mKeyWidth; // pixel + private int mMinYCoordinate; // pixel + private int mMaxYCoordinate; // pixel + // Static threshold for starting gesture detection + private int mDetectFastMoveSpeedThreshold; // pixel /sec + private int mDetectFastMoveTime; + private int mDetectFastMoveX; + private int mDetectFastMoveY; + // Dynamic threshold for gesture after fast typing + private boolean mAfterFastTyping; + private int mGestureDynamicDistanceThresholdFrom; // pixel + private int mGestureDynamicDistanceThresholdTo; // pixel + // Variables for gesture sampling + private int mGestureSamplingMinimumDistance; // pixel + private long mLastMajorEventTime; + private int mLastMajorEventX; + private int mLastMajorEventY; + // Variables for gesture recognition + private int mGestureRecognitionSpeedThreshold; // pixel / sec + private int mIncrementalRecognitionSize; + private int mLastIncrementalBatchSize; + + private static final int MSEC_PER_SEC = 1000; + + public GestureStrokeRecognitionPoints(final int pointerId, + final GestureStrokeRecognitionParams recognitionParams) { + mPointerId = pointerId; + mRecognitionParams = recognitionParams; + } + + public void setKeyboardGeometry(final int keyWidth, final int keyboardHeight) { + mKeyWidth = keyWidth; + mMinYCoordinate = -(int)(keyboardHeight * EXTRA_GESTURE_TRAIL_AREA_ABOVE_KEYBOARD_RATIO); + mMaxYCoordinate = keyboardHeight; + // TODO: Find an appropriate base metric for these length. Maybe diagonal length of the key? + mDetectFastMoveSpeedThreshold = (int)( + keyWidth * mRecognitionParams.mDetectFastMoveSpeedThreshold); + mGestureDynamicDistanceThresholdFrom = (int)( + keyWidth * mRecognitionParams.mDynamicDistanceThresholdFrom); + mGestureDynamicDistanceThresholdTo = (int)( + keyWidth * mRecognitionParams.mDynamicDistanceThresholdTo); + mGestureSamplingMinimumDistance = (int)( + keyWidth * mRecognitionParams.mSamplingMinimumDistance); + mGestureRecognitionSpeedThreshold = (int)( + keyWidth * mRecognitionParams.mRecognitionSpeedThreshold); + if (DEBUG) { + Log.d(TAG, String.format( + "[%d] setKeyboardGeometry: keyWidth=%3d tT=%3d >> %3d tD=%3d >> %3d", + mPointerId, keyWidth, + mRecognitionParams.mDynamicTimeThresholdFrom, + mRecognitionParams.mDynamicTimeThresholdTo, + mGestureDynamicDistanceThresholdFrom, + mGestureDynamicDistanceThresholdTo)); + } + } + + public int getLength() { + return mEventTimes.getLength(); + } + + public void onDownEvent(final int x, final int y, final long downTime, + final long gestureFirstDownTime, final long lastTypingTime) { + reset(); + final long elapsedTimeAfterTyping = downTime - lastTypingTime; + if (elapsedTimeAfterTyping < mRecognitionParams.mStaticTimeThresholdAfterFastTyping) { + mAfterFastTyping = true; + } + if (DEBUG) { + Log.d(TAG, String.format("[%d] onDownEvent: dT=%3d%s", mPointerId, + elapsedTimeAfterTyping, mAfterFastTyping ? " afterFastTyping" : "")); + } + final int elapsedTimeFromFirstDown = (int)(downTime - gestureFirstDownTime); + addPointOnKeyboard(x, y, elapsedTimeFromFirstDown, true /* isMajorEvent */); + } + + private int getGestureDynamicDistanceThreshold(final int deltaTime) { + if (!mAfterFastTyping || deltaTime >= mRecognitionParams.mDynamicThresholdDecayDuration) { + return mGestureDynamicDistanceThresholdTo; + } + final int decayedThreshold = + (mGestureDynamicDistanceThresholdFrom - mGestureDynamicDistanceThresholdTo) + * deltaTime / mRecognitionParams.mDynamicThresholdDecayDuration; + return mGestureDynamicDistanceThresholdFrom - decayedThreshold; + } + + private int getGestureDynamicTimeThreshold(final int deltaTime) { + if (!mAfterFastTyping || deltaTime >= mRecognitionParams.mDynamicThresholdDecayDuration) { + return mRecognitionParams.mDynamicTimeThresholdTo; + } + final int decayedThreshold = + (mRecognitionParams.mDynamicTimeThresholdFrom + - mRecognitionParams.mDynamicTimeThresholdTo) + * deltaTime / mRecognitionParams.mDynamicThresholdDecayDuration; + return mRecognitionParams.mDynamicTimeThresholdFrom - decayedThreshold; + } + + public final boolean isStartOfAGesture() { + if (!hasDetectedFastMove()) { + return false; + } + final int size = getLength(); + if (size <= 0) { + return false; + } + final int lastIndex = size - 1; + final int deltaTime = mEventTimes.get(lastIndex) - mDetectFastMoveTime; + if (deltaTime < 0) { + return false; + } + final int deltaDistance = getDistance( + mXCoordinates.get(lastIndex), mYCoordinates.get(lastIndex), + mDetectFastMoveX, mDetectFastMoveY); + final int distanceThreshold = getGestureDynamicDistanceThreshold(deltaTime); + final int timeThreshold = getGestureDynamicTimeThreshold(deltaTime); + final boolean isStartOfAGesture = deltaTime >= timeThreshold + && deltaDistance >= distanceThreshold; + if (DEBUG) { + Log.d(TAG, String.format("[%d] isStartOfAGesture: dT=%3d tT=%3d dD=%3d tD=%3d%s%s", + mPointerId, deltaTime, timeThreshold, + deltaDistance, distanceThreshold, + mAfterFastTyping ? " afterFastTyping" : "", + isStartOfAGesture ? " startOfAGesture" : "")); + } + return isStartOfAGesture; + } + + public void duplicateLastPointWith(final int time) { + final int lastIndex = getLength() - 1; + if (lastIndex >= 0) { + final int x = mXCoordinates.get(lastIndex); + final int y = mYCoordinates.get(lastIndex); + if (DEBUG) { + Log.d(TAG, String.format("[%d] duplicateLastPointWith: %d,%d|%d", mPointerId, + x, y, time)); + } + // TODO: Have appendMajorPoint() + appendPoint(x, y, time); + updateIncrementalRecognitionSize(x, y, time); + } + } + + protected void reset() { + mIncrementalRecognitionSize = 0; + mLastIncrementalBatchSize = 0; + mEventTimes.setLength(0); + mXCoordinates.setLength(0); + mYCoordinates.setLength(0); + mLastMajorEventTime = 0; + mDetectFastMoveTime = 0; + mAfterFastTyping = false; + } + + private void appendPoint(final int x, final int y, final int time) { + final int lastIndex = getLength() - 1; + // The point that is created by {@link duplicateLastPointWith(int)} may have later event + // time than the next {@link MotionEvent}. To maintain the monotonicity of the event time, + // drop the successive point here. + if (lastIndex >= 0 && mEventTimes.get(lastIndex) > time) { + Log.w(TAG, String.format("[%d] drop stale event: %d,%d|%d last: %d,%d|%d", mPointerId, + x, y, time, mXCoordinates.get(lastIndex), mYCoordinates.get(lastIndex), + mEventTimes.get(lastIndex))); + return; + } + 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 final boolean hasDetectedFastMove() { + return mDetectFastMoveTime > 0; + } + + private int detectFastMove(final int x, final int y, final int time) { + final int size = getLength(); + final int lastIndex = size - 1; + final int lastX = mXCoordinates.get(lastIndex); + final int lastY = mYCoordinates.get(lastIndex); + 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_SPEED) { + final float speed = (float)pixelsPerSec / msecs / mKeyWidth; + Log.d(TAG, String.format("[%d] detectFastMove: speed=%5.2f", mPointerId, speed)); + } + // Equivalent to (pixels / msecs < mStartSpeedThreshold / MSEC_PER_SEC) + if (!hasDetectedFastMove() && pixelsPerSec > mDetectFastMoveSpeedThreshold * msecs) { + if (DEBUG) { + final float speed = (float)pixelsPerSec / msecs / mKeyWidth; + Log.d(TAG, String.format( + "[%d] detectFastMove: speed=%5.2f T=%3d points=%3d fastMove", + mPointerId, speed, time, size)); + } + mDetectFastMoveTime = time; + mDetectFastMoveX = x; + mDetectFastMoveY = y; + } + } + return dist; + } + + /** + * Add a touch event as a gesture point. Returns true if the touch event is on the valid + * gesture area. + * @param x the x-coordinate of the touch event + * @param y the y-coordinate of the touch event + * @param time the elapsed time in millisecond from the first gesture down + * @param isMajorEvent false if this is a historical move event + * @return true if the touch event is on the valid gesture area + */ + public boolean addPointOnKeyboard(final int x, final int y, final int time, + final boolean isMajorEvent) { + final int size = getLength(); + if (size <= 0) { + // Down event + appendPoint(x, y, time); + updateMajorEvent(x, y, time); + } else { + final int distance = detectFastMove(x, y, time); + if (distance > mGestureSamplingMinimumDistance) { + appendPoint(x, y, time); + } + } + if (isMajorEvent) { + updateIncrementalRecognitionSize(x, y, time); + updateMajorEvent(x, y, time); + } + return y >= mMinYCoordinate && y < mMaxYCoordinate; + } + + private void updateIncrementalRecognitionSize(final int x, final int y, final int time) { + 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 (pixelsPerSec < mGestureRecognitionSpeedThreshold * msecs) { + mIncrementalRecognitionSize = getLength(); + } + } + + public final boolean hasRecognitionTimePast( + final long currentTime, final long lastRecognitionTime) { + return currentTime > lastRecognitionTime + mRecognitionParams.mRecognitionMinimumTime; + } + + public final void appendAllBatchPoints(final InputPointers out) { + appendBatchPoints(out, getLength()); + } + + public final void appendIncrementalBatchPoints(final InputPointers out) { + appendBatchPoints(out, mIncrementalRecognitionSize); + } + + private void appendBatchPoints(final InputPointers out, final int size) { + final int length = size - mLastIncrementalBatchSize; + if (length <= 0) { + return; + } + out.append(mPointerId, mEventTimes, mXCoordinates, mYCoordinates, + mLastIncrementalBatchSize, length); + mLastIncrementalBatchSize = size; + } + + private static int getDistance(final int x1, final int y1, final int x2, final int y2) { + return (int)Math.hypot(x1 - x2, y1 - y2); + } +} diff --git a/java/src/com/android/inputmethod/keyboard/internal/GestureStrokeWithPreviewPoints.java b/java/src/com/android/inputmethod/keyboard/internal/GestureStrokeWithPreviewPoints.java deleted file mode 100644 index 22407d8af..000000000 --- a/java/src/com/android/inputmethod/keyboard/internal/GestureStrokeWithPreviewPoints.java +++ /dev/null @@ -1,198 +0,0 @@ -/* - * Copyright (C) 2012 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 com.android.inputmethod.latin.utils.ResizableIntArray; - -public final class GestureStrokeWithPreviewPoints extends GestureStroke { - public static final int PREVIEW_CAPACITY = 256; - - private final ResizableIntArray mPreviewEventTimes = new ResizableIntArray(PREVIEW_CAPACITY); - private final ResizableIntArray mPreviewXCoordinates = new ResizableIntArray(PREVIEW_CAPACITY); - private final ResizableIntArray mPreviewYCoordinates = new ResizableIntArray(PREVIEW_CAPACITY); - - private final GestureStrokePreviewParams mPreviewParams; - - private int mStrokeId; - private int mLastPreviewSize; - private final HermiteInterpolator mInterpolator = new HermiteInterpolator(); - private int mLastInterpolatedPreviewIndex; - - private int mLastX; - private int mLastY; - private double mDistanceFromLastSample; - - public GestureStrokeWithPreviewPoints(final int pointerId, - final GestureStrokeParams strokeParams, - final GestureStrokePreviewParams previewParams) { - super(pointerId, strokeParams); - mPreviewParams = previewParams; - } - - @Override - protected void reset() { - super.reset(); - mStrokeId++; - mLastPreviewSize = 0; - mLastInterpolatedPreviewIndex = 0; - mPreviewEventTimes.setLength(0); - mPreviewXCoordinates.setLength(0); - mPreviewYCoordinates.setLength(0); - } - - public int getGestureStrokeId() { - return mStrokeId; - } - - private boolean needsSampling(final int x, final int y) { - mDistanceFromLastSample += Math.hypot(x - mLastX, y - mLastY); - mLastX = x; - mLastY = y; - final boolean isDownEvent = (mPreviewEventTimes.getLength() == 0); - if (mDistanceFromLastSample >= mPreviewParams.mMinSamplingDistance || isDownEvent) { - mDistanceFromLastSample = 0.0d; - return true; - } - return false; - } - - @Override - public boolean addPointOnKeyboard(final int x, final int y, final int time, - final boolean isMajorEvent) { - if (needsSampling(x, y)) { - mPreviewEventTimes.add(time); - mPreviewXCoordinates.add(x); - mPreviewYCoordinates.add(y); - } - return super.addPointOnKeyboard(x, y, time, isMajorEvent); - - } - - /** - * Append sampled preview points. - * - * @param eventTimes the event time array of gesture trail to be drawn. - * @param xCoords the x-coordinates array of gesture trail to be drawn. - * @param yCoords the y-coordinates array of gesture trail to be drawn. - * @param types the point types array of gesture trail. This is valid only when - * {@link GestureTrail#DEBUG_SHOW_POINTS} is true. - */ - public void appendPreviewStroke(final ResizableIntArray eventTimes, - final ResizableIntArray xCoords, final ResizableIntArray yCoords, - final ResizableIntArray types) { - final int length = mPreviewEventTimes.getLength() - mLastPreviewSize; - if (length <= 0) { - return; - } - eventTimes.append(mPreviewEventTimes, mLastPreviewSize, length); - xCoords.append(mPreviewXCoordinates, mLastPreviewSize, length); - yCoords.append(mPreviewYCoordinates, mLastPreviewSize, length); - if (GestureTrail.DEBUG_SHOW_POINTS) { - types.fill(GestureTrail.POINT_TYPE_SAMPLED, types.getLength(), length); - } - mLastPreviewSize = mPreviewEventTimes.getLength(); - } - - /** - * Calculate interpolated points between the last interpolated point and the end of the trail. - * And return the start index of the last interpolated segment of input arrays because it - * may need to recalculate the interpolated points in the segment if further segments are - * added to this stroke. - * - * @param lastInterpolatedIndex the start index of the last interpolated segment of - * eventTimes, xCoords, and yCoords. - * @param eventTimes the event time array of gesture trail to be drawn. - * @param xCoords the x-coordinates array of gesture trail to be drawn. - * @param yCoords the y-coordinates array of gesture trail to be drawn. - * @param types the point types array of gesture trail. This is valid only when - * {@link GestureTrail#DEBUG_SHOW_POINTS} is true. - * @return the start index of the last interpolated segment of input arrays. - */ - public int interpolateStrokeAndReturnStartIndexOfLastSegment(final int lastInterpolatedIndex, - final ResizableIntArray eventTimes, final ResizableIntArray xCoords, - final ResizableIntArray yCoords, final ResizableIntArray types) { - final int size = mPreviewEventTimes.getLength(); - final int[] pt = mPreviewEventTimes.getPrimitiveArray(); - final int[] px = mPreviewXCoordinates.getPrimitiveArray(); - final int[] py = mPreviewYCoordinates.getPrimitiveArray(); - mInterpolator.reset(px, py, 0, size); - // The last segment of gesture stroke needs to be interpolated again because the slope of - // the tangent at the last point isn't determined. - int lastInterpolatedDrawIndex = lastInterpolatedIndex; - int d1 = lastInterpolatedIndex; - for (int p2 = mLastInterpolatedPreviewIndex + 1; p2 < size; p2++) { - final int p1 = p2 - 1; - final int p0 = p1 - 1; - final int p3 = p2 + 1; - mLastInterpolatedPreviewIndex = p1; - lastInterpolatedDrawIndex = d1; - mInterpolator.setInterval(p0, p1, p2, p3); - final double m1 = Math.atan2(mInterpolator.mSlope1Y, mInterpolator.mSlope1X); - final double m2 = Math.atan2(mInterpolator.mSlope2Y, mInterpolator.mSlope2X); - final double deltaAngle = Math.abs(angularDiff(m2, m1)); - final int segmentsByAngle = (int)Math.ceil( - deltaAngle / mPreviewParams.mMaxInterpolationAngularThreshold); - final double deltaDistance = Math.hypot(mInterpolator.mP1X - mInterpolator.mP2X, - mInterpolator.mP1Y - mInterpolator.mP2Y); - final int segmentsByDistance = (int)Math.ceil(deltaDistance - / mPreviewParams.mMaxInterpolationDistanceThreshold); - final int segments = Math.min(mPreviewParams.mMaxInterpolationSegments, - Math.max(segmentsByAngle, segmentsByDistance)); - final int t1 = eventTimes.get(d1); - final int dt = pt[p2] - pt[p1]; - d1++; - for (int i = 1; i < segments; i++) { - final float t = i / (float)segments; - mInterpolator.interpolate(t); - eventTimes.add(d1, (int)(dt * t) + t1); - xCoords.add(d1, (int)mInterpolator.mInterpolatedX); - yCoords.add(d1, (int)mInterpolator.mInterpolatedY); - if (GestureTrail.DEBUG_SHOW_POINTS) { - types.add(d1, GestureTrail.POINT_TYPE_INTERPOLATED); - } - d1++; - } - eventTimes.add(d1, pt[p2]); - xCoords.add(d1, px[p2]); - yCoords.add(d1, py[p2]); - if (GestureTrail.DEBUG_SHOW_POINTS) { - types.add(d1, GestureTrail.POINT_TYPE_SAMPLED); - } - } - return lastInterpolatedDrawIndex; - } - - private static final double TWO_PI = Math.PI * 2.0d; - - /** - * Calculate the angular of rotation from a0 to a1. - * - * @param a1 the angular to which the rotation ends. - * @param a0 the angular from which the rotation starts. - * @return the angular rotation value from a0 to a1, normalized to [-PI, +PI]. - */ - private static double angularDiff(final double a1, final double a0) { - double deltaAngle = a1 - a0; - while (deltaAngle > Math.PI) { - deltaAngle -= TWO_PI; - } - while (deltaAngle < -Math.PI) { - deltaAngle += TWO_PI; - } - return deltaAngle; - } -} diff --git a/java/src/com/android/inputmethod/keyboard/internal/GestureTrail.java b/java/src/com/android/inputmethod/keyboard/internal/GestureTrail.java deleted file mode 100644 index aca667919..000000000 --- a/java/src/com/android/inputmethod/keyboard/internal/GestureTrail.java +++ /dev/null @@ -1,318 +0,0 @@ -/* - * Copyright (C) 2012 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.res.TypedArray; -import android.graphics.Canvas; -import android.graphics.Color; -import android.graphics.Paint; -import android.graphics.Path; -import android.graphics.Rect; -import android.os.SystemClock; - -import com.android.inputmethod.latin.Constants; -import com.android.inputmethod.latin.R; -import com.android.inputmethod.latin.utils.ResizableIntArray; - -/* - * @attr ref R.styleable#MainKeyboardView_gestureTrailFadeoutStartDelay - * @attr ref R.styleable#MainKeyboardView_gestureTrailFadeoutDuration - * @attr ref R.styleable#MainKeyboardView_gestureTrailUpdateInterval - * @attr ref R.styleable#MainKeyboardView_gestureTrailColor - * @attr ref R.styleable#MainKeyboardView_gestureTrailWidth - */ -final class GestureTrail { - public static final boolean DEBUG_SHOW_POINTS = false; - public static final int POINT_TYPE_SAMPLED = 1; - public static final int POINT_TYPE_INTERPOLATED = 2; - private static final int FADEOUT_START_DELAY_FOR_DEBUG = 2000; // millisecond - private static final int FADEOUT_DURATION_FOR_DEBUG = 200; // millisecond - - private static final int DEFAULT_CAPACITY = GestureStrokeWithPreviewPoints.PREVIEW_CAPACITY; - - // These three {@link ResizableIntArray}s should be synchronized by {@link #mEventTimes}. - private final ResizableIntArray mXCoordinates = new ResizableIntArray(DEFAULT_CAPACITY); - private final ResizableIntArray mYCoordinates = new ResizableIntArray(DEFAULT_CAPACITY); - private final ResizableIntArray mEventTimes = new ResizableIntArray(DEFAULT_CAPACITY); - private final ResizableIntArray mPointTypes = new ResizableIntArray( - DEBUG_SHOW_POINTS ? DEFAULT_CAPACITY : 0); - private int mCurrentStrokeId = -1; - // The wall time of the zero value in {@link #mEventTimes} - private long mCurrentTimeBase; - private int mTrailStartIndex; - private int mLastInterpolatedDrawIndex; - - static final class Params { - public final int mTrailColor; - public final float mTrailStartWidth; - public final float mTrailEndWidth; - public final float mTrailBodyRatio; - public boolean mTrailShadowEnabled; - public final float mTrailShadowRatio; - public final int mFadeoutStartDelay; - public final int mFadeoutDuration; - public final int mUpdateInterval; - - public final int mTrailLingerDuration; - - public Params(final TypedArray mainKeyboardViewAttr) { - mTrailColor = mainKeyboardViewAttr.getColor( - R.styleable.MainKeyboardView_gestureTrailColor, 0); - mTrailStartWidth = mainKeyboardViewAttr.getDimension( - R.styleable.MainKeyboardView_gestureTrailStartWidth, 0.0f); - mTrailEndWidth = mainKeyboardViewAttr.getDimension( - R.styleable.MainKeyboardView_gestureTrailEndWidth, 0.0f); - final int PERCENTAGE_INT = 100; - mTrailBodyRatio = (float)mainKeyboardViewAttr.getInt( - R.styleable.MainKeyboardView_gestureTrailBodyRatio, PERCENTAGE_INT) - / (float)PERCENTAGE_INT; - final int trailShadowRatioInt = mainKeyboardViewAttr.getInt( - R.styleable.MainKeyboardView_gestureTrailShadowRatio, 0); - mTrailShadowEnabled = (trailShadowRatioInt > 0); - mTrailShadowRatio = (float)trailShadowRatioInt / (float)PERCENTAGE_INT; - mFadeoutStartDelay = DEBUG_SHOW_POINTS ? FADEOUT_START_DELAY_FOR_DEBUG - : mainKeyboardViewAttr.getInt( - R.styleable.MainKeyboardView_gestureTrailFadeoutStartDelay, 0); - mFadeoutDuration = DEBUG_SHOW_POINTS ? FADEOUT_DURATION_FOR_DEBUG - : mainKeyboardViewAttr.getInt( - R.styleable.MainKeyboardView_gestureTrailFadeoutDuration, 0); - mTrailLingerDuration = mFadeoutStartDelay + mFadeoutDuration; - mUpdateInterval = mainKeyboardViewAttr.getInt( - R.styleable.MainKeyboardView_gestureTrailUpdateInterval, 0); - } - } - - // Use this value as imaginary zero because x-coordinates may be zero. - private static final int DOWN_EVENT_MARKER = -128; - - private static int markAsDownEvent(final int xCoord) { - return DOWN_EVENT_MARKER - xCoord; - } - - private static boolean isDownEventXCoord(final int xCoordOrMark) { - return xCoordOrMark <= DOWN_EVENT_MARKER; - } - - private static int getXCoordValue(final int xCoordOrMark) { - return isDownEventXCoord(xCoordOrMark) - ? DOWN_EVENT_MARKER - xCoordOrMark : xCoordOrMark; - } - - public void addStroke(final GestureStrokeWithPreviewPoints stroke, final long downTime) { - synchronized (mEventTimes) { - addStrokeLocked(stroke, downTime); - } - } - - private void addStrokeLocked(final GestureStrokeWithPreviewPoints stroke, final long downTime) { - final int trailSize = mEventTimes.getLength(); - stroke.appendPreviewStroke(mEventTimes, mXCoordinates, mYCoordinates, mPointTypes); - if (mEventTimes.getLength() == trailSize) { - return; - } - final int[] eventTimes = mEventTimes.getPrimitiveArray(); - final int strokeId = stroke.getGestureStrokeId(); - // Because interpolation algorithm in {@link GestureStrokeWithPreviewPoints} can't determine - // the interpolated points in the last segment of gesture stroke, it may need recalculation - // of interpolation when new segments are added to the stroke. - // {@link #mLastInterpolatedDrawIndex} holds the start index of the last segment. It may - // be updated by the interpolation - // {@link GestureStrokeWithPreviewPoints#interpolatePreviewStroke} - // or by animation {@link #drawGestureTrail(Canvas,Paint,Rect,Params)} below. - final int lastInterpolatedIndex = (strokeId == mCurrentStrokeId) - ? mLastInterpolatedDrawIndex : trailSize; - mLastInterpolatedDrawIndex = stroke.interpolateStrokeAndReturnStartIndexOfLastSegment( - lastInterpolatedIndex, mEventTimes, mXCoordinates, mYCoordinates, mPointTypes); - if (strokeId != mCurrentStrokeId) { - final int elapsedTime = (int)(downTime - mCurrentTimeBase); - for (int i = mTrailStartIndex; i < trailSize; i++) { - // Decay the previous strokes' event times. - eventTimes[i] -= elapsedTime; - } - final int[] xCoords = mXCoordinates.getPrimitiveArray(); - final int downIndex = trailSize; - xCoords[downIndex] = markAsDownEvent(xCoords[downIndex]); - mCurrentTimeBase = downTime - eventTimes[downIndex]; - mCurrentStrokeId = strokeId; - } - } - - /** - * Calculate the alpha of a gesture trail. - * A gesture trail starts from fully opaque. After mFadeStartDelay has been passed, the alpha - * of a trail reduces in proportion to the elapsed time. Then after mFadeDuration has been - * passed, a trail becomes fully transparent. - * - * @param elapsedTime the elapsed time since a trail has been made. - * @param params gesture trail display parameters - * @return the width of a gesture trail - */ - 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 - params.mFadeoutStartDelay) - / params.mFadeoutDuration; - return Constants.Color.ALPHA_OPAQUE - decreasingAlpha; - } - - /** - * Calculate the width of a gesture trail. - * A gesture trail starts from the width of mTrailStartWidth and reduces its width in proportion - * to the elapsed time. After mTrailEndWidth has been passed, the width becomes mTraiLEndWidth. - * - * @param elapsedTime the elapsed time since a trail has been made. - * @param params gesture trail display parameters - * @return the width of a gesture trail - */ - private static float getWidth(final int elapsedTime, final Params params) { - final float deltaWidth = params.mTrailStartWidth - params.mTrailEndWidth; - return params.mTrailStartWidth - (deltaWidth * elapsedTime) / params.mTrailLingerDuration; - } - - private final RoundedLine mRoundedLine = new RoundedLine(); - private final Rect mRoundedLineBounds = new Rect(); - - /** - * Draw gesture trail - * @param canvas The canvas to draw the gesture trail - * @param paint The paint object to be used to draw the gesture trail - * @param outBoundsRect the bounding box of this gesture trail drawing - * @param params The drawing parameters of gesture trail - * @return true if some gesture trails remain to be drawn - */ - public boolean drawGestureTrail(final Canvas canvas, final Paint paint, - final Rect outBoundsRect, final Params params) { - synchronized (mEventTimes) { - return drawGestureTrailLocked(canvas, paint, outBoundsRect, params); - } - } - - private boolean drawGestureTrailLocked(final Canvas canvas, final Paint paint, - final Rect outBoundsRect, final Params params) { - // Initialize bounds rectangle. - outBoundsRect.setEmpty(); - final int trailSize = mEventTimes.getLength(); - if (trailSize == 0) { - return false; - } - - final int[] eventTimes = mEventTimes.getPrimitiveArray(); - final int[] xCoords = mXCoordinates.getPrimitiveArray(); - final int[] yCoords = mYCoordinates.getPrimitiveArray(); - final int[] pointTypes = mPointTypes.getPrimitiveArray(); - final int sinceDown = (int)(SystemClock.uptimeMillis() - mCurrentTimeBase); - int startIndex; - for (startIndex = mTrailStartIndex; startIndex < trailSize; startIndex++) { - final int elapsedTime = sinceDown - eventTimes[startIndex]; - // Skip too old trail points. - if (elapsedTime < params.mTrailLingerDuration) { - break; - } - } - mTrailStartIndex = startIndex; - - if (startIndex < trailSize) { - paint.setColor(params.mTrailColor); - paint.setStyle(Paint.Style.FILL); - final RoundedLine roundedLine = mRoundedLine; - int p1x = getXCoordValue(xCoords[startIndex]); - int p1y = yCoords[startIndex]; - final int lastTime = sinceDown - eventTimes[startIndex]; - float r1 = getWidth(lastTime, params) / 2.0f; - for (int i = startIndex + 1; i < trailSize; i++) { - final int elapsedTime = sinceDown - eventTimes[i]; - final int p2x = getXCoordValue(xCoords[i]); - final int p2y = yCoords[i]; - final float r2 = getWidth(elapsedTime, params) / 2.0f; - // Draw trail line only when the current point isn't a down point. - if (!isDownEventXCoord(xCoords[i])) { - final float body1 = r1 * params.mTrailBodyRatio; - final float body2 = r2 * params.mTrailBodyRatio; - final Path path = roundedLine.makePath(p1x, p1y, body1, p2x, p2y, body2); - if (!path.isEmpty()) { - roundedLine.getBounds(mRoundedLineBounds); - if (params.mTrailShadowEnabled) { - final float shadow2 = r2 * params.mTrailShadowRatio; - paint.setShadowLayer(shadow2, 0.0f, 0.0f, params.mTrailColor); - final int shadowInset = -(int)Math.ceil(shadow2); - mRoundedLineBounds.inset(shadowInset, shadowInset); - } - // Take union for the bounds. - outBoundsRect.union(mRoundedLineBounds); - final int alpha = getAlpha(elapsedTime, params); - paint.setAlpha(alpha); - canvas.drawPath(path, paint); - } - } - p1x = p2x; - p1y = p2y; - r1 = r2; - } - if (DEBUG_SHOW_POINTS) { - debugDrawPoints(canvas, startIndex, trailSize, paint); - } - } - - final int newSize = trailSize - startIndex; - if (newSize < startIndex) { - mTrailStartIndex = 0; - if (newSize > 0) { - System.arraycopy(eventTimes, startIndex, eventTimes, 0, newSize); - System.arraycopy(xCoords, startIndex, xCoords, 0, newSize); - System.arraycopy(yCoords, startIndex, yCoords, 0, newSize); - if (DEBUG_SHOW_POINTS) { - System.arraycopy(pointTypes, startIndex, pointTypes, 0, newSize); - } - } - mEventTimes.setLength(newSize); - mXCoordinates.setLength(newSize); - mYCoordinates.setLength(newSize); - if (DEBUG_SHOW_POINTS) { - mPointTypes.setLength(newSize); - } - // The start index of the last segment of the stroke - // {@link mLastInterpolatedDrawIndex} should also be updated because all array - // elements have just been shifted for compaction or been zeroed. - mLastInterpolatedDrawIndex = Math.max(mLastInterpolatedDrawIndex - startIndex, 0); - } - return newSize > 0; - } - - private void debugDrawPoints(final Canvas canvas, final int startIndex, final int endIndex, - final Paint paint) { - final int[] xCoords = mXCoordinates.getPrimitiveArray(); - final int[] yCoords = mYCoordinates.getPrimitiveArray(); - final int[] pointTypes = mPointTypes.getPrimitiveArray(); - // {@link Paint} that is zero width stroke and anti alias off draws exactly 1 pixel. - paint.setAntiAlias(false); - paint.setStrokeWidth(0); - for (int i = startIndex; i < endIndex; i++) { - final int pointType = pointTypes[i]; - if (pointType == POINT_TYPE_INTERPOLATED) { - paint.setColor(Color.RED); - } else if (pointType == POINT_TYPE_SAMPLED) { - paint.setColor(0xFFA000FF); - } else { - paint.setColor(Color.GREEN); - } - canvas.drawPoint(getXCoordValue(xCoords[i]), yCoords[i], paint); - } - paint.setAntiAlias(true); - } -} diff --git a/java/src/com/android/inputmethod/keyboard/internal/GestureTrailDrawingParams.java b/java/src/com/android/inputmethod/keyboard/internal/GestureTrailDrawingParams.java new file mode 100644 index 000000000..088f03aa6 --- /dev/null +++ b/java/src/com/android/inputmethod/keyboard/internal/GestureTrailDrawingParams.java @@ -0,0 +1,79 @@ +/* + * Copyright (C) 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. + */ + +package com.android.inputmethod.keyboard.internal; + +import android.content.res.TypedArray; + +import com.android.inputmethod.latin.R; + +/** + * This class holds parameters to control how a gesture trail is drawn and animated on the screen. + * + * On the other hand, {@link GestureStrokeDrawingParams} class controls how each gesture stroke is + * sampled and interpolated. This class controls how those gesture strokes are displayed as a + * gesture trail and animated on the screen. + * + * @attr ref R.styleable#MainKeyboardView_gestureTrailFadeoutStartDelay + * @attr ref R.styleable#MainKeyboardView_gestureTrailFadeoutDuration + * @attr ref R.styleable#MainKeyboardView_gestureTrailUpdateInterval + * @attr ref R.styleable#MainKeyboardView_gestureTrailColor + * @attr ref R.styleable#MainKeyboardView_gestureTrailWidth + */ +final class GestureTrailDrawingParams { + private static final int FADEOUT_START_DELAY_FOR_DEBUG = 2000; // millisecond + private static final int FADEOUT_DURATION_FOR_DEBUG = 200; // millisecond + + public final int mTrailColor; + public final float mTrailStartWidth; + public final float mTrailEndWidth; + public final float mTrailBodyRatio; + public boolean mTrailShadowEnabled; + public final float mTrailShadowRatio; + public final int mFadeoutStartDelay; + public final int mFadeoutDuration; + public final int mUpdateInterval; + + public final int mTrailLingerDuration; + + public GestureTrailDrawingParams(final TypedArray mainKeyboardViewAttr) { + mTrailColor = mainKeyboardViewAttr.getColor( + R.styleable.MainKeyboardView_gestureTrailColor, 0); + mTrailStartWidth = mainKeyboardViewAttr.getDimension( + R.styleable.MainKeyboardView_gestureTrailStartWidth, 0.0f); + mTrailEndWidth = mainKeyboardViewAttr.getDimension( + R.styleable.MainKeyboardView_gestureTrailEndWidth, 0.0f); + final int PERCENTAGE_INT = 100; + mTrailBodyRatio = (float)mainKeyboardViewAttr.getInt( + R.styleable.MainKeyboardView_gestureTrailBodyRatio, PERCENTAGE_INT) + / (float)PERCENTAGE_INT; + final int trailShadowRatioInt = mainKeyboardViewAttr.getInt( + R.styleable.MainKeyboardView_gestureTrailShadowRatio, 0); + mTrailShadowEnabled = (trailShadowRatioInt > 0); + mTrailShadowRatio = (float)trailShadowRatioInt / (float)PERCENTAGE_INT; + mFadeoutStartDelay = GestureTrailDrawingPoints.DEBUG_SHOW_POINTS + ? FADEOUT_START_DELAY_FOR_DEBUG + : mainKeyboardViewAttr.getInt( + R.styleable.MainKeyboardView_gestureTrailFadeoutStartDelay, 0); + mFadeoutDuration = GestureTrailDrawingPoints.DEBUG_SHOW_POINTS + ? FADEOUT_DURATION_FOR_DEBUG + : mainKeyboardViewAttr.getInt( + R.styleable.MainKeyboardView_gestureTrailFadeoutDuration, 0); + mTrailLingerDuration = mFadeoutStartDelay + mFadeoutDuration; + mUpdateInterval = mainKeyboardViewAttr.getInt( + R.styleable.MainKeyboardView_gestureTrailUpdateInterval, 0); + } +} diff --git a/java/src/com/android/inputmethod/keyboard/internal/GestureTrailDrawingPoints.java b/java/src/com/android/inputmethod/keyboard/internal/GestureTrailDrawingPoints.java new file mode 100644 index 000000000..bf4c4da10 --- /dev/null +++ b/java/src/com/android/inputmethod/keyboard/internal/GestureTrailDrawingPoints.java @@ -0,0 +1,276 @@ +/* + * Copyright (C) 2012 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.graphics.Canvas; +import android.graphics.Color; +import android.graphics.Paint; +import android.graphics.Path; +import android.graphics.Rect; +import android.os.SystemClock; + +import com.android.inputmethod.latin.Constants; +import com.android.inputmethod.latin.utils.ResizableIntArray; + +/** + * This class holds drawing points to represent a gesture trail. The gesture trail may contain + * multiple non-contiguous gesture strokes and will be animated asynchronously from gesture input. + * + * On the other hand, {@link GestureStrokeDrawingPoints} class holds drawing points of each gesture + * stroke. This class holds drawing points of those gesture strokes to draw as a gesture trail. + * Drawing points in this class will be asynchronously removed when fading out animation goes. + */ +final class GestureTrailDrawingPoints { + public static final boolean DEBUG_SHOW_POINTS = false; + public static final int POINT_TYPE_SAMPLED = 1; + public static final int POINT_TYPE_INTERPOLATED = 2; + + private static final int DEFAULT_CAPACITY = GestureStrokeDrawingPoints.PREVIEW_CAPACITY; + + // These three {@link ResizableIntArray}s should be synchronized by {@link #mEventTimes}. + private final ResizableIntArray mXCoordinates = new ResizableIntArray(DEFAULT_CAPACITY); + private final ResizableIntArray mYCoordinates = new ResizableIntArray(DEFAULT_CAPACITY); + private final ResizableIntArray mEventTimes = new ResizableIntArray(DEFAULT_CAPACITY); + private final ResizableIntArray mPointTypes = new ResizableIntArray( + DEBUG_SHOW_POINTS ? DEFAULT_CAPACITY : 0); + private int mCurrentStrokeId = -1; + // The wall time of the zero value in {@link #mEventTimes} + private long mCurrentTimeBase; + private int mTrailStartIndex; + private int mLastInterpolatedDrawIndex; + + // Use this value as imaginary zero because x-coordinates may be zero. + private static final int DOWN_EVENT_MARKER = -128; + + private static int markAsDownEvent(final int xCoord) { + return DOWN_EVENT_MARKER - xCoord; + } + + private static boolean isDownEventXCoord(final int xCoordOrMark) { + return xCoordOrMark <= DOWN_EVENT_MARKER; + } + + private static int getXCoordValue(final int xCoordOrMark) { + return isDownEventXCoord(xCoordOrMark) + ? DOWN_EVENT_MARKER - xCoordOrMark : xCoordOrMark; + } + + public void addStroke(final GestureStrokeDrawingPoints stroke, final long downTime) { + synchronized (mEventTimes) { + addStrokeLocked(stroke, downTime); + } + } + + private void addStrokeLocked(final GestureStrokeDrawingPoints stroke, final long downTime) { + final int trailSize = mEventTimes.getLength(); + stroke.appendPreviewStroke(mEventTimes, mXCoordinates, mYCoordinates, mPointTypes); + if (mEventTimes.getLength() == trailSize) { + return; + } + final int[] eventTimes = mEventTimes.getPrimitiveArray(); + final int strokeId = stroke.getGestureStrokeId(); + // Because interpolation algorithm in {@link GestureStrokeDrawingPoints} can't determine + // the interpolated points in the last segment of gesture stroke, it may need recalculation + // of interpolation when new segments are added to the stroke. + // {@link #mLastInterpolatedDrawIndex} holds the start index of the last segment. It may + // be updated by the interpolation + // {@link GestureStrokeDrawingPoints#interpolatePreviewStroke} + // or by animation {@link #drawGestureTrail(Canvas,Paint,Rect,GestureTrailDrawingParams)} + // below. + final int lastInterpolatedIndex = (strokeId == mCurrentStrokeId) + ? mLastInterpolatedDrawIndex : trailSize; + mLastInterpolatedDrawIndex = stroke.interpolateStrokeAndReturnStartIndexOfLastSegment( + lastInterpolatedIndex, mEventTimes, mXCoordinates, mYCoordinates, mPointTypes); + if (strokeId != mCurrentStrokeId) { + final int elapsedTime = (int)(downTime - mCurrentTimeBase); + for (int i = mTrailStartIndex; i < trailSize; i++) { + // Decay the previous strokes' event times. + eventTimes[i] -= elapsedTime; + } + final int[] xCoords = mXCoordinates.getPrimitiveArray(); + final int downIndex = trailSize; + xCoords[downIndex] = markAsDownEvent(xCoords[downIndex]); + mCurrentTimeBase = downTime - eventTimes[downIndex]; + mCurrentStrokeId = strokeId; + } + } + + /** + * Calculate the alpha of a gesture trail. + * A gesture trail starts from fully opaque. After mFadeStartDelay has been passed, the alpha + * of a trail reduces in proportion to the elapsed time. Then after mFadeDuration has been + * passed, a trail becomes fully transparent. + * + * @param elapsedTime the elapsed time since a trail has been made. + * @param params gesture trail display parameters + * @return the width of a gesture trail + */ + private static int getAlpha(final int elapsedTime, final GestureTrailDrawingParams params) { + if (elapsedTime < params.mFadeoutStartDelay) { + return Constants.Color.ALPHA_OPAQUE; + } + final int decreasingAlpha = Constants.Color.ALPHA_OPAQUE + * (elapsedTime - params.mFadeoutStartDelay) + / params.mFadeoutDuration; + return Constants.Color.ALPHA_OPAQUE - decreasingAlpha; + } + + /** + * Calculate the width of a gesture trail. + * A gesture trail starts from the width of mTrailStartWidth and reduces its width in proportion + * to the elapsed time. After mTrailEndWidth has been passed, the width becomes mTraiLEndWidth. + * + * @param elapsedTime the elapsed time since a trail has been made. + * @param params gesture trail display parameters + * @return the width of a gesture trail + */ + private static float getWidth(final int elapsedTime, final GestureTrailDrawingParams params) { + final float deltaWidth = params.mTrailStartWidth - params.mTrailEndWidth; + return params.mTrailStartWidth - (deltaWidth * elapsedTime) / params.mTrailLingerDuration; + } + + private final RoundedLine mRoundedLine = new RoundedLine(); + private final Rect mRoundedLineBounds = new Rect(); + + /** + * Draw gesture trail + * @param canvas The canvas to draw the gesture trail + * @param paint The paint object to be used to draw the gesture trail + * @param outBoundsRect the bounding box of this gesture trail drawing + * @param params The drawing parameters of gesture trail + * @return true if some gesture trails remain to be drawn + */ + public boolean drawGestureTrail(final Canvas canvas, final Paint paint, + final Rect outBoundsRect, final GestureTrailDrawingParams params) { + synchronized (mEventTimes) { + return drawGestureTrailLocked(canvas, paint, outBoundsRect, params); + } + } + + private boolean drawGestureTrailLocked(final Canvas canvas, final Paint paint, + final Rect outBoundsRect, final GestureTrailDrawingParams params) { + // Initialize bounds rectangle. + outBoundsRect.setEmpty(); + final int trailSize = mEventTimes.getLength(); + if (trailSize == 0) { + return false; + } + + final int[] eventTimes = mEventTimes.getPrimitiveArray(); + final int[] xCoords = mXCoordinates.getPrimitiveArray(); + final int[] yCoords = mYCoordinates.getPrimitiveArray(); + final int[] pointTypes = mPointTypes.getPrimitiveArray(); + final int sinceDown = (int)(SystemClock.uptimeMillis() - mCurrentTimeBase); + int startIndex; + for (startIndex = mTrailStartIndex; startIndex < trailSize; startIndex++) { + final int elapsedTime = sinceDown - eventTimes[startIndex]; + // Skip too old trail points. + if (elapsedTime < params.mTrailLingerDuration) { + break; + } + } + mTrailStartIndex = startIndex; + + if (startIndex < trailSize) { + paint.setColor(params.mTrailColor); + paint.setStyle(Paint.Style.FILL); + final RoundedLine roundedLine = mRoundedLine; + int p1x = getXCoordValue(xCoords[startIndex]); + int p1y = yCoords[startIndex]; + final int lastTime = sinceDown - eventTimes[startIndex]; + float r1 = getWidth(lastTime, params) / 2.0f; + for (int i = startIndex + 1; i < trailSize; i++) { + final int elapsedTime = sinceDown - eventTimes[i]; + final int p2x = getXCoordValue(xCoords[i]); + final int p2y = yCoords[i]; + final float r2 = getWidth(elapsedTime, params) / 2.0f; + // Draw trail line only when the current point isn't a down point. + if (!isDownEventXCoord(xCoords[i])) { + final float body1 = r1 * params.mTrailBodyRatio; + final float body2 = r2 * params.mTrailBodyRatio; + final Path path = roundedLine.makePath(p1x, p1y, body1, p2x, p2y, body2); + if (!path.isEmpty()) { + roundedLine.getBounds(mRoundedLineBounds); + if (params.mTrailShadowEnabled) { + final float shadow2 = r2 * params.mTrailShadowRatio; + paint.setShadowLayer(shadow2, 0.0f, 0.0f, params.mTrailColor); + final int shadowInset = -(int)Math.ceil(shadow2); + mRoundedLineBounds.inset(shadowInset, shadowInset); + } + // Take union for the bounds. + outBoundsRect.union(mRoundedLineBounds); + final int alpha = getAlpha(elapsedTime, params); + paint.setAlpha(alpha); + canvas.drawPath(path, paint); + } + } + p1x = p2x; + p1y = p2y; + r1 = r2; + } + if (DEBUG_SHOW_POINTS) { + debugDrawPoints(canvas, startIndex, trailSize, paint); + } + } + + final int newSize = trailSize - startIndex; + if (newSize < startIndex) { + mTrailStartIndex = 0; + if (newSize > 0) { + System.arraycopy(eventTimes, startIndex, eventTimes, 0, newSize); + System.arraycopy(xCoords, startIndex, xCoords, 0, newSize); + System.arraycopy(yCoords, startIndex, yCoords, 0, newSize); + if (DEBUG_SHOW_POINTS) { + System.arraycopy(pointTypes, startIndex, pointTypes, 0, newSize); + } + } + mEventTimes.setLength(newSize); + mXCoordinates.setLength(newSize); + mYCoordinates.setLength(newSize); + if (DEBUG_SHOW_POINTS) { + mPointTypes.setLength(newSize); + } + // The start index of the last segment of the stroke + // {@link mLastInterpolatedDrawIndex} should also be updated because all array + // elements have just been shifted for compaction or been zeroed. + mLastInterpolatedDrawIndex = Math.max(mLastInterpolatedDrawIndex - startIndex, 0); + } + return newSize > 0; + } + + private void debugDrawPoints(final Canvas canvas, final int startIndex, final int endIndex, + final Paint paint) { + final int[] xCoords = mXCoordinates.getPrimitiveArray(); + final int[] yCoords = mYCoordinates.getPrimitiveArray(); + final int[] pointTypes = mPointTypes.getPrimitiveArray(); + // {@link Paint} that is zero width stroke and anti alias off draws exactly 1 pixel. + paint.setAntiAlias(false); + paint.setStrokeWidth(0); + for (int i = startIndex; i < endIndex; i++) { + final int pointType = pointTypes[i]; + if (pointType == POINT_TYPE_INTERPOLATED) { + paint.setColor(Color.RED); + } else if (pointType == POINT_TYPE_SAMPLED) { + paint.setColor(0xFFA000FF); + } else { + paint.setColor(Color.GREEN); + } + canvas.drawPoint(getXCoordValue(xCoords[i]), yCoords[i], paint); + } + paint.setAntiAlias(true); + } +} diff --git a/java/src/com/android/inputmethod/keyboard/internal/GestureTrailsDrawingPreview.java b/java/src/com/android/inputmethod/keyboard/internal/GestureTrailsDrawingPreview.java index 13f2b60d6..eef4b36ed 100644 --- a/java/src/com/android/inputmethod/keyboard/internal/GestureTrailsDrawingPreview.java +++ b/java/src/com/android/inputmethod/keyboard/internal/GestureTrailsDrawingPreview.java @@ -29,16 +29,16 @@ import android.util.SparseArray; import android.view.View; import com.android.inputmethod.keyboard.PointerTracker; -import com.android.inputmethod.keyboard.internal.GestureTrail.Params; import com.android.inputmethod.latin.utils.CollectionUtils; import com.android.inputmethod.latin.utils.LeakGuardHandlerWrapper; /** - * Draw gesture trail preview graphics during gesture. + * Draw preview graphics of multiple gesture trails during gesture input. */ public final class GestureTrailsDrawingPreview extends AbstractDrawingPreview { - private final SparseArray mGestureTrails = CollectionUtils.newSparseArray(); - private final Params mGestureTrailParams; + private final SparseArray mGestureTrails = + CollectionUtils.newSparseArray(); + private final GestureTrailDrawingParams mDrawingParams; private final Paint mGesturePaint; private int mOffscreenWidth; private int mOffscreenHeight; @@ -55,12 +55,12 @@ public final class GestureTrailsDrawingPreview extends AbstractDrawingPreview { extends LeakGuardHandlerWrapper { private static final int MSG_UPDATE_GESTURE_TRAIL = 0; - private final Params mGestureTrailParams; + private final GestureTrailDrawingParams mDrawingParams; public DrawingHandler(final GestureTrailsDrawingPreview ownerInstance, - final Params gestureTrailParams) { + final GestureTrailDrawingParams drawingParams) { super(ownerInstance); - mGestureTrailParams = gestureTrailParams; + mDrawingParams = drawingParams; } @Override @@ -79,15 +79,15 @@ public final class GestureTrailsDrawingPreview extends AbstractDrawingPreview { public void postUpdateGestureTrailPreview() { removeMessages(MSG_UPDATE_GESTURE_TRAIL); sendMessageDelayed(obtainMessage(MSG_UPDATE_GESTURE_TRAIL), - mGestureTrailParams.mUpdateInterval); + mDrawingParams.mUpdateInterval); } } public GestureTrailsDrawingPreview(final View drawingView, final TypedArray mainKeyboardViewAttr) { super(drawingView); - mGestureTrailParams = new Params(mainKeyboardViewAttr); - mDrawingHandler = new DrawingHandler(this, mGestureTrailParams); + mDrawingParams = new GestureTrailDrawingParams(mainKeyboardViewAttr); + mDrawingHandler = new DrawingHandler(this, mDrawingParams); final Paint gesturePaint = new Paint(); gesturePaint.setAntiAlias(true); gesturePaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.SRC)); @@ -96,8 +96,8 @@ public final class GestureTrailsDrawingPreview extends AbstractDrawingPreview { @Override public void setKeyboardGeometry(final int[] originCoords, final int width, final int height) { - mOffscreenOffsetY = (int)( - height * GestureStroke.EXTRA_GESTURE_TRAIL_AREA_ABOVE_KEYBOARD_RATIO); + mOffscreenOffsetY = (int)(height + * GestureStrokeRecognitionPoints.EXTRA_GESTURE_TRAIL_AREA_ABOVE_KEYBOARD_RATIO); mOffscreenWidth = width; mOffscreenHeight = mOffscreenOffsetY + height; } @@ -143,9 +143,9 @@ public final class GestureTrailsDrawingPreview extends AbstractDrawingPreview { // Trails count == fingers count that have ever been active. final int trailsCount = mGestureTrails.size(); for (int index = 0; index < trailsCount; index++) { - final GestureTrail trail = mGestureTrails.valueAt(index); + final GestureTrailDrawingPoints trail = mGestureTrails.valueAt(index); needsUpdatingGestureTrail |= trail.drawGestureTrail(offscreenCanvas, paint, - mGestureTrailBoundsRect, mGestureTrailParams); + mGestureTrailBoundsRect, mDrawingParams); // {@link #mGestureTrailBoundsRect} has bounding box of the trail. dirtyRect.union(mGestureTrailBoundsRect); } @@ -188,15 +188,15 @@ public final class GestureTrailsDrawingPreview extends AbstractDrawingPreview { if (!isPreviewEnabled()) { return; } - GestureTrail trail; + GestureTrailDrawingPoints trail; synchronized (mGestureTrails) { trail = mGestureTrails.get(tracker.mPointerId); if (trail == null) { - trail = new GestureTrail(); + trail = new GestureTrailDrawingPoints(); mGestureTrails.put(tracker.mPointerId, trail); } } - trail.addStroke(tracker.getGestureStrokeWithPreviewPoints(), tracker.getDownTime()); + trail.addStroke(tracker.getGestureStrokeDrawingPoints(), tracker.getDownTime()); // TODO: Should narrow the invalidate region. getDrawingView().invalidate(); -- cgit v1.2.3-83-g751a