diff options
32 files changed, 1154 insertions, 399 deletions
diff --git a/java/res/values/attrs.xml b/java/res/values/attrs.xml index 4975d6540..cced45d9f 100644 --- a/java/res/values/attrs.xml +++ b/java/res/values/attrs.xml @@ -131,6 +131,12 @@ <attr name="gestureFloatingPreviewTextConnectorWidth" format="dimension" /> <!-- Delay after gesture input and gesture floating preview text dismissing in millisecond --> <attr name="gestureFloatingPreviewTextLingerTimeout" format="integer" /> + <!-- Delay after gesture trail starts fading out in millisecond. --> + <attr name="gesturePreviewTrailFadeoutStartDelay" format="integer" /> + <!-- Duration while gesture preview trail is fading out in millisecond. --> + <attr name="gesturePreviewTrailFadeoutDuration" format="integer" /> + <!-- Interval of updating gesture preview trail in millisecond. --> + <attr name="gesturePreviewTrailUpdateInterval" format="integer" /> <attr name="gesturePreviewTrailColor" format="color" /> <attr name="gesturePreviewTrailWidth" format="dimension" /> </declare-styleable> diff --git a/java/res/values/config.xml b/java/res/values/config.xml index 54a6687a3..8e2d43e4e 100644 --- a/java/res/values/config.xml +++ b/java/res/values/config.xml @@ -50,6 +50,9 @@ --> <integer name="config_key_preview_linger_timeout">70</integer> <integer name="config_gesture_floating_preview_text_linger_timeout">200</integer> + <integer name="config_gesture_preview_trail_fadeout_start_delay">100</integer> + <integer name="config_gesture_preview_trail_fadeout_duration">800</integer> + <integer name="config_gesture_preview_trail_update_interval">20</integer> <!-- Configuration for MainKeyboardView --> diff --git a/java/res/values/keypress-vibration-durations.xml b/java/res/values/keypress-vibration-durations.xml index 2569f2317..8f6b647b7 100644 --- a/java/res/values/keypress-vibration-durations.xml +++ b/java/res/values/keypress-vibration-durations.xml @@ -22,5 +22,6 @@ <!-- Build.HARDWARE,duration_in_milliseconds --> <item>herring,5</item> <item>tuna,5</item> + <item>mako,20</item> </string-array> </resources> diff --git a/java/res/values/keypress-volumes.xml b/java/res/values/keypress-volumes.xml index 3b433e4ab..dd86d6c38 100644 --- a/java/res/values/keypress-volumes.xml +++ b/java/res/values/keypress-volumes.xml @@ -24,5 +24,6 @@ <item>tuna,0.5</item> <item>stingray,0.4</item> <item>grouper,0.3</item> + <item>mako,0.3</item> </string-array> </resources> diff --git a/java/res/values/styles.xml b/java/res/values/styles.xml index ae67c4369..e5e7fed80 100644 --- a/java/res/values/styles.xml +++ b/java/res/values/styles.xml @@ -78,6 +78,9 @@ <item name="gestureFloatingPreviewTextConnectorColor">@android:color/white</item> <item name="gestureFloatingPreviewTextConnectorWidth">@dimen/gesture_floating_preview_text_connector_width</item> <item name="gestureFloatingPreviewTextLingerTimeout">@integer/config_gesture_floating_preview_text_linger_timeout</item> + <item name="gesturePreviewTrailFadeoutStartDelay">@integer/config_gesture_preview_trail_fadeout_start_delay</item> + <item name="gesturePreviewTrailFadeoutDuration">@integer/config_gesture_preview_trail_fadeout_duration</item> + <item name="gesturePreviewTrailUpdateInterval">@integer/config_gesture_preview_trail_update_interval</item> <item name="gesturePreviewTrailColor">@android:color/holo_blue_light</item> <item name="gesturePreviewTrailWidth">@dimen/gesture_preview_trail_width</item> <!-- Common attributes of MainKeyboardView --> diff --git a/java/src/com/android/inputmethod/keyboard/PointerTracker.java b/java/src/com/android/inputmethod/keyboard/PointerTracker.java index 67f37a7e7..b5b3ef65f 100644 --- a/java/src/com/android/inputmethod/keyboard/PointerTracker.java +++ b/java/src/com/android/inputmethod/keyboard/PointerTracker.java @@ -17,14 +17,13 @@ package com.android.inputmethod.keyboard; import android.content.res.TypedArray; -import android.graphics.Canvas; -import android.graphics.Paint; import android.os.SystemClock; import android.util.Log; import android.view.MotionEvent; import com.android.inputmethod.accessibility.AccessibilityUtils; import com.android.inputmethod.keyboard.internal.GestureStroke; +import com.android.inputmethod.keyboard.internal.GestureStrokeWithPreviewTrail; import com.android.inputmethod.keyboard.internal.PointerTrackerQueue; import com.android.inputmethod.latin.CollectionUtils; import com.android.inputmethod.latin.InputPointers; @@ -211,7 +210,7 @@ public class PointerTracker implements PointerTrackerQueue.Element { private static final KeyboardActionListener EMPTY_LISTENER = new KeyboardActionListener.Adapter(); - private final GestureStroke mGestureStroke; + private final GestureStrokeWithPreviewTrail mGestureStrokeWithPreviewTrail; public static void init(boolean hasDistinctMultitouch, boolean needsPhantomSuddenMoveEventHack) { @@ -297,7 +296,8 @@ public class PointerTracker implements PointerTrackerQueue.Element { final int trackersSize = sTrackers.size(); for (int i = 0; i < trackersSize; ++i) { final PointerTracker tracker = sTrackers.get(i); - tracker.mGestureStroke.appendIncrementalBatchPoints(sAggregratedPointers); + tracker.mGestureStrokeWithPreviewTrail.appendIncrementalBatchPoints( + sAggregratedPointers); } return sAggregratedPointers; } @@ -308,7 +308,7 @@ public class PointerTracker implements PointerTrackerQueue.Element { final int trackersSize = sTrackers.size(); for (int i = 0; i < trackersSize; ++i) { final PointerTracker tracker = sTrackers.get(i); - tracker.mGestureStroke.appendAllBatchPoints(sAggregratedPointers); + tracker.mGestureStrokeWithPreviewTrail.appendAllBatchPoints(sAggregratedPointers); } return sAggregratedPointers; } @@ -319,7 +319,7 @@ public class PointerTracker implements PointerTrackerQueue.Element { final int trackersSize = sTrackers.size(); for (int i = 0; i < trackersSize; ++i) { final PointerTracker tracker = sTrackers.get(i); - tracker.mGestureStroke.reset(); + tracker.mGestureStrokeWithPreviewTrail.reset(); } sAggregratedPointers.reset(); } @@ -329,7 +329,7 @@ public class PointerTracker implements PointerTrackerQueue.Element { throw new NullPointerException(); } mPointerId = id; - mGestureStroke = new GestureStroke(id); + mGestureStrokeWithPreviewTrail = new GestureStrokeWithPreviewTrail(id); setKeyDetectorInner(handler.getKeyDetector()); mListener = handler.getKeyboardActionListener(); mDrawingProxy = handler.getDrawingProxy(); @@ -429,7 +429,7 @@ public class PointerTracker implements PointerTrackerQueue.Element { mKeyDetector = keyDetector; mKeyboard = keyDetector.getKeyboard(); mIsAlphabetKeyboard = mKeyboard.mId.isAlphabetKeyboard(); - mGestureStroke.setGestureSampleLength(mKeyboard.mMostCommonKeyWidth); + mGestureStrokeWithPreviewTrail.setGestureSampleLength(mKeyboard.mMostCommonKeyWidth); final Key newKey = mKeyDetector.detectHitKey(mKeyX, mKeyY); if (newKey != mCurrentKey) { if (mDrawingProxy != null) { @@ -539,10 +539,8 @@ public class PointerTracker implements PointerTrackerQueue.Element { mDrawingProxy.invalidateKey(key); } - public void drawGestureTrail(final Canvas canvas, final Paint paint) { - if (mInGesture) { - mGestureStroke.drawGestureTrail(canvas, paint); - } + public GestureStrokeWithPreviewTrail getGestureStrokeWithPreviewTrail() { + return mGestureStrokeWithPreviewTrail; } public int getLastX() { @@ -692,7 +690,7 @@ public class PointerTracker implements PointerTrackerQueue.Element { mIsPossibleGesture = true; // TODO: pointer times should be relative to first down even in entire batch input // instead of resetting to 0 for each new down event. - mGestureStroke.addPoint(x, y, 0, false); + mGestureStrokeWithPreviewTrail.addPoint(x, y, 0, false); } } } @@ -733,7 +731,7 @@ public class PointerTracker implements PointerTrackerQueue.Element { final long eventTime, final boolean isHistorical, final Key key) { final int gestureTime = (int)(eventTime - tracker.getDownTime()); if (sShouldHandleGesture && mIsPossibleGesture) { - final GestureStroke stroke = mGestureStroke; + final GestureStroke stroke = mGestureStrokeWithPreviewTrail; stroke.addPoint(x, y, gestureTime, isHistorical); if (!mInGesture && stroke.isStartOfAGesture()) { startBatchInput(); diff --git a/java/src/com/android/inputmethod/keyboard/internal/GesturePreviewTrail.java b/java/src/com/android/inputmethod/keyboard/internal/GesturePreviewTrail.java new file mode 100644 index 000000000..e814d8009 --- /dev/null +++ b/java/src/com/android/inputmethod/keyboard/internal/GesturePreviewTrail.java @@ -0,0 +1,166 @@ +/* + * 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.Paint; +import android.os.SystemClock; + +import com.android.inputmethod.latin.Constants; +import com.android.inputmethod.latin.R; +import com.android.inputmethod.latin.ResizableIntArray; + +class GesturePreviewTrail { + private static final int DEFAULT_CAPACITY = GestureStrokeWithPreviewTrail.PREVIEW_CAPACITY; + + private final GesturePreviewTrailParams mPreviewParams; + private final ResizableIntArray mXCoordinates = new ResizableIntArray(DEFAULT_CAPACITY); + private final ResizableIntArray mYCoordinates = new ResizableIntArray(DEFAULT_CAPACITY); + private final ResizableIntArray mEventTimes = new ResizableIntArray(DEFAULT_CAPACITY); + private int mCurrentStrokeId = -1; + private long mCurrentDownTime; + private int mTrailStartIndex; + + // Use this value as imaginary zero because x-coordinates may be zero. + private static final int DOWN_EVENT_MARKER = -128; + + static class GesturePreviewTrailParams { + public final int mFadeoutStartDelay; + public final int mFadeoutDuration; + public final int mUpdateInterval; + + public GesturePreviewTrailParams(final TypedArray keyboardViewAttr) { + mFadeoutStartDelay = keyboardViewAttr.getInt( + R.styleable.KeyboardView_gesturePreviewTrailFadeoutStartDelay, 0); + mFadeoutDuration = keyboardViewAttr.getInt( + R.styleable.KeyboardView_gesturePreviewTrailFadeoutDuration, 0); + mUpdateInterval = keyboardViewAttr.getInt( + R.styleable.KeyboardView_gesturePreviewTrailUpdateInterval, 0); + } + } + + public GesturePreviewTrail(final GesturePreviewTrailParams params) { + mPreviewParams = params; + } + + private static int markAsDownEvent(final int xCoord) { + return DOWN_EVENT_MARKER - xCoord; + } + + 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 GestureStrokeWithPreviewTrail stroke, final long downTime) { + final int strokeId = stroke.getGestureStrokeId(); + final boolean isNewStroke = strokeId != mCurrentStrokeId; + final int trailSize = mEventTimes.getLength(); + stroke.appendPreviewStroke(mEventTimes, mXCoordinates, mYCoordinates); + final int newTrailSize = mEventTimes.getLength(); + if (stroke.getGestureStrokePreviewSize() == 0) { + return; + } + if (isNewStroke) { + final int elapsedTime = (int)(downTime - mCurrentDownTime); + final int[] eventTimes = mEventTimes.getPrimitiveArray(); + for (int i = mTrailStartIndex; i < trailSize; i++) { + eventTimes[i] -= elapsedTime; + } + + if (newTrailSize > trailSize) { + final int[] xCoords = mXCoordinates.getPrimitiveArray(); + xCoords[trailSize] = markAsDownEvent(xCoords[trailSize]); + } + mCurrentDownTime = downTime; + mCurrentStrokeId = strokeId; + } + } + + private int getAlpha(final int elapsedTime) { + if (elapsedTime < mPreviewParams.mFadeoutStartDelay) { + return Constants.Color.ALPHA_OPAQUE; + } + final int decreasingAlpha = Constants.Color.ALPHA_OPAQUE + * (elapsedTime - mPreviewParams.mFadeoutStartDelay) + / mPreviewParams.mFadeoutDuration; + return Constants.Color.ALPHA_OPAQUE - decreasingAlpha; + } + + /** + * Draw gesture preview trail + * @param canvas The canvas to draw the gesture preview trail + * @param paint The paint object to be used to draw the gesture preview trail + * @return true if some gesture preview trails remain to be drawn + */ + public boolean drawGestureTrail(final Canvas canvas, final Paint paint) { + 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 sinceDown = (int)(SystemClock.uptimeMillis() - mCurrentDownTime); + final int lingeringDuration = mPreviewParams.mFadeoutStartDelay + + mPreviewParams.mFadeoutDuration; + int startIndex; + for (startIndex = mTrailStartIndex; startIndex < trailSize; startIndex++) { + final int elapsedTime = sinceDown - eventTimes[startIndex]; + // Skip too old trail points. + if (elapsedTime < lingeringDuration) { + break; + } + } + mTrailStartIndex = startIndex; + + if (startIndex < trailSize) { + int lastX = getXCoordValue(xCoords[startIndex]); + int lastY = yCoords[startIndex]; + for (int i = startIndex + 1; i < trailSize - 1; i++) { + final int x = xCoords[i]; + final int y = yCoords[i]; + final int elapsedTime = sinceDown - eventTimes[i]; + // Draw trail line only when the current point isn't a down point. + if (!isDownEventXCoord(x)) { + paint.setAlpha(getAlpha(elapsedTime)); + canvas.drawLine(lastX, lastY, x, y, paint); + } + lastX = getXCoordValue(x); + lastY = y; + } + } + + 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); + } + mEventTimes.setLength(newSize); + mXCoordinates.setLength(newSize); + mYCoordinates.setLength(newSize); + } + return newSize > 0; + } +} diff --git a/java/src/com/android/inputmethod/keyboard/internal/GestureStroke.java b/java/src/com/android/inputmethod/keyboard/internal/GestureStroke.java index 79e977a40..292842d22 100644 --- a/java/src/com/android/inputmethod/keyboard/internal/GestureStroke.java +++ b/java/src/com/android/inputmethod/keyboard/internal/GestureStroke.java @@ -14,10 +14,6 @@ package com.android.inputmethod.keyboard.internal; -import android.graphics.Canvas; -import android.graphics.Paint; - -import com.android.inputmethod.latin.Constants; import com.android.inputmethod.latin.InputPointers; import com.android.inputmethod.latin.ResizableIntArray; @@ -48,13 +44,8 @@ public class GestureStroke { private static final float DOUBLE_PI = (float)(2.0f * Math.PI); - // Fade based on number of gesture samples, see MIN_GESTURE_SAMPLING_RATIO_TO_KEY_HEIGHT - private static final int DRAWING_GESTURE_FADE_START = 10; - private static final int DRAWING_GESTURE_FADE_RATE = 6; - - public GestureStroke(int pointerId) { + public GestureStroke(final int pointerId) { mPointerId = pointerId; - reset(); } public void setGestureSampleLength(final int keyWidth) { @@ -158,7 +149,7 @@ public class GestureStroke { if (dx == 0 && dy == 0) return 0; // Would it be faster to call atan2f() directly via JNI? Not sure about what the JIT // does with Math.atan2(). - return (float)Math.atan2((double)dy, (double)dx); + return (float)Math.atan2(dy, dx); } private static float getAngleDiff(final float a1, final float a2) { @@ -168,20 +159,4 @@ public class GestureStroke { } return diff; } - - public void drawGestureTrail(final Canvas canvas, final Paint paint) { - // TODO: These paint parameter interpolation should be tunable, possibly introduce an object - // that implements an interface such as Paint getPaint(int step, int strokePoints) - final int size = mXCoordinates.getLength(); - final int[] xCoords = mXCoordinates.getPrimitiveArray(); - final int[] yCoords = mYCoordinates.getPrimitiveArray(); - int alpha = Constants.Color.ALPHA_OPAQUE; - for (int i = size - 1; i > 0 && alpha > 0; i--) { - paint.setAlpha(alpha); - if (size - i > DRAWING_GESTURE_FADE_START) { - alpha -= DRAWING_GESTURE_FADE_RATE; - } - canvas.drawLine(xCoords[i - 1], yCoords[i - 1], xCoords[i], yCoords[i], paint); - } - } } diff --git a/java/src/com/android/inputmethod/keyboard/internal/GestureStrokeWithPreviewTrail.java b/java/src/com/android/inputmethod/keyboard/internal/GestureStrokeWithPreviewTrail.java new file mode 100644 index 000000000..6c1a9bc01 --- /dev/null +++ b/java/src/com/android/inputmethod/keyboard/internal/GestureStrokeWithPreviewTrail.java @@ -0,0 +1,70 @@ +/* + * 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.ResizableIntArray; + +public class GestureStrokeWithPreviewTrail 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 int mStrokeId; + private int mLastPreviewSize; + + public GestureStrokeWithPreviewTrail(final int pointerId) { + super(pointerId); + } + + @Override + public void reset() { + super.reset(); + mStrokeId++; + mLastPreviewSize = 0; + mPreviewEventTimes.setLength(0); + mPreviewXCoordinates.setLength(0); + mPreviewYCoordinates.setLength(0); + } + + public int getGestureStrokeId() { + return mStrokeId; + } + + public int getGestureStrokePreviewSize() { + return mPreviewEventTimes.getLength(); + } + + @Override + public void addPoint(final int x, final int y, final int time, final boolean isHistorical) { + super.addPoint(x, y, time, isHistorical); + mPreviewEventTimes.add(time); + mPreviewXCoordinates.add(x); + mPreviewYCoordinates.add(y); + } + + public void appendPreviewStroke(final ResizableIntArray eventTimes, + final ResizableIntArray xCoords, final ResizableIntArray yCoords) { + 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); + mLastPreviewSize = mPreviewEventTimes.getLength(); + } +} diff --git a/java/src/com/android/inputmethod/keyboard/internal/PreviewPlacerView.java b/java/src/com/android/inputmethod/keyboard/internal/PreviewPlacerView.java index 3f33aee5a..269b202b5 100644 --- a/java/src/com/android/inputmethod/keyboard/internal/PreviewPlacerView.java +++ b/java/src/com/android/inputmethod/keyboard/internal/PreviewPlacerView.java @@ -28,6 +28,7 @@ import android.util.SparseArray; import android.widget.RelativeLayout; import com.android.inputmethod.keyboard.PointerTracker; +import com.android.inputmethod.keyboard.internal.GesturePreviewTrail.GesturePreviewTrailParams; import com.android.inputmethod.latin.CollectionUtils; import com.android.inputmethod.latin.R; import com.android.inputmethod.latin.StaticInnerHandlerWrapper; @@ -48,7 +49,9 @@ public class PreviewPlacerView extends RelativeLayout { private int mXOrigin; private int mYOrigin; - private final SparseArray<PointerTracker> mPointers = CollectionUtils.newSparseArray(); + private final SparseArray<GesturePreviewTrail> mGesturePreviewTrails = + CollectionUtils.newSparseArray(); + private final GesturePreviewTrailParams mGesturePreviewTrailParams; private String mGestureFloatingPreviewText; private int mLastPointerX; @@ -57,23 +60,31 @@ public class PreviewPlacerView extends RelativeLayout { private boolean mDrawsGesturePreviewTrail; private boolean mDrawsGestureFloatingPreviewText; - private final DrawingHandler mDrawingHandler = new DrawingHandler(this); + private final DrawingHandler mDrawingHandler; private static class DrawingHandler extends StaticInnerHandlerWrapper<PreviewPlacerView> { private static final int MSG_DISMISS_GESTURE_FLOATING_PREVIEW_TEXT = 0; + private static final int MSG_UPDATE_GESTURE_PREVIEW_TRAIL = 1; - public DrawingHandler(PreviewPlacerView outerInstance) { + private final GesturePreviewTrailParams mGesturePreviewTrailParams; + + public DrawingHandler(final PreviewPlacerView outerInstance, + final GesturePreviewTrailParams gesturePreviewTrailParams) { super(outerInstance); + mGesturePreviewTrailParams = gesturePreviewTrailParams; } @Override - public void handleMessage(Message msg) { + public void handleMessage(final Message msg) { final PreviewPlacerView placerView = getOuterInstance(); if (placerView == null) return; switch (msg.what) { case MSG_DISMISS_GESTURE_FLOATING_PREVIEW_TEXT: placerView.setGestureFloatingPreviewText(null); break; + case MSG_UPDATE_GESTURE_PREVIEW_TRAIL: + placerView.invalidate(); + break; } } @@ -89,16 +100,27 @@ public class PreviewPlacerView extends RelativeLayout { placerView.mGestureFloatingPreviewTextLingerTimeout); } + private void cancelUpdateGestureTrailPreview() { + removeMessages(MSG_UPDATE_GESTURE_PREVIEW_TRAIL); + } + + public void postUpdateGestureTrailPreview() { + cancelUpdateGestureTrailPreview(); + sendMessageDelayed(obtainMessage(MSG_UPDATE_GESTURE_PREVIEW_TRAIL), + mGesturePreviewTrailParams.mUpdateInterval); + } + public void cancelAllMessages() { cancelDismissGestureFloatingPreviewText(); + cancelUpdateGestureTrailPreview(); } } - public PreviewPlacerView(Context context, AttributeSet attrs) { + public PreviewPlacerView(final Context context, final AttributeSet attrs) { this(context, attrs, R.attr.keyboardViewStyle); } - public PreviewPlacerView(Context context, AttributeSet attrs, int defStyle) { + public PreviewPlacerView(final Context context, final AttributeSet attrs, final int defStyle) { super(context); setWillNotDraw(false); @@ -128,8 +150,11 @@ public class PreviewPlacerView extends RelativeLayout { R.styleable.KeyboardView_gesturePreviewTrailColor, 0); final int gesturePreviewTrailWidth = keyboardViewAttr.getDimensionPixelSize( R.styleable.KeyboardView_gesturePreviewTrailWidth, 0); + mGesturePreviewTrailParams = new GesturePreviewTrailParams(keyboardViewAttr); keyboardViewAttr.recycle(); + mDrawingHandler = new DrawingHandler(this, mGesturePreviewTrailParams); + mGesturePaint = new Paint(); mGesturePaint.setAntiAlias(true); mGesturePaint.setStyle(Paint.Style.STROKE); @@ -144,21 +169,28 @@ public class PreviewPlacerView extends RelativeLayout { mTextPaint.setTextSize(gestureFloatingPreviewTextSize); } - public void setOrigin(int x, int y) { + public void setOrigin(final int x, final int y) { mXOrigin = x; mYOrigin = y; } - public void setGesturePreviewMode(boolean drawsGesturePreviewTrail, - boolean drawsGestureFloatingPreviewText) { + public void setGesturePreviewMode(final boolean drawsGesturePreviewTrail, + final boolean drawsGestureFloatingPreviewText) { mDrawsGesturePreviewTrail = drawsGesturePreviewTrail; mDrawsGestureFloatingPreviewText = drawsGestureFloatingPreviewText; } - public void invalidatePointer(PointerTracker tracker) { - synchronized (mPointers) { - mPointers.put(tracker.mPointerId, tracker); + public void invalidatePointer(final PointerTracker tracker) { + GesturePreviewTrail trail; + synchronized (mGesturePreviewTrails) { + trail = mGesturePreviewTrails.get(tracker.mPointerId); + if (trail == null) { + trail = new GesturePreviewTrail(mGesturePreviewTrailParams); + mGesturePreviewTrails.put(tracker.mPointerId, trail); + } } + trail.addStroke(tracker.getGestureStrokeWithPreviewTrail(), tracker.getDownTime()); + mLastPointerX = tracker.getLastX(); mLastPointerY = tracker.getLastY(); // TODO: Should narrow the invalidate region. @@ -166,17 +198,23 @@ public class PreviewPlacerView extends RelativeLayout { } @Override - public void onDraw(Canvas canvas) { + public void onDraw(final Canvas canvas) { super.onDraw(canvas); canvas.translate(mXOrigin, mYOrigin); if (mDrawsGesturePreviewTrail) { - synchronized (mPointers) { - final int trackerCount = mPointers.size(); - for (int index = 0; index < trackerCount; index++) { - final PointerTracker tracker = mPointers.valueAt(index); - tracker.drawGestureTrail(canvas, mGesturePaint); + boolean needsUpdatingGesturePreviewTrail = false; + synchronized (mGesturePreviewTrails) { + // Trails count == fingers count that have ever been active. + final int trailsCount = mGesturePreviewTrails.size(); + for (int index = 0; index < trailsCount; index++) { + final GesturePreviewTrail trail = mGesturePreviewTrails.valueAt(index); + needsUpdatingGesturePreviewTrail |= + trail.drawGestureTrail(canvas, mGesturePaint); } } + if (needsUpdatingGesturePreviewTrail) { + mDrawingHandler.postUpdateGestureTrailPreview(); + } } if (mDrawsGestureFloatingPreviewText) { drawGestureFloatingPreviewText(canvas, mGestureFloatingPreviewText); @@ -184,7 +222,7 @@ public class PreviewPlacerView extends RelativeLayout { canvas.translate(-mXOrigin, -mYOrigin); } - public void setGestureFloatingPreviewText(String gestureFloatingPreviewText) { + public void setGestureFloatingPreviewText(final String gestureFloatingPreviewText) { mGestureFloatingPreviewText = gestureFloatingPreviewText; invalidate(); } @@ -197,7 +235,8 @@ public class PreviewPlacerView extends RelativeLayout { mDrawingHandler.cancelAllMessages(); } - private void drawGestureFloatingPreviewText(Canvas canvas, String gestureFloatingPreviewText) { + private void drawGestureFloatingPreviewText(final Canvas canvas, + final String gestureFloatingPreviewText) { if (TextUtils.isEmpty(gestureFloatingPreviewText)) { return; } diff --git a/java/src/com/android/inputmethod/latin/BinaryDictionaryGetter.java b/java/src/com/android/inputmethod/latin/BinaryDictionaryGetter.java index 4ada909de..e1cb195bc 100644 --- a/java/src/com/android/inputmethod/latin/BinaryDictionaryGetter.java +++ b/java/src/com/android/inputmethod/latin/BinaryDictionaryGetter.java @@ -25,7 +25,10 @@ import android.content.res.AssetFileDescriptor; import android.util.Log; import java.io.File; -import java.io.RandomAccessFile; +import java.io.FileInputStream; +import java.io.IOException; +import java.nio.ByteBuffer; +import java.nio.channels.FileChannel; import java.util.ArrayList; import java.util.HashMap; import java.util.Locale; @@ -349,17 +352,21 @@ class BinaryDictionaryGetter { // ad-hock ## HACK ## if (!Locale.ENGLISH.getLanguage().equals(locale.getLanguage())) return true; + FileInputStream inStream = null; try { // Read the version of the file - final RandomAccessFile raf = new RandomAccessFile(f, "r"); - final int magic = raf.readInt(); + inStream = new FileInputStream(f); + final ByteBuffer buffer = inStream.getChannel().map( + FileChannel.MapMode.READ_ONLY, 0, f.length()); + final int magic = buffer.getInt(); if (magic != BinaryDictInputOutput.VERSION_2_MAGIC_NUMBER) { return false; } - final int formatVersion = raf.readInt(); - final int headerSize = raf.readInt(); + final int formatVersion = buffer.getInt(); + final int headerSize = buffer.getInt(); final HashMap<String, String> options = CollectionUtils.newHashMap(); - BinaryDictInputOutput.populateOptionsFromFile(raf, headerSize, options); + BinaryDictInputOutput.populateOptions(buffer, headerSize, options); + final String version = options.get(VERSION_KEY); if (null == version) { // No version in the options : the format is unexpected @@ -374,6 +381,14 @@ class BinaryDictionaryGetter { return false; } catch (NumberFormatException e) { return false; + } finally { + if (inStream != null) { + try { + inStream.close(); + } catch (IOException e) { + // do nothing + } + } } } diff --git a/java/src/com/android/inputmethod/latin/makedict/BinaryDictInputOutput.java b/java/src/com/android/inputmethod/latin/makedict/BinaryDictInputOutput.java index b23b7db34..161b94ca0 100644 --- a/java/src/com/android/inputmethod/latin/makedict/BinaryDictInputOutput.java +++ b/java/src/com/android/inputmethod/latin/makedict/BinaryDictInputOutput.java @@ -22,10 +22,13 @@ import com.android.inputmethod.latin.makedict.FusionDictionary.Node; import com.android.inputmethod.latin.makedict.FusionDictionary.WeightedString; import java.io.ByteArrayOutputStream; +import java.io.File; +import java.io.FileInputStream; import java.io.FileNotFoundException; import java.io.IOException; import java.io.OutputStream; -import java.io.RandomAccessFile; +import java.nio.ByteBuffer; +import java.nio.channels.FileChannel; import java.util.ArrayList; import java.util.Arrays; import java.util.HashMap; @@ -307,33 +310,32 @@ public class BinaryDictInputOutput { } /** - * Reads a string from a RandomAccessFile. This is the converse of the above method. + * Reads a string from a ByteBuffer. This is the converse of the above method. */ - private static String readString(final RandomAccessFile source) throws IOException { + private static String readString(final ByteBuffer buffer) { final StringBuilder s = new StringBuilder(); - int character = readChar(source); + int character = readChar(buffer); while (character != INVALID_CHARACTER) { s.appendCodePoint(character); - character = readChar(source); + character = readChar(buffer); } return s.toString(); } /** - * Reads a character from the file. + * Reads a character from the ByteBuffer. * * This follows the character format documented earlier in this source file. * - * @param source the file, positioned over an encoded character. + * @param buffer the buffer, positioned over an encoded character. * @return the character code. */ - private static int readChar(RandomAccessFile source) throws IOException { - int character = source.readUnsignedByte(); + private static int readChar(final ByteBuffer buffer) { + int character = readUnsignedByte(buffer); if (!fitsOnOneByte(character)) { - if (GROUP_CHARACTERS_TERMINATOR == character) - return INVALID_CHARACTER; + if (GROUP_CHARACTERS_TERMINATOR == character) return INVALID_CHARACTER; character <<= 16; - character += source.readUnsignedShort(); + character += readUnsignedShort(buffer); } return character; } @@ -1091,46 +1093,46 @@ public class BinaryDictInputOutput { // readDictionaryBinary is the public entry point for them. static final int[] characterBuffer = new int[MAX_WORD_LENGTH]; - private static CharGroupInfo readCharGroup(RandomAccessFile source, - final int originalGroupAddress) throws IOException { + private static CharGroupInfo readCharGroup(final ByteBuffer buffer, + final int originalGroupAddress) { int addressPointer = originalGroupAddress; - final int flags = source.readUnsignedByte(); + final int flags = readUnsignedByte(buffer); ++addressPointer; final int characters[]; if (0 != (flags & FLAG_HAS_MULTIPLE_CHARS)) { int index = 0; - int character = CharEncoding.readChar(source); + int character = CharEncoding.readChar(buffer); addressPointer += CharEncoding.getCharSize(character); while (-1 != character) { characterBuffer[index++] = character; - character = CharEncoding.readChar(source); + character = CharEncoding.readChar(buffer); addressPointer += CharEncoding.getCharSize(character); } characters = Arrays.copyOfRange(characterBuffer, 0, index); } else { - final int character = CharEncoding.readChar(source); + final int character = CharEncoding.readChar(buffer); addressPointer += CharEncoding.getCharSize(character); characters = new int[] { character }; } final int frequency; if (0 != (FLAG_IS_TERMINAL & flags)) { ++addressPointer; - frequency = source.readUnsignedByte(); + frequency = readUnsignedByte(buffer); } else { frequency = CharGroup.NOT_A_TERMINAL; } int childrenAddress = addressPointer; switch (flags & MASK_GROUP_ADDRESS_TYPE) { case FLAG_GROUP_ADDRESS_TYPE_ONEBYTE: - childrenAddress += source.readUnsignedByte(); + childrenAddress += readUnsignedByte(buffer); addressPointer += 1; break; case FLAG_GROUP_ADDRESS_TYPE_TWOBYTES: - childrenAddress += source.readUnsignedShort(); + childrenAddress += readUnsignedShort(buffer); addressPointer += 2; break; case FLAG_GROUP_ADDRESS_TYPE_THREEBYTES: - childrenAddress += (source.readUnsignedByte() << 16) + source.readUnsignedShort(); + childrenAddress += readUnsignedInt24(buffer); addressPointer += 3; break; case FLAG_GROUP_ADDRESS_TYPE_NOADDRESS: @@ -1140,38 +1142,38 @@ public class BinaryDictInputOutput { } ArrayList<WeightedString> shortcutTargets = null; if (0 != (flags & FLAG_HAS_SHORTCUT_TARGETS)) { - final long pointerBefore = source.getFilePointer(); + final int pointerBefore = buffer.position(); shortcutTargets = new ArrayList<WeightedString>(); - source.readUnsignedShort(); // Skip the size + buffer.getShort(); // Skip the size while (true) { - final int targetFlags = source.readUnsignedByte(); - final String word = CharEncoding.readString(source); + final int targetFlags = readUnsignedByte(buffer); + final String word = CharEncoding.readString(buffer); shortcutTargets.add(new WeightedString(word, targetFlags & FLAG_ATTRIBUTE_FREQUENCY)); if (0 == (targetFlags & FLAG_ATTRIBUTE_HAS_NEXT)) break; } - addressPointer += (source.getFilePointer() - pointerBefore); + addressPointer += buffer.position() - pointerBefore; } ArrayList<PendingAttribute> bigrams = null; if (0 != (flags & FLAG_HAS_BIGRAMS)) { bigrams = new ArrayList<PendingAttribute>(); while (true) { - final int bigramFlags = source.readUnsignedByte(); + final int bigramFlags = readUnsignedByte(buffer); ++addressPointer; final int sign = 0 == (bigramFlags & FLAG_ATTRIBUTE_OFFSET_NEGATIVE) ? 1 : -1; int bigramAddress = addressPointer; switch (bigramFlags & MASK_ATTRIBUTE_ADDRESS_TYPE) { case FLAG_ATTRIBUTE_ADDRESS_TYPE_ONEBYTE: - bigramAddress += sign * source.readUnsignedByte(); + bigramAddress += sign * readUnsignedByte(buffer); addressPointer += 1; break; case FLAG_ATTRIBUTE_ADDRESS_TYPE_TWOBYTES: - bigramAddress += sign * source.readUnsignedShort(); + bigramAddress += sign * readUnsignedShort(buffer); addressPointer += 2; break; case FLAG_ATTRIBUTE_ADDRESS_TYPE_THREEBYTES: - final int offset = ((source.readUnsignedByte() << 16) - + source.readUnsignedShort()); + final int offset = (readUnsignedByte(buffer) << 16) + + readUnsignedShort(buffer); bigramAddress += sign * offset; addressPointer += 3; break; @@ -1188,15 +1190,15 @@ public class BinaryDictInputOutput { } /** - * Reads and returns the char group count out of a file and forwards the pointer. + * Reads and returns the char group count out of a buffer and forwards the pointer. */ - private static int readCharGroupCount(RandomAccessFile source) throws IOException { - final int msb = source.readUnsignedByte(); + private static int readCharGroupCount(final ByteBuffer buffer) { + final int msb = readUnsignedByte(buffer); if (MAX_CHARGROUPS_FOR_ONE_BYTE_CHARGROUP_COUNT >= msb) { return msb; } else { return ((MAX_CHARGROUPS_FOR_ONE_BYTE_CHARGROUP_COUNT & msb) << 8) - + source.readUnsignedByte(); + + readUnsignedByte(buffer); } } @@ -1204,31 +1206,29 @@ public class BinaryDictInputOutput { // of this method. Since it performs direct, unbuffered random access to the file and // may be called hundreds of thousands of times, the resulting performance is not // reasonable without some kind of cache. Thus: - // TODO: perform buffered I/O here and in other places in the code. private static TreeMap<Integer, String> wordCache = new TreeMap<Integer, String>(); /** * Finds, as a string, the word at the address passed as an argument. * - * @param source the file to read from. + * @param buffer the buffer to read from. * @param headerSize the size of the header. * @param address the address to seek. * @return the word, as a string. - * @throws IOException if the file can't be read. */ - private static String getWordAtAddress(final RandomAccessFile source, final long headerSize, - int address) throws IOException { + private static String getWordAtAddress(final ByteBuffer buffer, final int headerSize, + final int address) { final String cachedString = wordCache.get(address); if (null != cachedString) return cachedString; - final long originalPointer = source.getFilePointer(); - source.seek(headerSize); - final int count = readCharGroupCount(source); + final int originalPointer = buffer.position(); + buffer.position(headerSize); + final int count = readCharGroupCount(buffer); int groupOffset = getGroupCountSize(count); final StringBuilder builder = new StringBuilder(); String result = null; CharGroupInfo last = null; for (int i = count - 1; i >= 0; --i) { - CharGroupInfo info = readCharGroup(source, groupOffset); + CharGroupInfo info = readCharGroup(buffer, groupOffset); groupOffset = info.mEndAddress; if (info.mOriginalAddress == address) { builder.append(new String(info.mCharacters, 0, info.mCharacters.length)); @@ -1239,9 +1239,9 @@ public class BinaryDictInputOutput { if (info.mChildrenAddress > address) { if (null == last) continue; builder.append(new String(last.mCharacters, 0, last.mCharacters.length)); - source.seek(last.mChildrenAddress + headerSize); + buffer.position(last.mChildrenAddress + headerSize); groupOffset = last.mChildrenAddress + 1; - i = source.readUnsignedByte(); + i = readUnsignedByte(buffer); last = null; continue; } @@ -1249,14 +1249,14 @@ public class BinaryDictInputOutput { } if (0 == i && hasChildrenAddress(last.mChildrenAddress)) { builder.append(new String(last.mCharacters, 0, last.mCharacters.length)); - source.seek(last.mChildrenAddress + headerSize); + buffer.position(last.mChildrenAddress + headerSize); groupOffset = last.mChildrenAddress + 1; - i = source.readUnsignedByte(); + i = readUnsignedByte(buffer); last = null; continue; } } - source.seek(originalPointer); + buffer.position(originalPointer); wordCache.put(address, result); return result; } @@ -1269,44 +1269,47 @@ public class BinaryDictInputOutput { * This will recursively read other nodes into the structure, populating the reverse * maps on the fly and using them to keep track of already read nodes. * - * @param source the data file, correctly positioned at the start of a node. + * @param buffer the buffer, correctly positioned at the start of a node. * @param headerSize the size, in bytes, of the file header. * @param reverseNodeMap a mapping from addresses to already read nodes. * @param reverseGroupMap a mapping from addresses to already read character groups. * @return the read node with all his children already read. */ - private static Node readNode(RandomAccessFile source, long headerSize, - Map<Integer, Node> reverseNodeMap, Map<Integer, CharGroup> reverseGroupMap) + private static Node readNode(final ByteBuffer buffer, final int headerSize, + final Map<Integer, Node> reverseNodeMap, final Map<Integer, CharGroup> reverseGroupMap) throws IOException { - final int nodeOrigin = (int)(source.getFilePointer() - headerSize); - final int count = readCharGroupCount(source); + final int nodeOrigin = buffer.position() - headerSize; + final int count = readCharGroupCount(buffer); final ArrayList<CharGroup> nodeContents = new ArrayList<CharGroup>(); int groupOffset = nodeOrigin + getGroupCountSize(count); for (int i = count; i > 0; --i) { - CharGroupInfo info = readCharGroup(source, groupOffset); + CharGroupInfo info =readCharGroup(buffer, groupOffset); ArrayList<WeightedString> shortcutTargets = info.mShortcutTargets; ArrayList<WeightedString> bigrams = null; if (null != info.mBigrams) { bigrams = new ArrayList<WeightedString>(); for (PendingAttribute bigram : info.mBigrams) { - final String word = getWordAtAddress(source, headerSize, bigram.mAddress); + final String word = getWordAtAddress( + buffer, headerSize, bigram.mAddress); bigrams.add(new WeightedString(word, bigram.mFrequency)); } } if (hasChildrenAddress(info.mChildrenAddress)) { Node children = reverseNodeMap.get(info.mChildrenAddress); if (null == children) { - final long currentPosition = source.getFilePointer(); - source.seek(info.mChildrenAddress + headerSize); - children = readNode(source, headerSize, reverseNodeMap, reverseGroupMap); - source.seek(currentPosition); + final int currentPosition = buffer.position(); + buffer.position(info.mChildrenAddress + headerSize); + children = readNode( + buffer, headerSize, reverseNodeMap, reverseGroupMap); + buffer.position(currentPosition); } nodeContents.add( - new CharGroup(info.mCharacters, shortcutTargets, bigrams, info.mFrequency, - children)); + new CharGroup(info.mCharacters, shortcutTargets, + bigrams, info.mFrequency, children)); } else { nodeContents.add( - new CharGroup(info.mCharacters, shortcutTargets, bigrams, info.mFrequency)); + new CharGroup(info.mCharacters, shortcutTargets, + bigrams, info.mFrequency)); } groupOffset = info.mEndAddress; } @@ -1318,12 +1321,13 @@ public class BinaryDictInputOutput { /** * Helper function to get the binary format version from the header. + * @throws IOException */ - private static int getFormatVersion(final RandomAccessFile source) throws IOException { - final int magic_v1 = source.readUnsignedShort(); - if (VERSION_1_MAGIC_NUMBER == magic_v1) return source.readUnsignedByte(); - final int magic_v2 = (magic_v1 << 16) + source.readUnsignedShort(); - if (VERSION_2_MAGIC_NUMBER == magic_v2) return source.readUnsignedShort(); + private static int getFormatVersion(final ByteBuffer buffer) throws IOException { + final int magic_v1 = readUnsignedShort(buffer); + if (VERSION_1_MAGIC_NUMBER == magic_v1) return readUnsignedByte(buffer); + final int magic_v2 = (magic_v1 << 16) + readUnsignedShort(buffer); + if (VERSION_2_MAGIC_NUMBER == magic_v2) return readUnsignedShort(buffer); return NOT_A_VERSION_NUMBER; } @@ -1333,53 +1337,60 @@ public class BinaryDictInputOutput { * The file is read at the current file pointer, so the caller must take care the pointer * is in the right place before calling this. */ - public static void populateOptionsFromFile(final RandomAccessFile source, final long headerSize, - final HashMap<String, String> options) throws IOException { - while (source.getFilePointer() < headerSize) { - final String key = CharEncoding.readString(source); - final String value = CharEncoding.readString(source); + public static void populateOptions(final ByteBuffer buffer, final int headerSize, + final HashMap<String, String> options) { + while (buffer.position() < headerSize) { + final String key = CharEncoding.readString(buffer); + final String value = CharEncoding.readString(buffer); options.put(key, value); } } /** - * Reads a random access file and returns the memory representation of the dictionary. + * Reads a byte buffer and returns the memory representation of the dictionary. * * This high-level method takes a binary file and reads its contents, populating a * FusionDictionary structure. The optional dict argument is an existing dictionary to * which words from the file should be added. If it is null, a new dictionary is created. * - * @param source the file to read. + * @param buffer the buffer to read. * @param dict an optional dictionary to add words to, or null. * @return the created (or merged) dictionary. */ - public static FusionDictionary readDictionaryBinary(final RandomAccessFile source, + public static FusionDictionary readDictionaryBinary(final ByteBuffer buffer, final FusionDictionary dict) throws IOException, UnsupportedFormatException { // Check file version - final int version = getFormatVersion(source); - if (version < MINIMUM_SUPPORTED_VERSION || version > MAXIMUM_SUPPORTED_VERSION ) { + final int version = getFormatVersion(buffer); + if (version < MINIMUM_SUPPORTED_VERSION || version > MAXIMUM_SUPPORTED_VERSION) { throw new UnsupportedFormatException("This file has version " + version + ", but this implementation does not support versions above " + MAXIMUM_SUPPORTED_VERSION); } + // clear cache + wordCache.clear(); + // Read options - final int optionsFlags = source.readUnsignedShort(); + final int optionsFlags = readUnsignedShort(buffer); - final long headerSize; + final int headerSize; final HashMap<String, String> options = new HashMap<String, String>(); if (version < FIRST_VERSION_WITH_HEADER_SIZE) { - headerSize = source.getFilePointer(); + headerSize = buffer.position(); } else { - headerSize = (source.readUnsignedByte() << 24) + (source.readUnsignedByte() << 16) - + (source.readUnsignedByte() << 8) + source.readUnsignedByte(); - populateOptionsFromFile(source, headerSize, options); - source.seek(headerSize); + headerSize = buffer.getInt(); + populateOptions(buffer, headerSize, options); + buffer.position(headerSize); + } + + if (headerSize < 0) { + throw new UnsupportedFormatException("header size can't be negative."); } Map<Integer, Node> reverseNodeMapping = new TreeMap<Integer, Node>(); Map<Integer, CharGroup> reverseGroupMapping = new TreeMap<Integer, CharGroup>(); - final Node root = readNode(source, headerSize, reverseNodeMapping, reverseGroupMapping); + final Node root = readNode( + buffer, headerSize, reverseNodeMapping, reverseGroupMapping); FusionDictionary newDict = new FusionDictionary(root, new FusionDictionary.DictionaryOptions(options, @@ -1403,6 +1414,28 @@ public class BinaryDictInputOutput { } /** + * Helper function to read one byte from ByteBuffer. + */ + private static int readUnsignedByte(final ByteBuffer buffer) { + return ((int)buffer.get()) & 0xFF; + } + + /** + * Helper function to read two byte from ByteBuffer. + */ + private static int readUnsignedShort(final ByteBuffer buffer) { + return ((int)buffer.getShort()) & 0xFFFF; + } + + /** + * Helper function to read three byte from ByteBuffer. + */ + private static int readUnsignedInt24(final ByteBuffer buffer) { + final int value = readUnsignedByte(buffer) << 16; + return value + readUnsignedShort(buffer); + } + + /** * Basic test to find out whether the file is a binary dictionary or not. * * Concretely this only tests the magic number. @@ -1411,14 +1444,26 @@ public class BinaryDictInputOutput { * @return true if it's a binary dictionary, false otherwise */ public static boolean isBinaryDictionary(final String filename) { + FileInputStream inStream = null; try { - RandomAccessFile f = new RandomAccessFile(filename, "r"); - final int version = getFormatVersion(f); + final File file = new File(filename); + inStream = new FileInputStream(file); + final ByteBuffer buffer = inStream.getChannel().map( + FileChannel.MapMode.READ_ONLY, 0, file.length()); + final int version = getFormatVersion(buffer); return (version >= MINIMUM_SUPPORTED_VERSION && version <= MAXIMUM_SUPPORTED_VERSION); } catch (FileNotFoundException e) { return false; } catch (IOException e) { return false; + } finally { + if (inStream != null) { + try { + inStream.close(); + } catch (IOException e) { + // do nothing + } + } } } diff --git a/native/jni/com_android_inputmethod_latin_BinaryDictionary.cpp b/native/jni/com_android_inputmethod_latin_BinaryDictionary.cpp index 6a0a7c2a0..a20958a88 100644 --- a/native/jni/com_android_inputmethod_latin_BinaryDictionary.cpp +++ b/native/jni/com_android_inputmethod_latin_BinaryDictionary.cpp @@ -172,10 +172,10 @@ static int latinime_BinaryDictionary_getSuggestions(JNIEnv *env, jobject object, int spaceIndices[spaceIndicesLength]; const jsize outputTypesLength = env->GetArrayLength(outputTypesArray); int outputTypes[outputTypesLength]; - memset(outputChars, 0, outputCharsLength * sizeof(outputChars[0])); - memset(scores, 0, scoresLength * sizeof(scores[0])); - memset(spaceIndices, 0, spaceIndicesLength * sizeof(spaceIndices[0])); - memset(outputTypes, 0, outputTypesLength * sizeof(outputTypes[0])); + memset(outputChars, 0, sizeof(outputChars)); + memset(scores, 0, sizeof(scores)); + memset(spaceIndices, 0, sizeof(spaceIndices)); + memset(outputTypes, 0, sizeof(outputTypes)); int count; if (isGesture || arraySize > 0) { diff --git a/native/jni/src/correction.cpp b/native/jni/src/correction.cpp index 71f8a4fc8..9ad65b09d 100644 --- a/native/jni/src/correction.cpp +++ b/native/jni/src/correction.cpp @@ -61,19 +61,19 @@ inline static void dumpEditDistance10ForDebug(int *editDistanceTable, } inline static void calcEditDistanceOneStep(int *editDistanceTable, const unsigned short *input, - const int inputLength, const unsigned short *output, const int outputLength) { + const int inputSize, const unsigned short *output, const int outputLength) { // TODO: Make sure that editDistance[0 ~ MAX_WORD_LENGTH_INTERNAL] is not touched. - // Let dp[i][j] be editDistanceTable[i * (inputLength + 1) + j]. - // Assuming that dp[0][0] ... dp[outputLength - 1][inputLength] are already calculated, - // and calculate dp[ouputLength][0] ... dp[outputLength][inputLength]. - int *const current = editDistanceTable + outputLength * (inputLength + 1); - const int *const prev = editDistanceTable + (outputLength - 1) * (inputLength + 1); + // Let dp[i][j] be editDistanceTable[i * (inputSize + 1) + j]. + // Assuming that dp[0][0] ... dp[outputLength - 1][inputSize] are already calculated, + // and calculate dp[ouputLength][0] ... dp[outputLength][inputSize]. + int *const current = editDistanceTable + outputLength * (inputSize + 1); + const int *const prev = editDistanceTable + (outputLength - 1) * (inputSize + 1); const int *const prevprev = - outputLength >= 2 ? editDistanceTable + (outputLength - 2) * (inputLength + 1) : 0; + outputLength >= 2 ? editDistanceTable + (outputLength - 2) * (inputSize + 1) : 0; current[0] = outputLength; const uint32_t co = toBaseLowerCase(output[outputLength - 1]); const uint32_t prevCO = outputLength >= 2 ? toBaseLowerCase(output[outputLength - 2]) : 0; - for (int i = 1; i <= inputLength; ++i) { + for (int i = 1; i <= inputSize; ++i) { const uint32_t ci = toBaseLowerCase(input[i - 1]); const uint16_t cost = (ci == co) ? 0 : 1; current[i] = min(current[i - 1] + 1, min(prev[i] + 1, prev[i - 1] + cost)); @@ -84,11 +84,11 @@ inline static void calcEditDistanceOneStep(int *editDistanceTable, const unsigne } inline static int getCurrentEditDistance(int *editDistanceTable, const int editDistanceTableWidth, - const int outputLength, const int inputLength) { + const int outputLength, const int inputSize) { if (DEBUG_EDIT_DISTANCE) { - AKLOGI("getCurrentEditDistance %d, %d", inputLength, outputLength); + AKLOGI("getCurrentEditDistance %d, %d", inputSize, outputLength); } - return editDistanceTable[(editDistanceTableWidth + 1) * (outputLength) + inputLength]; + return editDistanceTable[(editDistanceTableWidth + 1) * (outputLength) + inputSize]; } ////////////////////// @@ -109,12 +109,12 @@ void Correction::resetCorrection() { mTotalTraverseCount = 0; } -void Correction::initCorrection(const ProximityInfo *pi, const int inputLength, +void Correction::initCorrection(const ProximityInfo *pi, const int inputSize, const int maxDepth) { mProximityInfo = pi; - mInputLength = inputLength; + mInputSize = inputSize; mMaxDepth = maxDepth; - mMaxEditDistance = mInputLength < 5 ? 2 : mInputLength / 2; + mMaxEditDistance = mInputSize < 5 ? 2 : mInputSize / 2; // TODO: This is not supposed to be required. Check what's going wrong with // editDistance[0 ~ MAX_WORD_LENGTH_INTERNAL] initEditDistance(mEditDistanceTable); @@ -168,22 +168,22 @@ int Correction::getFreqForSplitMultipleWords(const int *freqArray, const int *wo } int Correction::getFinalProbability(const int probability, unsigned short **word, int *wordLength) { - return getFinalProbabilityInternal(probability, word, wordLength, mInputLength); + return getFinalProbabilityInternal(probability, word, wordLength, mInputSize); } int Correction::getFinalProbabilityForSubQueue(const int probability, unsigned short **word, - int *wordLength, const int inputLength) { - return getFinalProbabilityInternal(probability, word, wordLength, inputLength); + int *wordLength, const int inputSize) { + return getFinalProbabilityInternal(probability, word, wordLength, inputSize); } int Correction::getFinalProbabilityInternal(const int probability, unsigned short **word, - int *wordLength, const int inputLength) { + int *wordLength, const int inputSize) { const int outputIndex = mTerminalOutputIndex; const int inputIndex = mTerminalInputIndex; *wordLength = outputIndex + 1; *word = mWord; int finalProbability= Correction::RankingAlgorithm::calculateFinalProbability( - inputIndex, outputIndex, probability, mEditDistanceTable, this, inputLength); + inputIndex, outputIndex, probability, mEditDistanceTable, this, inputSize); return finalProbability; } @@ -270,13 +270,13 @@ bool Correction::needsToPrune() const { // TODO: use edit distance here return mOutputIndex - 1 >= mMaxDepth || mProximityCount > mMaxEditDistance // Allow one char longer word for missing character - || (!mDoAutoCompletion && (mOutputIndex > mInputLength)); + || (!mDoAutoCompletion && (mOutputIndex > mInputSize)); } void Correction::addCharToCurrentWord(const int32_t c) { mWord[mOutputIndex] = c; const unsigned short *primaryInputWord = mProximityInfoState.getPrimaryInputWord(); - calcEditDistanceOneStep(mEditDistanceTable, primaryInputWord, mInputLength, + calcEditDistanceOneStep(mEditDistanceTable, primaryInputWord, mInputSize, mWord, mOutputIndex + 1); } @@ -325,7 +325,7 @@ Correction::CorrectionType Correction::processCharAndCalcState( // Skip checking this node if (mNeedsToTraverseAllNodes || isSingleQuote(c)) { bool incremented = false; - if (mLastCharExceeded && mInputIndex == mInputLength - 1) { + if (mLastCharExceeded && mInputIndex == mInputSize - 1) { // TODO: Do not check the proximity if EditDistance exceeds the threshold const ProximityType matchId = mProximityInfoState.getMatchedProximityId( mInputIndex, c, true, &proximityIndex); @@ -354,7 +354,7 @@ Correction::CorrectionType Correction::processCharAndCalcState( if (mExcessiveCount == 0 && mExcessivePos < mOutputIndex) { mExcessivePos = mOutputIndex; } - if (mExcessivePos < mInputLength - 1) { + if (mExcessivePos < mInputSize - 1) { mExceeding = mExcessivePos == mInputIndex && canTryCorrection; } } @@ -373,7 +373,7 @@ Correction::CorrectionType Correction::processCharAndCalcState( if (mTransposedCount == 0 && mTransposedPos < mOutputIndex) { mTransposedPos = mOutputIndex; } - if (mTransposedPos < mInputLength - 1) { + if (mTransposedPos < mInputSize - 1) { mTransposing = mInputIndex == mTransposedPos && canTryCorrection; } } @@ -392,7 +392,7 @@ Correction::CorrectionType Correction::processCharAndCalcState( } else { --mTransposedCount; if (DEBUG_CORRECTION - && (INPUTLENGTH_FOR_DEBUG <= 0 || INPUTLENGTH_FOR_DEBUG == mInputLength) + && (INPUTLENGTH_FOR_DEBUG <= 0 || INPUTLENGTH_FOR_DEBUG == mInputSize) && (MIN_OUTPUT_INDEX_FOR_DEBUG <= 0 || MIN_OUTPUT_INDEX_FOR_DEBUG < mOutputIndex)) { DUMP_WORD(mWord, mOutputIndex); @@ -423,7 +423,7 @@ Correction::CorrectionType Correction::processCharAndCalcState( && isEquivalentChar(mProximityInfoState.getMatchedProximityId( mInputIndex, mWord[mOutputIndex - 1], false))) { if (DEBUG_CORRECTION - && (INPUTLENGTH_FOR_DEBUG <= 0 || INPUTLENGTH_FOR_DEBUG == mInputLength) + && (INPUTLENGTH_FOR_DEBUG <= 0 || INPUTLENGTH_FOR_DEBUG == mInputSize) && (MIN_OUTPUT_INDEX_FOR_DEBUG <= 0 || MIN_OUTPUT_INDEX_FOR_DEBUG < mOutputIndex)) { AKLOGI("CONVERSION p->e %c", mWord[mOutputIndex - 1]); @@ -453,7 +453,7 @@ Correction::CorrectionType Correction::processCharAndCalcState( // As the current char turned out to be an unrelated char, // we will try other correction-types. Please note that mCorrectionStates[mOutputIndex] // here refers to the previous state. - if (mInputIndex < mInputLength - 1 && mOutputIndex > 0 && mTransposedCount > 0 + if (mInputIndex < mInputSize - 1 && mOutputIndex > 0 && mTransposedCount > 0 && !mCorrectionStates[mOutputIndex].mTransposing && mCorrectionStates[mOutputIndex - 1].mTransposing && isEquivalentChar(mProximityInfoState.getMatchedProximityId( @@ -490,7 +490,7 @@ Correction::CorrectionType Correction::processCharAndCalcState( ++mSkippedCount; --mProximityCount; return processSkipChar(c, isTerminal, false); - } else if (mInputIndex - 1 < mInputLength + } else if (mInputIndex - 1 < mInputSize && mSkippedCount > 0 && mCorrectionStates[mOutputIndex].mSkipping && mCorrectionStates[mOutputIndex].mAdditionalProximityMatching @@ -502,7 +502,7 @@ Correction::CorrectionType Correction::processCharAndCalcState( mProximityMatching = true; ++mProximityCount; mDistances[mOutputIndex] = ADDITIONAL_PROXIMITY_CHAR_DISTANCE_INFO; - } else if ((mExceeding || mTransposing) && mInputIndex - 1 < mInputLength + } else if ((mExceeding || mTransposing) && mInputIndex - 1 < mInputSize && isEquivalentChar( mProximityInfoState.getMatchedProximityId(mInputIndex + 1, c, false))) { // 1.2. Excessive or transpose correction @@ -513,7 +513,7 @@ Correction::CorrectionType Correction::processCharAndCalcState( incrementInputIndex(); } if (DEBUG_CORRECTION - && (INPUTLENGTH_FOR_DEBUG <= 0 || INPUTLENGTH_FOR_DEBUG == mInputLength) + && (INPUTLENGTH_FOR_DEBUG <= 0 || INPUTLENGTH_FOR_DEBUG == mInputSize) && (MIN_OUTPUT_INDEX_FOR_DEBUG <= 0 || MIN_OUTPUT_INDEX_FOR_DEBUG < mOutputIndex)) { DUMP_WORD(mWord, mOutputIndex); @@ -529,7 +529,7 @@ Correction::CorrectionType Correction::processCharAndCalcState( // 3. Skip correction ++mSkippedCount; if (DEBUG_CORRECTION - && (INPUTLENGTH_FOR_DEBUG <= 0 || INPUTLENGTH_FOR_DEBUG == mInputLength) + && (INPUTLENGTH_FOR_DEBUG <= 0 || INPUTLENGTH_FOR_DEBUG == mInputSize) && (MIN_OUTPUT_INDEX_FOR_DEBUG <= 0 || MIN_OUTPUT_INDEX_FOR_DEBUG < mOutputIndex)) { AKLOGI("SKIP: %d, %d, %d, %d, %c", mProximityCount, mSkippedCount, @@ -542,7 +542,7 @@ Correction::CorrectionType Correction::processCharAndCalcState( ++mProximityCount; mDistances[mOutputIndex] = ADDITIONAL_PROXIMITY_CHAR_DISTANCE_INFO; if (DEBUG_CORRECTION - && (INPUTLENGTH_FOR_DEBUG <= 0 || INPUTLENGTH_FOR_DEBUG == mInputLength) + && (INPUTLENGTH_FOR_DEBUG <= 0 || INPUTLENGTH_FOR_DEBUG == mInputSize) && (MIN_OUTPUT_INDEX_FOR_DEBUG <= 0 || MIN_OUTPUT_INDEX_FOR_DEBUG < mOutputIndex)) { AKLOGI("ADDITIONALPROX: %d, %d, %d, %d, %c", mProximityCount, mSkippedCount, @@ -550,7 +550,7 @@ Correction::CorrectionType Correction::processCharAndCalcState( } } else { if (DEBUG_CORRECTION - && (INPUTLENGTH_FOR_DEBUG <= 0 || INPUTLENGTH_FOR_DEBUG == mInputLength) + && (INPUTLENGTH_FOR_DEBUG <= 0 || INPUTLENGTH_FOR_DEBUG == mInputSize) && (MIN_OUTPUT_INDEX_FOR_DEBUG <= 0 || MIN_OUTPUT_INDEX_FOR_DEBUG < mOutputIndex)) { DUMP_WORD(mWord, mOutputIndex); @@ -560,7 +560,7 @@ Correction::CorrectionType Correction::processCharAndCalcState( return processUnrelatedCorrectionType(); } } else if (secondTransposing) { - // If inputIndex is greater than mInputLength, that means there is no + // If inputIndex is greater than mInputSize, that means there is no // proximity chars. So, we don't need to check proximity. mMatching = true; } else if (isEquivalentChar(matchedProximityCharId)) { @@ -573,7 +573,7 @@ Correction::CorrectionType Correction::processCharAndCalcState( mDistances[mOutputIndex] = mProximityInfoState.getNormalizedSquaredDistance(mInputIndex, proximityIndex); if (DEBUG_CORRECTION - && (INPUTLENGTH_FOR_DEBUG <= 0 || INPUTLENGTH_FOR_DEBUG == mInputLength) + && (INPUTLENGTH_FOR_DEBUG <= 0 || INPUTLENGTH_FOR_DEBUG == mInputSize) && (MIN_OUTPUT_INDEX_FOR_DEBUG <= 0 || MIN_OUTPUT_INDEX_FOR_DEBUG < mOutputIndex)) { AKLOGI("PROX: %d, %d, %d, %d, %c", mProximityCount, mSkippedCount, @@ -585,8 +585,8 @@ Correction::CorrectionType Correction::processCharAndCalcState( // 4. Last char excessive correction mLastCharExceeded = mExcessiveCount == 0 && mSkippedCount == 0 && mTransposedCount == 0 - && mProximityCount == 0 && (mInputIndex == mInputLength - 2); - const bool isSameAsUserTypedLength = (mInputLength == mInputIndex + 1) || mLastCharExceeded; + && mProximityCount == 0 && (mInputIndex == mInputSize - 2); + const bool isSameAsUserTypedLength = (mInputSize == mInputIndex + 1) || mLastCharExceeded; if (mLastCharExceeded) { ++mExcessiveCount; } @@ -597,7 +597,7 @@ Correction::CorrectionType Correction::processCharAndCalcState( } const bool needsToTryOnTerminalForTheLastPossibleExcessiveChar = - mExceeding && mInputIndex == mInputLength - 2; + mExceeding && mInputIndex == mInputSize - 2; // Finally, we are ready to go to the next character, the next "virtual node". // We should advance the input index. @@ -613,7 +613,7 @@ Correction::CorrectionType Correction::processCharAndCalcState( mTerminalInputIndex = mInputIndex - 1; mTerminalOutputIndex = mOutputIndex - 1; if (DEBUG_CORRECTION - && (INPUTLENGTH_FOR_DEBUG <= 0 || INPUTLENGTH_FOR_DEBUG == mInputLength) + && (INPUTLENGTH_FOR_DEBUG <= 0 || INPUTLENGTH_FOR_DEBUG == mInputSize) && (MIN_OUTPUT_INDEX_FOR_DEBUG <= 0 || MIN_OUTPUT_INDEX_FOR_DEBUG < mOutputIndex)) { DUMP_WORD(mWord, mOutputIndex); AKLOGI("ONTERMINAL(1): %d, %d, %d, %d, %c", mProximityCount, mSkippedCount, @@ -627,9 +627,6 @@ Correction::CorrectionType Correction::processCharAndCalcState( } } -Correction::~Correction() { -} - inline static int getQuoteCount(const unsigned short *word, const int length) { int quoteCount = 0; for (int i = 0; i < length; ++i) { @@ -651,7 +648,7 @@ inline static bool isUpperCase(unsigned short c) { /* static */ int Correction::RankingAlgorithm::calculateFinalProbability(const int inputIndex, const int outputIndex, const int freq, int *editDistanceTable, const Correction *correction, - const int inputLength) { + const int inputSize) { const int excessivePos = correction->getExcessivePos(); const int typedLetterMultiplier = correction->TYPED_LETTER_MULTIPLIER; const int fullWordMultiplier = correction->FULL_WORD_MULTIPLIER; @@ -663,55 +660,55 @@ int Correction::RankingAlgorithm::calculateFinalProbability(const int inputIndex const bool lastCharExceeded = correction->mLastCharExceeded; const bool useFullEditDistance = correction->mUseFullEditDistance; const int outputLength = outputIndex + 1; - if (skippedCount >= inputLength || inputLength == 0) { + if (skippedCount >= inputSize || inputSize == 0) { return -1; } // TODO: find more robust way - bool sameLength = lastCharExceeded ? (inputLength == inputIndex + 2) - : (inputLength == inputIndex + 1); + bool sameLength = lastCharExceeded ? (inputSize == inputIndex + 2) + : (inputSize == inputIndex + 1); // TODO: use mExcessiveCount - const int matchCount = inputLength - correction->mProximityCount - excessiveCount; + const int matchCount = inputSize - correction->mProximityCount - excessiveCount; const unsigned short *word = correction->mWord; const bool skipped = skippedCount > 0; const int quoteDiffCount = max(0, getQuoteCount(word, outputLength) - - getQuoteCount(proximityInfoState->getPrimaryInputWord(), inputLength)); + - getQuoteCount(proximityInfoState->getPrimaryInputWord(), inputSize)); // TODO: Calculate edit distance for transposed and excessive int ed = 0; if (DEBUG_DICT_FULL) { - dumpEditDistance10ForDebug(editDistanceTable, correction->mInputLength, outputLength); + dumpEditDistance10ForDebug(editDistanceTable, correction->mInputSize, outputLength); } int adjustedProximityMatchedCount = proximityMatchedCount; int finalFreq = freq; if (DEBUG_CORRECTION_FREQ - && (INPUTLENGTH_FOR_DEBUG <= 0 || INPUTLENGTH_FOR_DEBUG == inputLength)) { + && (INPUTLENGTH_FOR_DEBUG <= 0 || INPUTLENGTH_FOR_DEBUG == inputSize)) { AKLOGI("FinalFreq0: %d", finalFreq); } // TODO: Optimize this. if (transposedCount > 0 || proximityMatchedCount > 0 || skipped || excessiveCount > 0) { - ed = getCurrentEditDistance(editDistanceTable, correction->mInputLength, outputLength, - inputLength) - transposedCount; + ed = getCurrentEditDistance(editDistanceTable, correction->mInputSize, outputLength, + inputSize) - transposedCount; const int matchWeight = powerIntCapped(typedLetterMultiplier, - max(inputLength, outputLength) - ed); + max(inputSize, outputLength) - ed); multiplyIntCapped(matchWeight, &finalFreq); // TODO: Demote further if there are two or more excessive chars with longer user input? - if (inputLength > outputLength) { + if (inputSize > outputLength) { multiplyRate(INPUT_EXCEEDS_OUTPUT_DEMOTION_RATE, &finalFreq); } ed = max(0, ed - quoteDiffCount); - adjustedProximityMatchedCount = min(max(0, ed - (outputLength - inputLength)), + adjustedProximityMatchedCount = min(max(0, ed - (outputLength - inputSize)), proximityMatchedCount); if (transposedCount <= 0) { - if (ed == 1 && (inputLength == outputLength - 1 || inputLength == outputLength + 1)) { + if (ed == 1 && (inputSize == outputLength - 1 || inputSize == outputLength + 1)) { // Promote a word with just one skipped or excessive char if (sameLength) { multiplyRate(WORDS_WITH_JUST_ONE_CORRECTION_PROMOTION_RATE @@ -740,8 +737,8 @@ int Correction::RankingAlgorithm::calculateFinalProbability(const int inputIndex // Demotion for a word with missing character if (skipped) { const int demotionRate = WORDS_WITH_MISSING_CHARACTER_DEMOTION_RATE - * (10 * inputLength - WORDS_WITH_MISSING_CHARACTER_DEMOTION_START_POS_10X) - / (10 * inputLength + * (10 * inputSize - WORDS_WITH_MISSING_CHARACTER_DEMOTION_START_POS_10X) + / (10 * inputSize - WORDS_WITH_MISSING_CHARACTER_DEMOTION_START_POS_10X + 10); if (DEBUG_DICT_FULL) { AKLOGI("Demotion rate for missing character is %d.", demotionRate); @@ -843,7 +840,7 @@ int Correction::RankingAlgorithm::calculateFinalProbability(const int inputIndex ? adjustedProximityMatchedCount : (proximityMatchedCount + transposedCount); multiplyRate( - 100 - CORRECTION_COUNT_RATE_DEMOTION_RATE_BASE * errorCount / inputLength, &finalFreq); + 100 - CORRECTION_COUNT_RATE_DEMOTION_RATE_BASE * errorCount / inputSize, &finalFreq); // Promotion for an exactly matched word if (ed == 0) { @@ -878,7 +875,7 @@ int Correction::RankingAlgorithm::calculateFinalProbability(const int inputIndex e ... exceeding p ... proximity matching */ - if (matchCount == inputLength && matchCount >= 2 && !skipped + if (matchCount == inputSize && matchCount >= 2 && !skipped && word[matchCount] == word[matchCount - 1]) { multiplyRate(WORDS_WITH_MATCH_SKIP_PROMOTION_RATE, &finalFreq); } @@ -888,8 +885,8 @@ int Correction::RankingAlgorithm::calculateFinalProbability(const int inputIndex multiplyIntCapped(fullWordMultiplier, &finalFreq); } - if (useFullEditDistance && outputLength > inputLength + 1) { - const int diff = outputLength - inputLength - 1; + if (useFullEditDistance && outputLength > inputSize + 1) { + const int diff = outputLength - inputSize - 1; const int divider = diff < 31 ? 1 << diff : S_INT_MAX; finalFreq = divider > finalFreq ? 1 : finalFreq / divider; } @@ -899,8 +896,8 @@ int Correction::RankingAlgorithm::calculateFinalProbability(const int inputIndex } if (DEBUG_CORRECTION_FREQ - && (INPUTLENGTH_FOR_DEBUG <= 0 || INPUTLENGTH_FOR_DEBUG == inputLength)) { - DUMP_WORD(correction->getPrimaryInputWord(), inputLength); + && (INPUTLENGTH_FOR_DEBUG <= 0 || INPUTLENGTH_FOR_DEBUG == inputSize)) { + DUMP_WORD(correction->getPrimaryInputWord(), inputSize); DUMP_WORD(correction->mWord, outputLength); AKLOGI("FinalFreq: [P%d, S%d, T%d, E%d, A%d] %d, %d, %d, %d, %d, %d", proximityMatchedCount, skippedCount, transposedCount, excessiveCount, additionalProximityCount, diff --git a/native/jni/src/correction.h b/native/jni/src/correction.h index 9899f6e66..f016d5453 100644 --- a/native/jni/src/correction.h +++ b/native/jni/src/correction.h @@ -18,6 +18,7 @@ #define LATINIME_CORRECTION_H #include <cassert> +#include <cstring> // for memset() #include <stdint.h> #include "correction_state.h" @@ -38,11 +39,27 @@ class Correction { NOT_ON_TERMINAL } CorrectionType; - Correction() {}; - virtual ~Correction(); + Correction() + : mProximityInfo(0), mUseFullEditDistance(false), mDoAutoCompletion(false), + mMaxEditDistance(0), mMaxDepth(0), mInputSize(0), mSpaceProximityPos(0), + mMissingSpacePos(0), mTerminalInputIndex(0), mTerminalOutputIndex(0), mMaxErrors(0), + mTotalTraverseCount(0), mNeedsToTraverseAllNodes(false), mOutputIndex(0), + mInputIndex(0), mEquivalentCharCount(0), mProximityCount(0), mExcessiveCount(0), + mTransposedCount(0), mSkippedCount(0), mTransposedPos(0), mExcessivePos(0), + mSkipPos(0), mLastCharExceeded(false), mMatching(false), mProximityMatching(false), + mAdditionalProximityMatching(false), mExceeding(false), mTransposing(false), + mSkipping(false), mProximityInfoState() { + memset(mWord, 0, sizeof(mWord)); + memset(mDistances, 0, sizeof(mDistances)); + memset(mEditDistanceTable, 0, sizeof(mEditDistanceTable)); + // NOTE: mCorrectionStates is an array of instances. + // No need to initialize it explicitly here. + } + + virtual ~Correction() {} void resetCorrection(); void initCorrection( - const ProximityInfo *pi, const int inputLength, const int maxWordLength); + const ProximityInfo *pi, const int inputSize, const int maxWordLength); void initCorrectionState(const int rootPos, const int childCount, const bool traverseAll); // TODO: remove @@ -66,7 +83,7 @@ class Correction { const bool isSpaceProximity, const unsigned short *word); int getFinalProbability(const int probability, unsigned short **word, int *wordLength); int getFinalProbabilityForSubQueue(const int probability, unsigned short **word, - int *wordLength, const int inputLength); + int *wordLength, const int inputSize); CorrectionType processCharAndCalcState(const int32_t c, const bool isTerminal); @@ -90,7 +107,7 @@ class Correction { public: static int calculateFinalProbability(const int inputIndex, const int depth, const int probability, int *editDistanceTable, const Correction *correction, - const int inputLength); + const int inputSize); static int calcFreqForSplitMultipleWords(const int *freqArray, const int *wordLengthArray, const int wordCount, const Correction *correction, const bool isSpaceProximity, const unsigned short *word); @@ -105,9 +122,9 @@ class Correction { // proximity info state void initInputParams(const ProximityInfo *proximityInfo, const int32_t *inputCodes, - const int inputLength, const int *xCoordinates, const int *yCoordinates) { - mProximityInfoState.initInputParams( - proximityInfo, inputCodes, inputLength, xCoordinates, yCoordinates); + const int inputSize, const int *xCoordinates, const int *yCoordinates) { + mProximityInfoState.initInputParams(0, MAX_POINT_TO_KEY_LENGTH, + proximityInfo, inputCodes, inputSize, xCoordinates, yCoordinates, 0, 0, false); } const unsigned short *getPrimaryInputWord() const { @@ -204,7 +221,7 @@ class Correction { inline CorrectionType processUnrelatedCorrectionType(); inline void addCharToCurrentWord(const int32_t c); inline int getFinalProbabilityInternal(const int probability, unsigned short **word, - int *wordLength, const int inputLength); + int *wordLength, const int inputSize); static const int TYPED_LETTER_MULTIPLIER = 2; static const int FULL_WORD_MULTIPLIER = 2; @@ -214,7 +231,7 @@ class Correction { bool mDoAutoCompletion; int mMaxEditDistance; int mMaxDepth; - int mInputLength; + int mInputSize; int mSpaceProximityPos; int mMissingSpacePos; int mTerminalInputIndex; diff --git a/native/jni/src/defines.h b/native/jni/src/defines.h index 929b303d6..9b530077a 100644 --- a/native/jni/src/defines.h +++ b/native/jni/src/defines.h @@ -294,6 +294,9 @@ static inline void prof_out(void) { #define MAX_SPACES_INTERNAL 16 +// Max Distance between point to key +#define MAX_POINT_TO_KEY_LENGTH 10000000 + // TODO: Reduce this constant if possible; check the maximum number of digraphs in the same // word in the dictionary for languages with digraphs, like German and French #define DEFAULT_MAX_DIGRAPH_SEARCH_DEPTH 5 diff --git a/native/jni/src/dictionary.cpp b/native/jni/src/dictionary.cpp index 8ce8c8ba6..2fbe83e86 100644 --- a/native/jni/src/dictionary.cpp +++ b/native/jni/src/dictionary.cpp @@ -30,11 +30,15 @@ namespace latinime { // TODO: Change the type of all keyCodes to uint32_t Dictionary::Dictionary(void *dict, int dictSize, int mmapFd, int dictBufAdjust, - int typedLetterMultiplier, int fullWordMultiplier, - int maxWordLength, int maxWords, int maxPredictions) - : mDict(static_cast<unsigned char *>(dict)), - mOffsetDict((static_cast<unsigned char *>(dict)) + BinaryFormat::getHeaderSize(mDict)), - mDictSize(dictSize), mMmapFd(mmapFd), mDictBufAdjust(dictBufAdjust) { + int typedLetterMultiplier, int fullWordMultiplier, int maxWordLength, int maxWords, + int maxPredictions) + : mDict(static_cast<unsigned char *>(dict)), + mOffsetDict((static_cast<unsigned char *>(dict)) + BinaryFormat::getHeaderSize(mDict)), + mDictSize(dictSize), mMmapFd(mmapFd), mDictBufAdjust(dictBufAdjust), + mUnigramDictionary(new UnigramDictionary(mOffsetDict, typedLetterMultiplier, + fullWordMultiplier, maxWordLength, maxWords, BinaryFormat::getFlags(mDict))), + mBigramDictionary(new BigramDictionary(mOffsetDict, maxWordLength, maxPredictions)), + mGestureDecoder(new GestureDecoderWrapper(maxWordLength, maxWords)) { if (DEBUG_DICT) { if (MAX_WORD_LENGTH_INTERNAL < maxWordLength) { AKLOGI("Max word length (%d) is greater than %d", @@ -42,11 +46,6 @@ Dictionary::Dictionary(void *dict, int dictSize, int mmapFd, int dictBufAdjust, AKLOGI("IN NATIVE SUGGEST Version: %d", (mDict[0] & 0xFF)); } } - const unsigned int options = BinaryFormat::getFlags(mDict); - mUnigramDictionary = new UnigramDictionary(mOffsetDict, typedLetterMultiplier, - fullWordMultiplier, maxWordLength, maxWords, options); - mBigramDictionary = new BigramDictionary(mOffsetDict, maxWordLength, maxPredictions); - mGestureDecoder = new GestureDecoderWrapper(maxWordLength, maxWords); } Dictionary::~Dictionary() { diff --git a/native/jni/src/geometry_utils.h b/native/jni/src/geometry_utils.h index 146eb8055..f30e9fcc0 100644 --- a/native/jni/src/geometry_utils.h +++ b/native/jni/src/geometry_utils.h @@ -19,7 +19,6 @@ #include <cmath> -#define MAX_DISTANCE 10000000 #define MAX_PATHS 2 #define DEBUG_DECODER false diff --git a/native/jni/src/gesture/gesture_decoder_wrapper.h b/native/jni/src/gesture/gesture_decoder_wrapper.h index 2f6220b92..92e1ded49 100644 --- a/native/jni/src/gesture/gesture_decoder_wrapper.h +++ b/native/jni/src/gesture/gesture_decoder_wrapper.h @@ -29,8 +29,8 @@ class ProximityInfo; class GestureDecoderWrapper : public IncrementalDecoderInterface { public: - GestureDecoderWrapper(const int maxWordLength, const int maxWords) { - mIncrementalDecoderInterface = getGestureDecoderInstance(maxWordLength, maxWords); + GestureDecoderWrapper(const int maxWordLength, const int maxWords) + : mIncrementalDecoderInterface(getGestureDecoderInstance(maxWordLength, maxWords)) { } virtual ~GestureDecoderWrapper() { diff --git a/native/jni/src/gesture/incremental_decoder_wrapper.h b/native/jni/src/gesture/incremental_decoder_wrapper.h index 1f4d8a047..da7afdb8a 100644 --- a/native/jni/src/gesture/incremental_decoder_wrapper.h +++ b/native/jni/src/gesture/incremental_decoder_wrapper.h @@ -29,8 +29,8 @@ class ProximityInfo; class IncrementalDecoderWrapper : public IncrementalDecoderInterface { public: - IncrementalDecoderWrapper(const int maxWordLength, const int maxWords) { - mIncrementalDecoderInterface = getIncrementalDecoderInstance(maxWordLength, maxWords); + IncrementalDecoderWrapper(const int maxWordLength, const int maxWords) + : mIncrementalDecoderInterface(getIncrementalDecoderInstance(maxWordLength, maxWords)) { } virtual ~IncrementalDecoderWrapper() { diff --git a/native/jni/src/hash_map_compat.h b/native/jni/src/hash_map_compat.h new file mode 100644 index 000000000..116359a73 --- /dev/null +++ b/native/jni/src/hash_map_compat.h @@ -0,0 +1,34 @@ +/* + * 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. + */ + +#ifndef LATINIME_HASH_MAP_COMPAT_H +#define LATINIME_HASH_MAP_COMPAT_H + +// TODO: Use std::unordered_map that has been standardized in C++11 + +#ifdef __APPLE__ +#include <ext/hash_map> +#else // __APPLE__ +#include <hash_map> +#endif // __APPLE__ + +#ifdef __SGI_STL_PORT +#define hash_map_compat stlport::hash_map +#else // __SGI_STL_PORT +#define hash_map_compat __gnu_cxx::hash_map +#endif // __SGI_STL_PORT + +#endif // LATINIME_HASH_MAP_COMPAT_H diff --git a/native/jni/src/proximity_info.cpp b/native/jni/src/proximity_info.cpp index 1b9bac0f0..e681f6f97 100644 --- a/native/jni/src/proximity_info.cpp +++ b/native/jni/src/proximity_info.cpp @@ -29,6 +29,9 @@ namespace latinime { +/* static */ const int ProximityInfo::NOT_A_CODE = -1; +/* static */ const float ProximityInfo::NOT_A_DISTANCE_FLOAT = -1.0f; + static inline void safeGetOrFillZeroIntArrayRegion(JNIEnv *env, jintArray jArray, jsize len, jint *buffer) { if (jArray && buffer) { @@ -54,16 +57,17 @@ ProximityInfo::ProximityInfo(JNIEnv *env, const jstring localeJStr, const int ma const jintArray keyWidths, const jintArray keyHeights, const jintArray keyCharCodes, const jfloatArray sweetSpotCenterXs, const jfloatArray sweetSpotCenterYs, const jfloatArray sweetSpotRadii) - : MAX_PROXIMITY_CHARS_SIZE(maxProximityCharsSize), KEYBOARD_WIDTH(keyboardWidth), - KEYBOARD_HEIGHT(keyboardHeight), GRID_WIDTH(gridWidth), GRID_HEIGHT(gridHeight), - MOST_COMMON_KEY_WIDTH(mostCommonKeyWidth), + : MAX_PROXIMITY_CHARS_SIZE(maxProximityCharsSize), GRID_WIDTH(gridWidth), + GRID_HEIGHT(gridHeight), MOST_COMMON_KEY_WIDTH(mostCommonKeyWidth), MOST_COMMON_KEY_WIDTH_SQUARE(mostCommonKeyWidth * mostCommonKeyWidth), CELL_WIDTH((keyboardWidth + gridWidth - 1) / gridWidth), CELL_HEIGHT((keyboardHeight + gridHeight - 1) / gridHeight), KEY_COUNT(min(keyCount, MAX_KEY_COUNT_IN_A_KEYBOARD)), HAS_TOUCH_POSITION_CORRECTION_DATA(keyCount > 0 && keyXCoordinates && keyYCoordinates && keyWidths && keyHeights && keyCharCodes && sweetSpotCenterXs - && sweetSpotCenterYs && sweetSpotRadii) { + && sweetSpotCenterYs && sweetSpotRadii), + mProximityCharsArray(new int32_t[GRID_WIDTH * GRID_HEIGHT * MAX_PROXIMITY_CHARS_SIZE + /* proximityGridLength */]) { const int proximityGridLength = GRID_WIDTH * GRID_HEIGHT * MAX_PROXIMITY_CHARS_SIZE; if (DEBUG_PROXIMITY_INFO) { AKLOGI("Create proximity info array %d", proximityGridLength); @@ -75,7 +79,6 @@ ProximityInfo::ProximityInfo(JNIEnv *env, const jstring localeJStr, const int ma } memset(mLocaleStr, 0, sizeof(mLocaleStr)); env->GetStringUTFRegion(localeJStr, 0, env->GetStringLength(localeJStr), mLocaleStr); - mProximityCharsArray = new int32_t[proximityGridLength]; safeGetOrFillZeroIntArrayRegion(env, proximityChars, proximityGridLength, mProximityCharsArray); safeGetOrFillZeroIntArrayRegion(env, keyXCoordinates, KEY_COUNT, mKeyXCoordinates); safeGetOrFillZeroIntArrayRegion(env, keyYCoordinates, KEY_COUNT, mKeyYCoordinates); diff --git a/native/jni/src/proximity_info.h b/native/jni/src/proximity_info.h index 7ee633ba8..822909b7a 100644 --- a/native/jni/src/proximity_info.h +++ b/native/jni/src/proximity_info.h @@ -108,8 +108,8 @@ class ProximityInfo { static const int MAX_KEY_COUNT_IN_A_KEYBOARD = 64; // The upper limit of the char code in mCodeToKeyIndex static const int MAX_CHAR_CODE = 127; - static const float NOT_A_DISTANCE_FLOAT = -1.0f; - static const int NOT_A_CODE = -1; + static const int NOT_A_CODE; + static const float NOT_A_DISTANCE_FLOAT; int getStartIndexFromCoordinates(const int x, const int y) const; void initializeCodeToKeyIndex(); @@ -129,8 +129,6 @@ class ProximityInfo { } const int MAX_PROXIMITY_CHARS_SIZE; - const int KEYBOARD_WIDTH; - const int KEYBOARD_HEIGHT; const int GRID_WIDTH; const int GRID_HEIGHT; const int MOST_COMMON_KEY_WIDTH; diff --git a/native/jni/src/proximity_info_state.cpp b/native/jni/src/proximity_info_state.cpp index 86c8a697a..f01b81e8d 100644 --- a/native/jni/src/proximity_info_state.cpp +++ b/native/jni/src/proximity_info_state.cpp @@ -20,13 +20,15 @@ #define LOG_TAG "LatinIME: proximity_info_state.cpp" #include "defines.h" +#include "geometry_utils.h" #include "proximity_info.h" #include "proximity_info_state.h" namespace latinime { -void ProximityInfoState::initInputParams( - const ProximityInfo *proximityInfo, const int32_t *inputCodes, const int inputLength, - const int *xCoordinates, const int *yCoordinates) { +void ProximityInfoState::initInputParams(const int pointerId, const float maxPointToKeyLength, + const ProximityInfo *proximityInfo, const int32_t *const inputCodes, const int inputSize, + const int *const xCoordinates, const int *const yCoordinates, const int *const times, + const int *const pointerIds, const bool isGeometric) { mProximityInfo = proximityInfo; mHasTouchPositionCorrectionData = proximityInfo->hasTouchPositionCorrectionData(); mMostCommonKeyWidthSquare = proximityInfo->getMostCommonKeyWidthSquare(); @@ -36,78 +38,148 @@ void ProximityInfoState::initInputParams( mCellWidth = proximityInfo->getCellWidth(); mGridHeight = proximityInfo->getGridWidth(); mGridWidth = proximityInfo->getGridHeight(); - const int normalizedSquaredDistancesLength = - MAX_PROXIMITY_CHARS_SIZE_INTERNAL * MAX_WORD_LENGTH_INTERNAL; - for (int i = 0; i < normalizedSquaredDistancesLength; ++i) { - mNormalizedSquaredDistances[i] = NOT_A_DISTANCE; - } - memset(mInputCodes, 0, - MAX_WORD_LENGTH_INTERNAL * MAX_PROXIMITY_CHARS_SIZE_INTERNAL * sizeof(mInputCodes[0])); + memset(mInputCodes, 0, sizeof(mInputCodes)); - for (int i = 0; i < inputLength; ++i) { - const int32_t primaryKey = inputCodes[i]; - const int x = xCoordinates[i]; - const int y = yCoordinates[i]; - int *proximities = &mInputCodes[i * MAX_PROXIMITY_CHARS_SIZE_INTERNAL]; - mProximityInfo->calculateNearbyKeyCodes(x, y, primaryKey, proximities); - } + if (!isGeometric && pointerId == 0) { + // Initialize + // - mInputCodes + // - mNormalizedSquaredDistances + // TODO: Merge + for (int i = 0; i < inputSize; ++i) { + const int32_t primaryKey = inputCodes[i]; + const int x = xCoordinates[i]; + const int y = yCoordinates[i]; + int *proximities = &mInputCodes[i * MAX_PROXIMITY_CHARS_SIZE_INTERNAL]; + mProximityInfo->calculateNearbyKeyCodes(x, y, primaryKey, proximities); + } - if (DEBUG_PROXIMITY_CHARS) { - for (int i = 0; i < inputLength; ++i) { - AKLOGI("---"); - for (int j = 0; j < MAX_PROXIMITY_CHARS_SIZE_INTERNAL; ++j) { - int icc = mInputCodes[i * MAX_PROXIMITY_CHARS_SIZE_INTERNAL + j]; - int icfjc = inputCodes[i * MAX_PROXIMITY_CHARS_SIZE_INTERNAL + j]; - icc += 0; - icfjc += 0; - AKLOGI("--- (%d)%c,%c", i, icc, icfjc); AKLOGI("--- A<%d>,B<%d>", icc, icfjc); + if (DEBUG_PROXIMITY_CHARS) { + for (int i = 0; i < inputSize; ++i) { + AKLOGI("---"); + for (int j = 0; j < MAX_PROXIMITY_CHARS_SIZE_INTERNAL; ++j) { + int icc = mInputCodes[i * MAX_PROXIMITY_CHARS_SIZE_INTERNAL + j]; + int icfjc = inputCodes[i * MAX_PROXIMITY_CHARS_SIZE_INTERNAL + j]; + icc += 0; + icfjc += 0; + AKLOGI("--- (%d)%c,%c", i, icc, icfjc); AKLOGI("--- A<%d>,B<%d>", icc, icfjc); + } } } } - mInputXCoordinates = xCoordinates; - mInputYCoordinates = yCoordinates; - mTouchPositionCorrectionEnabled = - mHasTouchPositionCorrectionData && xCoordinates && yCoordinates; - mInputLength = inputLength; - for (int i = 0; i < inputLength; ++i) { - mPrimaryInputWord[i] = getPrimaryCharAt(i); + + /////////////////////// + // Setup touch points + mMaxPointToKeyLength = maxPointToKeyLength; + mInputXs.clear(); + mInputYs.clear(); + mTimes.clear(); + mLengthCache.clear(); + mDistanceCache.clear(); + + mInputSize = 0; + if (xCoordinates && yCoordinates) { + const bool proximityOnly = !isGeometric && (xCoordinates[0] < 0 || yCoordinates[0] < 0); + for (int i = 0; i < inputSize; ++i) { + // Assuming pointerId == 0 if pointerIds is null. + const int pid = pointerIds ? pointerIds[i] : 0; + if (pointerId == pid) { + const int c = isGeometric ? NOT_A_COORDINATE : getPrimaryCharAt(i); + const int x = proximityOnly ? NOT_A_COORDINATE : xCoordinates[i]; + const int y = proximityOnly ? NOT_A_COORDINATE : yCoordinates[i]; + const int time = times ? times[i] : -1; + if (pushTouchPoint(c, x, y, time, isGeometric)) { + ++mInputSize; + } + } + } } - mPrimaryInputWord[inputLength] = 0; - if (DEBUG_PROXIMITY_CHARS) { - AKLOGI("--- initInputParams"); + + if (mInputSize > 0) { + const int keyCount = mProximityInfo->getKeyCount(); + mDistanceCache.resize(mInputSize * keyCount); + for (int i = 0; i < mInputSize; ++i) { + for (int k = 0; k < keyCount; ++k) { + const int index = i * keyCount + k; + const int x = mInputXs[i]; + const int y = mInputYs[i]; + mDistanceCache[index] = + mProximityInfo->getNormalizedSquaredDistanceFromCenterFloat(k, x, y); + } + } } - for (int i = 0; i < mInputLength; ++i) { - const int *proximityChars = getProximityCharsAt(i); - const int primaryKey = proximityChars[0]; - const int x = xCoordinates[i]; - const int y = yCoordinates[i]; - if (DEBUG_PROXIMITY_CHARS) { - int a = x + y + primaryKey; - a += 0; - AKLOGI("--- Primary = %c, x = %d, y = %d", primaryKey, x, y); + + // end + /////////////////////// + + memset(mNormalizedSquaredDistances, NOT_A_DISTANCE, sizeof(mNormalizedSquaredDistances)); + memset(mPrimaryInputWord, 0, sizeof(mPrimaryInputWord)); + mTouchPositionCorrectionEnabled = mInputSize > 0 && mHasTouchPositionCorrectionData + && xCoordinates && yCoordinates && !isGeometric; + if (!isGeometric && pointerId == 0) { + for (int i = 0; i < inputSize; ++i) { + mPrimaryInputWord[i] = getPrimaryCharAt(i); } - for (int j = 0; j < MAX_PROXIMITY_CHARS_SIZE_INTERNAL && proximityChars[j] > 0; ++j) { - const int currentChar = proximityChars[j]; - const float squaredDistance = - hasInputCoordinates() ? calculateNormalizedSquaredDistance( - mProximityInfo->getKeyIndex(currentChar), i) : - NOT_A_DISTANCE_FLOAT; - if (squaredDistance >= 0.0f) { - mNormalizedSquaredDistances[i * MAX_PROXIMITY_CHARS_SIZE_INTERNAL + j] = - (int) (squaredDistance * NORMALIZED_SQUARED_DISTANCE_SCALING_FACTOR); - } else { - mNormalizedSquaredDistances[i * MAX_PROXIMITY_CHARS_SIZE_INTERNAL + j] = - (j == 0) ? EQUIVALENT_CHAR_WITHOUT_DISTANCE_INFO : - PROXIMITY_CHAR_WITHOUT_DISTANCE_INFO; - } + + for (int i = 0; i < mInputSize && mTouchPositionCorrectionEnabled; ++i) { + const int *proximityChars = getProximityCharsAt(i); + const int primaryKey = proximityChars[0]; + const int x = xCoordinates[i]; + const int y = yCoordinates[i]; if (DEBUG_PROXIMITY_CHARS) { - AKLOGI("--- Proximity (%d) = %c", j, currentChar); + int a = x + y + primaryKey; + a += 0; + AKLOGI("--- Primary = %c, x = %d, y = %d", primaryKey, x, y); + } + for (int j = 0; j < MAX_PROXIMITY_CHARS_SIZE_INTERNAL && proximityChars[j] > 0; ++j) { + const int currentChar = proximityChars[j]; + const float squaredDistance = + hasInputCoordinates() ? calculateNormalizedSquaredDistance( + mProximityInfo->getKeyIndex(currentChar), i) : + NOT_A_DISTANCE_FLOAT; + if (squaredDistance >= 0.0f) { + mNormalizedSquaredDistances[i * MAX_PROXIMITY_CHARS_SIZE_INTERNAL + j] = + (int) (squaredDistance * NORMALIZED_SQUARED_DISTANCE_SCALING_FACTOR); + } else { + mNormalizedSquaredDistances[i * MAX_PROXIMITY_CHARS_SIZE_INTERNAL + j] = + (j == 0) ? EQUIVALENT_CHAR_WITHOUT_DISTANCE_INFO : + PROXIMITY_CHAR_WITHOUT_DISTANCE_INFO; + } + if (DEBUG_PROXIMITY_CHARS) { + AKLOGI("--- Proximity (%d) = %c", j, currentChar); + } } } } } +bool ProximityInfoState::pushTouchPoint(const int nodeChar, int x, int y, + const int time, const bool sample) { + const uint32_t size = mInputXs.size(); + // TODO: Should have a const variable for 10 + const int sampleRate = mProximityInfo->getMostCommonKeyWidth() / 10; + if (size > 0) { + const int dist = getDistanceInt(x, y, mInputXs[size - 1], mInputYs[size - 1]); + if (sample && dist < sampleRate) { + return false; + } + mLengthCache.push_back(mLengthCache[size - 1] + dist); + } else { + mLengthCache.push_back(0); + } + if (nodeChar >= 0 && (x < 0 || y < 0)) { + const int keyId = mProximityInfo->getKeyIndex(nodeChar); + if (keyId >= 0) { + x = mProximityInfo->getKeyCenterXOfIdG(keyId); + y = mProximityInfo->getKeyCenterYOfIdG(keyId); + } + } + mInputXs.push_back(x); + mInputYs.push_back(y); + mTimes.push_back(time); + return true; +} + float ProximityInfoState::calculateNormalizedSquaredDistance( const int keyIndex, const int inputIndex) const { if (keyIndex == NOT_AN_INDEX) { @@ -116,7 +188,7 @@ float ProximityInfoState::calculateNormalizedSquaredDistance( if (!mProximityInfo->hasSweetSpotData(keyIndex)) { return NOT_A_DISTANCE_FLOAT; } - if (NOT_A_COORDINATE == mInputXCoordinates[inputIndex]) { + if (NOT_A_COORDINATE == mInputXs[inputIndex]) { return NOT_A_DISTANCE_FLOAT; } const float squaredDistance = calculateSquaredDistanceFromSweetSpotCenter( @@ -125,12 +197,37 @@ float ProximityInfoState::calculateNormalizedSquaredDistance( return squaredDistance / squaredRadius; } +int ProximityInfoState::getDuration(const int index) const { + if (mInputSize > 0 && index > 0 && index < static_cast<int>(mInputSize) - 1) { + return mTimes[index + 1] - mTimes[index - 1]; + } + return 0; +} + +float ProximityInfoState::getPointToKeyLength(int inputIndex, int charCode, float scale) { + const int keyId = mProximityInfo->getKeyIndex(charCode); + if (keyId >= 0) { + const int index = inputIndex * mProximityInfo->getKeyCount() + keyId; + return min(mDistanceCache[index] * scale, mMaxPointToKeyLength); + } + return 0; +} + +int ProximityInfoState::getKeyKeyDistance(int key0, int key1) { + return mProximityInfo->getKeyKeyDistanceG(key0, key1); +} + +int ProximityInfoState::getSpaceY() { + const int keyId = mProximityInfo->getKeyIndex(' '); + return mProximityInfo->getKeyCenterYOfIdG(keyId); +} + float ProximityInfoState::calculateSquaredDistanceFromSweetSpotCenter( const int keyIndex, const int inputIndex) const { const float sweetSpotCenterX = mProximityInfo->getSweetSpotCenterXAt(keyIndex); const float sweetSpotCenterY = mProximityInfo->getSweetSpotCenterYAt(keyIndex); - const float inputX = static_cast<float>(mInputXCoordinates[inputIndex]); - const float inputY = static_cast<float>(mInputYCoordinates[inputIndex]); + const float inputX = static_cast<float>(mInputXs[inputIndex]); + const float inputY = static_cast<float>(mInputYs[inputIndex]); return square(inputX - sweetSpotCenterX) + square(inputY - sweetSpotCenterY); } } // namespace latinime diff --git a/native/jni/src/proximity_info_state.h b/native/jni/src/proximity_info_state.h index 2fb0ae044..26fd89b36 100644 --- a/native/jni/src/proximity_info_state.h +++ b/native/jni/src/proximity_info_state.h @@ -17,8 +17,10 @@ #ifndef LATINIME_PROXIMITY_INFO_STATE_H #define LATINIME_PROXIMITY_INFO_STATE_H +#include <cstring> // for memset() #include <stdint.h> #include <string> +#include <vector> #include "char_utils.h" #include "defines.h" @@ -40,14 +42,26 @@ class ProximityInfoState { ///////////////////////////////////////// // Defined in proximity_info_state.cpp // ///////////////////////////////////////// - void initInputParams( - const ProximityInfo *proximityInfo, const int32_t *inputCodes, const int inputLength, - const int *xCoordinates, const int *yCoordinates); + void initInputParams(const int pointerId, const float maxPointToKeyLength, + const ProximityInfo *proximityInfo, const int32_t *const inputCodes, + const int inputSize, const int *xCoordinates, const int *yCoordinates, + const int *const times, const int *const pointerIds, const bool isGeometric); ///////////////////////////////////////// // Defined here // ///////////////////////////////////////// - ProximityInfoState() {}; + ProximityInfoState() + : mProximityInfo(0), mMaxPointToKeyLength(0), + mHasTouchPositionCorrectionData(false), mMostCommonKeyWidthSquare(0), mLocaleStr(), + mKeyCount(0), mCellHeight(0), mCellWidth(0), mGridHeight(0), mGridWidth(0), + mInputXs(), mInputYs(), mTimes(), mDistanceCache(), mLengthCache(), + mTouchPositionCorrectionEnabled(false), mInputSize(0) { + memset(mInputCodes, 0, sizeof(mInputCodes)); + memset(mNormalizedSquaredDistances, 0, sizeof(mNormalizedSquaredDistances)); + memset(mPrimaryInputWord, 0, sizeof(mPrimaryInputWord)); + } + + virtual ~ProximityInfoState() {} inline unsigned short getPrimaryCharAt(const int index) const { return getProximityCharsAt(index)[0]; @@ -65,14 +79,14 @@ class ProximityInfoState { } inline bool existsAdjacentProximityChars(const int index) const { - if (index < 0 || index >= mInputLength) return false; + if (index < 0 || index >= mInputSize) return false; const int currentChar = getPrimaryCharAt(index); const int leftIndex = index - 1; if (leftIndex >= 0 && existsCharInProximityAt(leftIndex, currentChar)) { return true; } const int rightIndex = index + 1; - if (rightIndex < mInputLength && existsCharInProximityAt(rightIndex, currentChar)) { + if (rightIndex < mInputSize && existsCharInProximityAt(rightIndex, currentChar)) { return true; } return false; @@ -158,7 +172,7 @@ class ProximityInfoState { } inline bool sameAsTyped(const unsigned short *word, int length) const { - if (length != mInputLength) { + if (length != mInputSize) { return false; } const int *inputCodes = mInputCodes; @@ -172,6 +186,34 @@ class ProximityInfoState { return true; } + int getDuration(const int index) const; + + bool isUsed() const { + return mInputSize > 0; + } + + uint32_t size() const { + return mInputSize; + } + + int getInputX(int index) const { + return mInputXs[index]; + } + + int getInputY(int index) const { + return mInputYs[index]; + } + + int getLengthCache(int index) const { + return mLengthCache[index]; + } + + float getPointToKeyLength(int inputIndex, int charCode, float scale); + + int getKeyKeyDistance(int key0, int key1); + + int getSpaceY(); + private: DISALLOW_COPY_AND_ASSIGN(ProximityInfoState); ///////////////////////////////////////// @@ -182,13 +224,14 @@ class ProximityInfoState { float calculateSquaredDistanceFromSweetSpotCenter( const int keyIndex, const int inputIndex) const; + bool pushTouchPoint(const int nodeChar, int x, int y, const int time, const bool sample); ///////////////////////////////////////// // Defined here // ///////////////////////////////////////// inline float square(const float x) const { return x * x; } bool hasInputCoordinates() const { - return mInputXCoordinates && mInputYCoordinates; + return mInputXs.size() > 0 && mInputYs.size() > 0; } inline const int *getProximityCharsAt(const int index) const { @@ -197,6 +240,7 @@ class ProximityInfoState { // const const ProximityInfo *mProximityInfo; + float mMaxPointToKeyLength; bool mHasTouchPositionCorrectionData; int mMostCommonKeyWidthSquare; std::string mLocaleStr; @@ -206,12 +250,15 @@ class ProximityInfoState { int mGridHeight; int mGridWidth; - const int *mInputXCoordinates; - const int *mInputYCoordinates; + std::vector<int> mInputXs; + std::vector<int> mInputYs; + std::vector<int> mTimes; + std::vector<float> mDistanceCache; + std::vector<int> mLengthCache; bool mTouchPositionCorrectionEnabled; int32_t mInputCodes[MAX_PROXIMITY_CHARS_SIZE_INTERNAL * MAX_WORD_LENGTH_INTERNAL]; int mNormalizedSquaredDistances[MAX_PROXIMITY_CHARS_SIZE_INTERNAL * MAX_WORD_LENGTH_INTERNAL]; - int mInputLength; + int mInputSize; unsigned short mPrimaryInputWord[MAX_WORD_LENGTH_INTERNAL]; }; } // namespace latinime diff --git a/native/jni/src/terminal_attributes.h b/native/jni/src/terminal_attributes.h index 1ae9c7cbb..34ab8f0ef 100644 --- a/native/jni/src/terminal_attributes.h +++ b/native/jni/src/terminal_attributes.h @@ -30,13 +30,13 @@ class TerminalAttributes { public: class ShortcutIterator { const uint8_t *const mDict; - bool mHasNextShortcutTarget; int mPos; + bool mHasNextShortcutTarget; public: - ShortcutIterator(const uint8_t *dict, const int pos, const uint8_t flags) : mDict(dict), - mPos(pos) { - mHasNextShortcutTarget = (0 != (flags & BinaryFormat::FLAG_HAS_SHORTCUT_TARGETS)); + ShortcutIterator(const uint8_t *dict, const int pos, const uint8_t flags) + : mDict(dict), mPos(pos), + mHasNextShortcutTarget(0 != (flags & BinaryFormat::FLAG_HAS_SHORTCUT_TARGETS)) { } inline bool hasNextShortcutTarget() const { @@ -62,13 +62,6 @@ class TerminalAttributes { } }; - private: - DISALLOW_IMPLICIT_CONSTRUCTORS(TerminalAttributes); - const uint8_t *const mDict; - const uint8_t mFlags; - const int mStartPos; - - public: TerminalAttributes(const uint8_t *const dict, const uint8_t flags, const int pos) : mDict(dict), mFlags(flags), mStartPos(pos) { } @@ -78,6 +71,12 @@ class TerminalAttributes { // skipped quickly, so we ignore it. return ShortcutIterator(mDict, mStartPos + BinaryFormat::SHORTCUT_LIST_SIZE_SIZE, mFlags); } + + private: + DISALLOW_IMPLICIT_CONSTRUCTORS(TerminalAttributes); + const uint8_t *const mDict; + const uint8_t mFlags; + const int mStartPos; }; } // namespace latinime #endif // LATINIME_TERMINAL_ATTRIBUTES_H diff --git a/native/jni/src/unigram_dictionary.cpp b/native/jni/src/unigram_dictionary.cpp index 705defc00..ba3c2db6b 100644 --- a/native/jni/src/unigram_dictionary.cpp +++ b/native/jni/src/unigram_dictionary.cpp @@ -237,7 +237,7 @@ int UnigramDictionary::getSuggestions(ProximityInfo *proximityInfo, void UnigramDictionary::getWordSuggestions(ProximityInfo *proximityInfo, const int *xcoordinates, const int *ycoordinates, const int *codes, - const int inputLength, const std::map<int, int> *bigramMap, const uint8_t *bigramFilter, + const int inputSize, const std::map<int, int> *bigramMap, const uint8_t *bigramFilter, const bool useFullEditDistance, Correction *correction, WordsPriorityQueuePool *queuePool) const { @@ -247,7 +247,7 @@ void UnigramDictionary::getWordSuggestions(ProximityInfo *proximityInfo, PROF_START(1); getOneWordSuggestions(proximityInfo, xcoordinates, ycoordinates, codes, bigramMap, bigramFilter, - useFullEditDistance, inputLength, correction, queuePool); + useFullEditDistance, inputSize, correction, queuePool); PROF_END(1); PROF_START(2); @@ -263,7 +263,7 @@ void UnigramDictionary::getWordSuggestions(ProximityInfo *proximityInfo, WordsPriorityQueue *masterQueue = queuePool->getMasterQueue(); if (masterQueue->size() > 0) { float nsForMaster = masterQueue->getHighestNormalizedScore( - correction->getPrimaryInputWord(), inputLength, 0, 0, 0); + correction->getPrimaryInputWord(), inputSize, 0, 0, 0); hasAutoCorrectionCandidate = (nsForMaster > START_TWO_WORDS_CORRECTION_THRESHOLD); } PROF_END(4); @@ -271,9 +271,9 @@ void UnigramDictionary::getWordSuggestions(ProximityInfo *proximityInfo, PROF_START(5); // Multiple word suggestions if (SUGGEST_MULTIPLE_WORDS - && inputLength >= MIN_USER_TYPED_LENGTH_FOR_MULTIPLE_WORD_SUGGESTION) { + && inputSize >= MIN_USER_TYPED_LENGTH_FOR_MULTIPLE_WORD_SUGGESTION) { getSplitMultipleWordsSuggestions(proximityInfo, xcoordinates, ycoordinates, codes, - useFullEditDistance, inputLength, correction, queuePool, + useFullEditDistance, inputSize, correction, queuePool, hasAutoCorrectionCandidate); } PROF_END(5); @@ -304,15 +304,15 @@ void UnigramDictionary::getWordSuggestions(ProximityInfo *proximityInfo, } void UnigramDictionary::initSuggestions(ProximityInfo *proximityInfo, const int *xCoordinates, - const int *yCoordinates, const int *codes, const int inputLength, + const int *yCoordinates, const int *codes, const int inputSize, Correction *correction) const { if (DEBUG_DICT) { AKLOGI("initSuggest"); - DUMP_WORD_INT(codes, inputLength); + DUMP_WORD_INT(codes, inputSize); } - correction->initInputParams(proximityInfo, codes, inputLength, xCoordinates, yCoordinates); - const int maxDepth = min(inputLength * MAX_DEPTH_MULTIPLIER, MAX_WORD_LENGTH); - correction->initCorrection(proximityInfo, inputLength, maxDepth); + correction->initInputParams(proximityInfo, codes, inputSize, xCoordinates, yCoordinates); + const int maxDepth = min(inputSize * MAX_DEPTH_MULTIPLIER, MAX_WORD_LENGTH); + correction->initCorrection(proximityInfo, inputSize, maxDepth); } static const char QUOTE = '\''; @@ -321,15 +321,15 @@ static const char SPACE = ' '; void UnigramDictionary::getOneWordSuggestions(ProximityInfo *proximityInfo, const int *xcoordinates, const int *ycoordinates, const int *codes, const std::map<int, int> *bigramMap, const uint8_t *bigramFilter, - const bool useFullEditDistance, const int inputLength, + const bool useFullEditDistance, const int inputSize, Correction *correction, WordsPriorityQueuePool *queuePool) const { - initSuggestions(proximityInfo, xcoordinates, ycoordinates, codes, inputLength, correction); - getSuggestionCandidates(useFullEditDistance, inputLength, bigramMap, bigramFilter, correction, + initSuggestions(proximityInfo, xcoordinates, ycoordinates, codes, inputSize, correction); + getSuggestionCandidates(useFullEditDistance, inputSize, bigramMap, bigramFilter, correction, queuePool, true /* doAutoCompletion */, DEFAULT_MAX_ERRORS, FIRST_WORD_INDEX); } void UnigramDictionary::getSuggestionCandidates(const bool useFullEditDistance, - const int inputLength, const std::map<int, int> *bigramMap, const uint8_t *bigramFilter, + const int inputSize, const std::map<int, int> *bigramMap, const uint8_t *bigramFilter, Correction *correction, WordsPriorityQueuePool *queuePool, const bool doAutoCompletion, const int maxErrors, const int currentWordIndex) const { uint8_t totalTraverseCount = correction->pushAndGetTotalTraverseCount(); @@ -351,7 +351,7 @@ void UnigramDictionary::getSuggestionCandidates(const bool useFullEditDistance, int childCount = BinaryFormat::getGroupCountAndForwardPointer(DICT_ROOT, &rootPosition); int outputIndex = 0; - correction->initCorrectionState(rootPosition, childCount, (inputLength <= 0)); + correction->initCorrectionState(rootPosition, childCount, (inputSize <= 0)); // Depth first search while (outputIndex >= 0) { @@ -446,7 +446,7 @@ inline void UnigramDictionary::onTerminal(const int probability, int UnigramDictionary::getSubStringSuggestion( ProximityInfo *proximityInfo, const int *xcoordinates, const int *ycoordinates, const int *codes, const bool useFullEditDistance, Correction *correction, - WordsPriorityQueuePool *queuePool, const int inputLength, + WordsPriorityQueuePool *queuePool, const int inputSize, const bool hasAutoCorrectionCandidate, const int currentWordIndex, const int inputWordStartPos, const int inputWordLength, const int outputWordStartPos, const bool isSpaceProximity, int *freqArray, @@ -497,7 +497,7 @@ int UnigramDictionary::getSubStringSuggestion( int nextWordLength = 0; // TODO: Optimize init suggestion initSuggestions(proximityInfo, xcoordinates, ycoordinates, codes, - inputLength, correction); + inputSize, correction); unsigned short word[MAX_WORD_LENGTH_INTERNAL]; int freq = getMostFrequentWordLike( @@ -566,7 +566,7 @@ int UnigramDictionary::getSubStringSuggestion( *outputWordLength = tempOutputWordLength; } - if ((inputWordStartPos + inputWordLength) < inputLength) { + if ((inputWordStartPos + inputWordLength) < inputSize) { if (outputWordStartPos + nextWordLength >= MAX_WORD_LENGTH) { return FLAG_MULTIPLE_SUGGEST_SKIP; } @@ -585,7 +585,7 @@ int UnigramDictionary::getSubStringSuggestion( freqArray[i], wordLengthArray[i]); } AKLOGI("Split two words: freq = %d, length = %d, %d, isSpace ? %d", pairFreq, - inputLength, tempOutputWordLength, isSpaceProximity); + inputSize, tempOutputWordLength, isSpaceProximity); } addWord(outputWord, tempOutputWordLength, pairFreq, queuePool->getMasterQueue(), Dictionary::KIND_CORRECTION); @@ -595,7 +595,7 @@ int UnigramDictionary::getSubStringSuggestion( void UnigramDictionary::getMultiWordsSuggestionRec(ProximityInfo *proximityInfo, const int *xcoordinates, const int *ycoordinates, const int *codes, - const bool useFullEditDistance, const int inputLength, + const bool useFullEditDistance, const int inputSize, Correction *correction, WordsPriorityQueuePool *queuePool, const bool hasAutoCorrectionCandidate, const int startInputPos, const int startWordIndex, const int outputWordLength, int *freqArray, int *wordLengthArray, @@ -606,11 +606,11 @@ void UnigramDictionary::getMultiWordsSuggestionRec(ProximityInfo *proximityInfo, } if (startWordIndex >= 1 && (hasAutoCorrectionCandidate - || inputLength < MIN_INPUT_LENGTH_FOR_THREE_OR_MORE_WORDS_CORRECTION)) { + || inputSize < MIN_INPUT_LENGTH_FOR_THREE_OR_MORE_WORDS_CORRECTION)) { // Do not suggest 3+ words if already has auto correction candidate return; } - for (int i = startInputPos + 1; i < inputLength; ++i) { + for (int i = startInputPos + 1; i < inputSize; ++i) { if (DEBUG_CORRECTION_FREQ) { AKLOGI("Multi words(%d), start in %d sep %d start out %d", startWordIndex, startInputPos, i, outputWordLength); @@ -621,7 +621,7 @@ void UnigramDictionary::getMultiWordsSuggestionRec(ProximityInfo *proximityInfo, int inputWordStartPos = startInputPos; int inputWordLength = i - startInputPos; const int suggestionFlag = getSubStringSuggestion(proximityInfo, xcoordinates, ycoordinates, - codes, useFullEditDistance, correction, queuePool, inputLength, + codes, useFullEditDistance, correction, queuePool, inputSize, hasAutoCorrectionCandidate, startWordIndex, inputWordStartPos, inputWordLength, outputWordLength, true /* not used */, freqArray, wordLengthArray, outputWord, &tempOutputWordLength); @@ -638,14 +638,14 @@ void UnigramDictionary::getMultiWordsSuggestionRec(ProximityInfo *proximityInfo, // Next word // Missing space inputWordStartPos = i; - inputWordLength = inputLength - i; + inputWordLength = inputSize - i; if(getSubStringSuggestion(proximityInfo, xcoordinates, ycoordinates, codes, - useFullEditDistance, correction, queuePool, inputLength, hasAutoCorrectionCandidate, + useFullEditDistance, correction, queuePool, inputSize, hasAutoCorrectionCandidate, startWordIndex + 1, inputWordStartPos, inputWordLength, tempOutputWordLength, false /* missing space */, freqArray, wordLengthArray, outputWord, 0) != FLAG_MULTIPLE_SUGGEST_CONTINUE) { getMultiWordsSuggestionRec(proximityInfo, xcoordinates, ycoordinates, codes, - useFullEditDistance, inputLength, correction, queuePool, + useFullEditDistance, inputSize, correction, queuePool, hasAutoCorrectionCandidate, inputWordStartPos, startWordIndex + 1, tempOutputWordLength, freqArray, wordLengthArray, outputWord); } @@ -668,7 +668,7 @@ void UnigramDictionary::getMultiWordsSuggestionRec(ProximityInfo *proximityInfo, AKLOGI("Do mistyped space correction"); } getSubStringSuggestion(proximityInfo, xcoordinates, ycoordinates, codes, - useFullEditDistance, correction, queuePool, inputLength, hasAutoCorrectionCandidate, + useFullEditDistance, correction, queuePool, inputSize, hasAutoCorrectionCandidate, startWordIndex + 1, inputWordStartPos, inputWordLength, tempOutputWordLength, true /* mistyped space */, freqArray, wordLengthArray, outputWord, 0); } @@ -676,10 +676,10 @@ void UnigramDictionary::getMultiWordsSuggestionRec(ProximityInfo *proximityInfo, void UnigramDictionary::getSplitMultipleWordsSuggestions(ProximityInfo *proximityInfo, const int *xcoordinates, const int *ycoordinates, const int *codes, - const bool useFullEditDistance, const int inputLength, + const bool useFullEditDistance, const int inputSize, Correction *correction, WordsPriorityQueuePool *queuePool, const bool hasAutoCorrectionCandidate) const { - if (inputLength >= MAX_WORD_LENGTH) return; + if (inputSize >= MAX_WORD_LENGTH) return; if (DEBUG_DICT) { AKLOGI("--- Suggest multiple words"); } @@ -692,7 +692,7 @@ void UnigramDictionary::getSplitMultipleWordsSuggestions(ProximityInfo *proximit const int startInputPos = 0; const int startWordIndex = 0; getMultiWordsSuggestionRec(proximityInfo, xcoordinates, ycoordinates, codes, - useFullEditDistance, inputLength, correction, queuePool, hasAutoCorrectionCandidate, + useFullEditDistance, inputSize, correction, queuePool, hasAutoCorrectionCandidate, startInputPos, startWordIndex, outputWordLength, freqArray, wordLengthArray, outputWord); } @@ -700,13 +700,13 @@ void UnigramDictionary::getSplitMultipleWordsSuggestions(ProximityInfo *proximit // Wrapper for getMostFrequentWordLikeInner, which matches it to the previous // interface. inline int UnigramDictionary::getMostFrequentWordLike(const int startInputIndex, - const int inputLength, Correction *correction, unsigned short *word) const { - uint16_t inWord[inputLength]; + const int inputSize, Correction *correction, unsigned short *word) const { + uint16_t inWord[inputSize]; - for (int i = 0; i < inputLength; ++i) { + for (int i = 0; i < inputSize; ++i) { inWord[i] = (uint16_t)correction->getPrimaryCharAt(startInputIndex + i); } - return getMostFrequentWordLikeInner(inWord, inputLength, word); + return getMostFrequentWordLikeInner(inWord, inputSize, word); } // This function will take the position of a character array within a CharGroup, diff --git a/native/jni/src/unigram_dictionary.h b/native/jni/src/unigram_dictionary.h index 6083f0175..2c6622210 100644 --- a/native/jni/src/unigram_dictionary.h +++ b/native/jni/src/unigram_dictionary.h @@ -53,7 +53,7 @@ class UnigramDictionary { private: DISALLOW_IMPLICIT_CONSTRUCTORS(UnigramDictionary); void getWordSuggestions(ProximityInfo *proximityInfo, const int *xcoordinates, - const int *ycoordinates, const int *codes, const int inputLength, + const int *ycoordinates, const int *codes, const int inputSize, const std::map<int, int> *bigramMap, const uint8_t *bigramFilter, const bool useFullEditDistance, Correction *correction, WordsPriorityQueuePool *queuePool) const; @@ -72,16 +72,16 @@ class UnigramDictionary { Correction *correction) const; void getOneWordSuggestions(ProximityInfo *proximityInfo, const int *xcoordinates, const int *ycoordinates, const int *codes, const std::map<int, int> *bigramMap, - const uint8_t *bigramFilter, const bool useFullEditDistance, const int inputLength, + const uint8_t *bigramFilter, const bool useFullEditDistance, const int inputSize, Correction *correction, WordsPriorityQueuePool *queuePool) const; void getSuggestionCandidates( - const bool useFullEditDistance, const int inputLength, + const bool useFullEditDistance, const int inputSize, const std::map<int, int> *bigramMap, const uint8_t *bigramFilter, Correction *correction, WordsPriorityQueuePool *queuePool, const bool doAutoCompletion, const int maxErrors, const int currentWordIndex) const; void getSplitMultipleWordsSuggestions(ProximityInfo *proximityInfo, const int *xcoordinates, const int *ycoordinates, const int *codes, - const bool useFullEditDistance, const int inputLength, + const bool useFullEditDistance, const int inputSize, Correction *correction, WordsPriorityQueuePool *queuePool, const bool hasAutoCorrectionCandidate) const; void onTerminal(const int freq, const TerminalAttributes& terminalAttributes, @@ -92,21 +92,21 @@ class UnigramDictionary { const uint8_t *bigramFilter, Correction *correction, int *newCount, int *newChildPosition, int *nextSiblingPosition, WordsPriorityQueuePool *queuePool, const int currentWordIndex) const; - int getMostFrequentWordLike(const int startInputIndex, const int inputLength, + int getMostFrequentWordLike(const int startInputIndex, const int inputSize, Correction *correction, unsigned short *word) const; int getMostFrequentWordLikeInner(const uint16_t *const inWord, const int length, short unsigned int *outWord) const; int getSubStringSuggestion( ProximityInfo *proximityInfo, const int *xcoordinates, const int *ycoordinates, const int *codes, const bool useFullEditDistance, Correction *correction, - WordsPriorityQueuePool *queuePool, const int inputLength, + WordsPriorityQueuePool *queuePool, const int inputSize, const bool hasAutoCorrectionCandidate, const int currentWordIndex, const int inputWordStartPos, const int inputWordLength, const int outputWordStartPos, const bool isSpaceProximity, int *freqArray, int *wordLengthArray, unsigned short *outputWord, int *outputWordLength) const; void getMultiWordsSuggestionRec(ProximityInfo *proximityInfo, const int *xcoordinates, const int *ycoordinates, const int *codes, - const bool useFullEditDistance, const int inputLength, + const bool useFullEditDistance, const int inputSize, Correction *correction, WordsPriorityQueuePool *queuePool, const bool hasAutoCorrectionCandidate, const int startPos, const int startWordIndex, const int outputWordLength, int *freqArray, int *wordLengthArray, diff --git a/native/jni/src/words_priority_queue.h b/native/jni/src/words_priority_queue.h index 1e4e00a23..19efa5da3 100644 --- a/native/jni/src/words_priority_queue.h +++ b/native/jni/src/words_priority_queue.h @@ -44,17 +44,16 @@ class WordsPriorityQueue { } }; - WordsPriorityQueue(int maxWords, int maxWordLength) : - MAX_WORDS((unsigned int) maxWords), MAX_WORD_LENGTH( - (unsigned int) maxWordLength) { - mSuggestedWords = new SuggestedWord[maxWordLength]; + WordsPriorityQueue(int maxWords, int maxWordLength) + : mSuggestions(), MAX_WORDS(static_cast<unsigned int>(maxWords)), + MAX_WORD_LENGTH(static_cast<unsigned int>(maxWordLength)), + mSuggestedWords(new SuggestedWord[maxWordLength]), mHighestSuggestedWord(0) { for (int i = 0; i < maxWordLength; ++i) { mSuggestedWords[i].mUsed = false; } - mHighestSuggestedWord = 0; } - ~WordsPriorityQueue() { + virtual ~WordsPriorityQueue() { delete[] mSuggestedWords; } diff --git a/native/jni/src/words_priority_queue_pool.h b/native/jni/src/words_priority_queue_pool.h index 38887291e..c5de9797f 100644 --- a/native/jni/src/words_priority_queue_pool.h +++ b/native/jni/src/words_priority_queue_pool.h @@ -24,9 +24,10 @@ namespace latinime { class WordsPriorityQueuePool { public: - WordsPriorityQueuePool(int mainQueueMaxWords, int subQueueMaxWords, int maxWordLength) { - // Note: using placement new() requires the caller to call the destructor explicitly. - mMasterQueue = new(mMasterQueueBuf) WordsPriorityQueue(mainQueueMaxWords, maxWordLength); + WordsPriorityQueuePool(int mainQueueMaxWords, int subQueueMaxWords, int maxWordLength) + // Note: using placement new() requires the caller to call the destructor explicitly. + : mMasterQueue(new(mMasterQueueBuf) WordsPriorityQueue( + mainQueueMaxWords, maxWordLength)) { for (int i = 0, subQueueBufOffset = 0; i < MULTIPLE_WORDS_SUGGESTION_MAX_WORDS * SUB_QUEUE_MAX_COUNT; ++i, subQueueBufOffset += sizeof(WordsPriorityQueue)) { @@ -85,11 +86,11 @@ class WordsPriorityQueuePool { private: DISALLOW_IMPLICIT_CONSTRUCTORS(WordsPriorityQueuePool); + char mMasterQueueBuf[sizeof(WordsPriorityQueue)]; + char mSubQueueBuf[SUB_QUEUE_MAX_COUNT * MULTIPLE_WORDS_SUGGESTION_MAX_WORDS + * sizeof(WordsPriorityQueue)]; WordsPriorityQueue *mMasterQueue; WordsPriorityQueue *mSubQueues[SUB_QUEUE_MAX_COUNT * MULTIPLE_WORDS_SUGGESTION_MAX_WORDS]; - char mMasterQueueBuf[sizeof(WordsPriorityQueue)]; - char mSubQueueBuf[MULTIPLE_WORDS_SUGGESTION_MAX_WORDS - * SUB_QUEUE_MAX_COUNT * sizeof(WordsPriorityQueue)]; }; } // namespace latinime #endif // LATINIME_WORDS_PRIORITY_QUEUE_POOL_H diff --git a/tests/src/com/android/inputmethod/latin/BinaryDictIOTests.java b/tests/src/com/android/inputmethod/latin/BinaryDictIOTests.java new file mode 100644 index 000000000..0094db8a7 --- /dev/null +++ b/tests/src/com/android/inputmethod/latin/BinaryDictIOTests.java @@ -0,0 +1,224 @@ +/* + * 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.latin; + +import com.android.inputmethod.latin.makedict.BinaryDictInputOutput; +import com.android.inputmethod.latin.makedict.FusionDictionary; +import com.android.inputmethod.latin.makedict.FusionDictionary.CharGroup; +import com.android.inputmethod.latin.makedict.FusionDictionary.Node; +import com.android.inputmethod.latin.makedict.UnsupportedFormatException; + +import android.test.AndroidTestCase; +import android.util.Log; +import android.util.SparseArray; + +import java.nio.ByteBuffer; +import java.nio.channels.FileChannel; +import java.io.File; +import java.io.FileOutputStream; +import java.io.FileInputStream; +import java.io.IOException; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Random; +import java.util.Set; + +/** + * Unit tests for BinaryDictInputOutput + */ +public class BinaryDictIOTests extends AndroidTestCase { + private static final String TAG = BinaryDictIOTests.class.getSimpleName(); + private static final int MAX_UNIGRAMS = 1000; + private static final int UNIGRAM_FREQ = 10; + private static final int BIGRAM_FREQ = 50; + + private static final String[] CHARACTERS = + { + "a", "b", "c", "d", "e", "f", "g", "h", "i", "j", "k", "l", "m", + "n", "o", "p", "q", "r", "s", "t", "u", "v", "w", "x", "y", "z" + }; + + /** + * Generates a random word. + */ + private String generateWord(final int value) { + final int lengthOfChars = CHARACTERS.length; + StringBuilder builder = new StringBuilder("a"); + long lvalue = Math.abs((long)value); + while (lvalue > 0) { + builder.append(CHARACTERS[(int)(lvalue % lengthOfChars)]); + lvalue /= lengthOfChars; + } + return builder.toString(); + } + + private List<String> generateWords(final int number, final Random random) { + final Set<String> wordSet = CollectionUtils.newHashSet(); + while (wordSet.size() < number) { + wordSet.add(generateWord(random.nextInt())); + } + return new ArrayList<String>(wordSet); + } + + private void addUnigrams(final int number, + final FusionDictionary dict, + final List<String> words) { + for (int i = 0; i < number; ++i) { + final String word = words.get(i); + dict.add(word, UNIGRAM_FREQ, null); + } + } + + private void addBigrams(final FusionDictionary dict, + final List<String> words, + final SparseArray<List<Integer>> sparseArray) { + for (int i = 0; i < sparseArray.size(); ++i) { + final int w1 = sparseArray.keyAt(i); + for (int w2 : sparseArray.valueAt(i)) { + dict.setBigram(words.get(w1), words.get(w2), BIGRAM_FREQ); + } + } + } + + private long timeWritingDictToFile(final String fileName, + final FusionDictionary dict) { + + final File file = new File(getContext().getFilesDir(), fileName); + long now = -1, diff = -1; + + try { + final FileOutputStream out = new FileOutputStream(file); + + now = System.currentTimeMillis(); + BinaryDictInputOutput.writeDictionaryBinary(out, dict, 2); + diff = System.currentTimeMillis() - now; + + out.flush(); + out.close(); + } catch (IOException e) { + Log.e(TAG, "IO exception while writing file: " + e); + } catch (UnsupportedFormatException e) { + Log.e(TAG, "UnsupportedFormatException: " + e); + } + + return diff; + } + + private void checkDictionary(final FusionDictionary dict, + final List<String> words, + final SparseArray<List<Integer>> bigrams) { + assertNotNull(dict); + + // check unigram + for (final String word : words) { + final CharGroup cg = FusionDictionary.findWordInTree(dict.mRoot, word); + assertNotNull(cg); + } + + // check bigram + for (int i = 0; i < bigrams.size(); ++i) { + final int w1 = bigrams.keyAt(i); + for (final int w2 : bigrams.valueAt(i)) { + final CharGroup cg = FusionDictionary.findWordInTree(dict.mRoot, words.get(w1)); + assertNotNull(words.get(w1) + "," + words.get(w2), cg.getBigram(words.get(w2))); + } + } + } + + private long timeReadingAndCheckDict(final String fileName, + final List<String> words, + final SparseArray<List<Integer>> bigrams) { + + long now, diff = -1; + + try { + final File file = new File(getContext().getFilesDir(), fileName); + final FileInputStream inStream = new FileInputStream(file); + final ByteBuffer buffer = inStream.getChannel().map( + FileChannel.MapMode.READ_ONLY, 0, file.length()); + + now = System.currentTimeMillis(); + + final FusionDictionary dict = + BinaryDictInputOutput.readDictionaryBinary(buffer, null); + + diff = System.currentTimeMillis() - now; + + checkDictionary(dict, words, bigrams); + return diff; + + } catch (IOException e) { + Log.e(TAG, "raise IOException while reading file " + e); + } catch (UnsupportedFormatException e) { + Log.e(TAG, "Unsupported format: " + e); + } + + return diff; + } + + private String runReadAndWrite(final List<String> words, + final SparseArray<List<Integer>> bigrams, + final String message) { + final FusionDictionary dict = new FusionDictionary(new Node(), + new FusionDictionary.DictionaryOptions( + new HashMap<String,String>(), false, false)); + + final String fileName = generateWord((int)System.currentTimeMillis()) + ".dict"; + + addUnigrams(words.size(), dict, words); + addBigrams(dict, words, bigrams); + // check original dictionary + checkDictionary(dict, words, bigrams); + + final long write = timeWritingDictToFile(fileName, dict); + final long read = timeReadingAndCheckDict(fileName, words, bigrams); + deleteFile(fileName); + + return "PROF: read=" + read + "ms, write=" + write + "ms :" + message; + } + + private void deleteFile(final String fileName) { + final File file = new File(getContext().getFilesDir(), fileName); + file.delete(); + } + + public void testReadAndWrite() { + final List<String> results = new ArrayList<String>(); + + final Random random = new Random(123456); + final List<String> words = generateWords(MAX_UNIGRAMS, random); + final SparseArray<List<Integer>> emptyArray = CollectionUtils.newSparseArray(); + + final SparseArray<List<Integer>> chain = CollectionUtils.newSparseArray(); + for (int i = 0; i < words.size(); ++i) chain.put(i, new ArrayList<Integer>()); + for (int i = 1; i < words.size(); ++i) chain.get(i-1).add(i); + + final SparseArray<List<Integer>> star = CollectionUtils.newSparseArray(); + final List<Integer> list0 = CollectionUtils.newArrayList(); + star.put(0, list0); + for (int i = 1; i < words.size(); ++i) star.get(0).add(i); + + results.add(runReadAndWrite(words, emptyArray, "only unigram")); + results.add(runReadAndWrite(words, chain, "chain")); + results.add(runReadAndWrite(words, star, "star")); + + for (final String result : results) { + Log.d(TAG, result); + } + } +} diff --git a/tools/dicttool/src/android/inputmethod/latin/dicttool/DictionaryMaker.java b/tools/dicttool/src/android/inputmethod/latin/dicttool/DictionaryMaker.java index 25e1740cb..fbfc1dabb 100644 --- a/tools/dicttool/src/android/inputmethod/latin/dicttool/DictionaryMaker.java +++ b/tools/dicttool/src/android/inputmethod/latin/dicttool/DictionaryMaker.java @@ -27,7 +27,8 @@ import java.io.FileNotFoundException; import java.io.FileOutputStream; import java.io.FileWriter; import java.io.IOException; -import java.io.RandomAccessFile; +import java.nio.ByteBuffer; +import java.nio.channels.FileChannel; import java.util.Arrays; import java.util.LinkedList; @@ -238,8 +239,23 @@ public class DictionaryMaker { */ private static FusionDictionary readBinaryFile(final String binaryFilename) throws FileNotFoundException, IOException, UnsupportedFormatException { - final RandomAccessFile inputFile = new RandomAccessFile(binaryFilename, "r"); - return BinaryDictInputOutput.readDictionaryBinary(inputFile, null); + FileInputStream inStream = null; + + try { + final File file = new File(binaryFilename); + inStream = new FileInputStream(file); + final ByteBuffer buffer = inStream.getChannel().map( + FileChannel.MapMode.READ_ONLY, 0, file.length()); + return BinaryDictInputOutput.readDictionaryBinary(buffer, null); + } finally { + if (inStream != null) { + try { + inStream.close(); + } catch (IOException e) { + // do nothing + } + } + } } /** |