diff options
Diffstat (limited to 'java/src')
11 files changed, 323 insertions, 138 deletions
diff --git a/java/src/com/android/inputmethod/keyboard/KeyboardLayoutSet.java b/java/src/com/android/inputmethod/keyboard/KeyboardLayoutSet.java index aaccf63ba..d97df7491 100644 --- a/java/src/com/android/inputmethod/keyboard/KeyboardLayoutSet.java +++ b/java/src/com/android/inputmethod/keyboard/KeyboardLayoutSet.java @@ -96,7 +96,7 @@ public class KeyboardLayoutSet { String mKeyboardLayoutSetName; int mMode; EditorInfo mEditorInfo; - boolean mTouchPositionCorrectionEnabled; + boolean mDisableTouchPositionCorrectionDataForTest; boolean mVoiceKeyEnabled; boolean mVoiceKeyOnMain; boolean mNoSettingsKey; @@ -167,7 +167,9 @@ public class KeyboardLayoutSet { } final int keyboardXmlId = elementParams.mKeyboardXmlId; builder.load(keyboardXmlId, id); - builder.setTouchPositionCorrectionEnabled(mParams.mTouchPositionCorrectionEnabled); + if (mParams.mDisableTouchPositionCorrectionDataForTest) { + builder.disableTouchPositionCorrectionDataForTest(); + } builder.setProximityCharsCorrectionEnabled( elementParams.mProximityCharsCorrectionEnabled); keyboard = builder.build(); @@ -264,8 +266,9 @@ public class KeyboardLayoutSet { return this; } - public void setTouchPositionCorrectionEnabled(final boolean enabled) { - mParams.mTouchPositionCorrectionEnabled = enabled; + // For test only + public void disableTouchPositionCorrectionDataForTest() { + mParams.mDisableTouchPositionCorrectionDataForTest = true; } public KeyboardLayoutSet build() { diff --git a/java/src/com/android/inputmethod/keyboard/MainKeyboardView.java b/java/src/com/android/inputmethod/keyboard/MainKeyboardView.java index f5c1b7a0c..f1fcfe785 100644 --- a/java/src/com/android/inputmethod/keyboard/MainKeyboardView.java +++ b/java/src/com/android/inputmethod/keyboard/MainKeyboardView.java @@ -318,12 +318,13 @@ public class MainKeyboardView extends KeyboardView implements PointerTracker.Key return hasMessages(MSG_TYPING_STATE_EXPIRED); } + // TODO: Remove "gesture off while fast typing" related dead code. @Override public void startGestureOffWhileFastTypingTimer() { - removeMessages(MSG_DISABLE_GESTURE_EXPIRED); - PointerTracker.setGestureOffWhileFastTyping(); - sendMessageDelayed(obtainMessage(MSG_DISABLE_GESTURE_EXPIRED), - mDisableGestureWhileFastTypingTimeout); +// removeMessages(MSG_DISABLE_GESTURE_EXPIRED); +// PointerTracker.setGestureOffWhileFastTyping(); +// sendMessageDelayed(obtainMessage(MSG_DISABLE_GESTURE_EXPIRED), +// mDisableGestureWhileFastTypingTimeout); } @Override diff --git a/java/src/com/android/inputmethod/keyboard/PointerTracker.java b/java/src/com/android/inputmethod/keyboard/PointerTracker.java index 0778ad902..d6c567ef7 100644 --- a/java/src/com/android/inputmethod/keyboard/PointerTracker.java +++ b/java/src/com/android/inputmethod/keyboard/PointerTracker.java @@ -169,6 +169,7 @@ public class PointerTracker implements PointerTrackerQueue.Element { private boolean mIsDetectingGesture = false; // per PointerTracker. private static boolean sInGesture = false; private static long sGestureFirstDownTime; + private static long sLastLetterTypingUpTime; private static final InputPointers sAggregratedPointers = new InputPointers( GestureStroke.DEFAULT_CAPACITY); private static int sLastRecognitionPointSize = 0; @@ -698,6 +699,7 @@ public class PointerTracker implements PointerTrackerQueue.Element { private void onGestureDownEvent(final int x, final int y, final long eventTime) { mIsDetectingGesture = true; + mGestureStrokeWithPreviewPoints.setLastLetterTypingTime(eventTime, sLastLetterTypingUpTime); final int elapsedTimeFromFirstDown = (int)(eventTime - sGestureFirstDownTime); mGestureStrokeWithPreviewPoints.addPoint(x, y, elapsedTimeFromFirstDown, true /* isMajorEvent */); @@ -842,7 +844,7 @@ public class PointerTracker implements PointerTrackerQueue.Element { if (ProductionFlag.IS_EXPERIMENTAL) { ResearchLogger.pointerTracker_onMoveEvent(x, y, lastX, lastY); } - onUpEventInternal(); + onUpEventInternal(eventTime); onDownEventInternal(x, y, eventTime); } else { // HACK: If there are currently multiple touches, register the key even if @@ -852,7 +854,7 @@ public class PointerTracker implements PointerTrackerQueue.Element { // this hack. if (getActivePointerTrackerCount() > 1 && sPointerTrackerQueue != null && !sPointerTrackerQueue.hasModifierKeyOlderThan(this)) { - onUpEventInternal(); + onUpEventInternal(eventTime); } if (!mIsDetectingGesture) { mKeyAlreadyProcessed = true; @@ -897,7 +899,7 @@ public class PointerTracker implements PointerTrackerQueue.Element { } } } - onUpEventInternal(); + onUpEventInternal(eventTime); if (queue != null) { queue.remove(this); } @@ -911,11 +913,11 @@ public class PointerTracker implements PointerTrackerQueue.Element { if (DEBUG_EVENT) { printTouchEvent("onPhntEvent:", getLastX(), getLastY(), eventTime); } - onUpEventInternal(); + onUpEventInternal(eventTime); mKeyAlreadyProcessed = true; } - private void onUpEventInternal() { + private void onUpEventInternal(final long eventTime) { mTimerProxy.cancelKeyTimers(); mIsInSlidingKeyInput = false; mIsDetectingGesture = false; @@ -943,6 +945,10 @@ public class PointerTracker implements PointerTrackerQueue.Element { } if (currentKey != null && !currentKey.isRepeatable()) { detectAndSendKey(currentKey, mKeyX, mKeyY); + final int code = currentKey.mCode; + if (Keyboard.isLetterCode(code) && code != Keyboard.CODE_SPACE) { + sLastLetterTypingUpTime = eventTime; + } } } diff --git a/java/src/com/android/inputmethod/keyboard/ProximityInfo.java b/java/src/com/android/inputmethod/keyboard/ProximityInfo.java index e1b082c16..6e138946f 100644 --- a/java/src/com/android/inputmethod/keyboard/ProximityInfo.java +++ b/java/src/com/android/inputmethod/keyboard/ProximityInfo.java @@ -44,7 +44,6 @@ public class ProximityInfo { private final int mKeyboardHeight; private final int mMostCommonKeyWidth; private final Key[] mKeys; - private final TouchPositionCorrection mTouchPositionCorrection; private final Key[][] mGridNeighbors; private final String mLocaleStr; @@ -67,14 +66,13 @@ public class ProximityInfo { mKeyHeight = mostCommonKeyHeight; mMostCommonKeyWidth = mostCommonKeyWidth; mKeys = keys; - mTouchPositionCorrection = touchPositionCorrection; mGridNeighbors = new Key[mGridSize][]; if (minWidth == 0 || height == 0) { // No proximity required. Keyboard might be more keys keyboard. return; } computeNearestNeighbors(); - mNativeProximityInfo = createNativeProximityInfo(); + mNativeProximityInfo = createNativeProximityInfo(touchPositionCorrection); } public static ProximityInfo createDummyProximityInfo() { @@ -106,12 +104,12 @@ public class ProximityInfo { private native void releaseProximityInfoNative(long nativeProximityInfo); - private final long createNativeProximityInfo() { + private final long createNativeProximityInfo( + final TouchPositionCorrection touchPositionCorrection) { final Key[][] gridNeighborKeys = mGridNeighbors; final int keyboardWidth = mKeyboardMinWidth; final int keyboardHeight = mKeyboardHeight; final Key[] keys = mKeys; - final TouchPositionCorrection touchPositionCorrection = mTouchPositionCorrection; final int[] proximityCharsArray = new int[mGridSize * MAX_PROXIMITY_CHARS_SIZE]; Arrays.fill(proximityCharsArray, Constants.NOT_A_CODE); for (int i = 0; i < mGridSize; ++i) { @@ -148,12 +146,12 @@ public class ProximityInfo { final Key key = keys[i]; final Rect hitBox = key.mHitBox; final int row = hitBox.top / mKeyHeight; - if (row < touchPositionCorrection.mRadii.length) { + if (row < touchPositionCorrection.getRows()) { final int hitBoxWidth = hitBox.width(); final int hitBoxHeight = hitBox.height(); - final float x = touchPositionCorrection.mXs[row]; - final float y = touchPositionCorrection.mYs[row]; - final float radius = touchPositionCorrection.mRadii[row]; + final float x = touchPositionCorrection.getX(row); + final float y = touchPositionCorrection.getY(row); + final float radius = touchPositionCorrection.getRadius(row); sweetSpotCenterXs[i] = hitBox.exactCenterX() + x * hitBoxWidth; sweetSpotCenterYs[i] = hitBox.exactCenterY() + y * hitBoxHeight; // Note that, in recent versions of Android, FloatMath is actually slower than diff --git a/java/src/com/android/inputmethod/keyboard/internal/GestureStroke.java b/java/src/com/android/inputmethod/keyboard/internal/GestureStroke.java index 193c3a4ba..9fe6fa3f8 100644 --- a/java/src/com/android/inputmethod/keyboard/internal/GestureStroke.java +++ b/java/src/com/android/inputmethod/keyboard/internal/GestureStroke.java @@ -34,8 +34,10 @@ public class GestureStroke { private long mLastMajorEventTime; private int mLastMajorEventX; private int mLastMajorEventY; + private boolean mAfterFastTyping; private int mKeyWidth; + private int mStartGestureLengthThresholdAfterFastTyping; // pixel private int mStartGestureLengthThreshold; // pixel private int mMinGestureSamplingLength; // pixel private int mGestureRecognitionSpeedThreshold; // pixel / sec @@ -45,7 +47,11 @@ public class GestureStroke { private int mDetectFastMoveY; // TODO: Move some of these to resource. - private static final float START_GESTURE_LENGTH_THRESHOLD_RATIO_TO_KEY_WIDTH = 0.60f; + private static final int GESTURE_AFTER_FAST_TYPING_DURATION_THRESHOLD = 350; // msec + private static final float START_GESTURE_LENGTH_THRESHOLD_AFTER_FAST_TYPING_RATIO_TO_KEY_WIDTH = + 8.0f; + private static final int START_GESTURE_LENGTH_THRESHOLD_DECAY_DURATION = 400; // msec + private static final float START_GESTURE_LENGTH_THRESHOLD_RATIO_TO_KEY_WIDTH = 0.6f; private static final int START_GESTURE_DURATION_THRESHOLD = 70; // msec private static final int MIN_GESTURE_RECOGNITION_TIME = 100; // msec private static final float MIN_GESTURE_SAMPLING_RATIO_TO_KEY_WIDTH = 1.0f / 6.0f; @@ -67,6 +73,8 @@ public class GestureStroke { public void setKeyboardGeometry(final int keyWidth) { mKeyWidth = keyWidth; // TODO: Find an appropriate base metric for these length. Maybe diagonal length of the key? + mStartGestureLengthThresholdAfterFastTyping = (int)(keyWidth + * START_GESTURE_LENGTH_THRESHOLD_AFTER_FAST_TYPING_RATIO_TO_KEY_WIDTH); mStartGestureLengthThreshold = (int)(keyWidth * START_GESTURE_LENGTH_THRESHOLD_RATIO_TO_KEY_WIDTH); mMinGestureSamplingLength = (int)(keyWidth * MIN_GESTURE_SAMPLING_RATIO_TO_KEY_WIDTH); @@ -75,10 +83,33 @@ public class GestureStroke { mDetectFastMoveSpeedThreshold = (int)(keyWidth * DETECT_FAST_MOVE_SPEED_THRESHOLD_RATIO_TO_KEY_WIDTH); if (DEBUG) { - Log.d(TAG, "setKeyboardGeometry: keyWidth=" + keyWidth); + Log.d(TAG, "[" + mPointerId + "] setKeyboardGeometry: keyWidth=" + keyWidth + + " tL0=" + mStartGestureLengthThresholdAfterFastTyping + + " tL=" + mStartGestureLengthThreshold); } } + public void setLastLetterTypingTime(final long downTime, final long lastTypingTime) { + final long elpasedTimeAfterTyping = downTime - lastTypingTime; + if (elpasedTimeAfterTyping < GESTURE_AFTER_FAST_TYPING_DURATION_THRESHOLD) { + mAfterFastTyping = true; + } + if (DEBUG) { + Log.d(TAG, "[" + mPointerId + "] setLastTypingTime: dT=" + elpasedTimeAfterTyping + + " afterFastTyping=" + mAfterFastTyping); + } + } + + private int getStartGestureLengthThreshold(final int deltaTime) { + if (!mAfterFastTyping || deltaTime >= START_GESTURE_LENGTH_THRESHOLD_DECAY_DURATION) { + return mStartGestureLengthThreshold; + } + final int decayedThreshold = + (mStartGestureLengthThresholdAfterFastTyping - mStartGestureLengthThreshold) + * deltaTime / START_GESTURE_LENGTH_THRESHOLD_DECAY_DURATION; + return mStartGestureLengthThresholdAfterFastTyping - decayedThreshold; + } + public boolean isStartOfAGesture() { if (mDetectFastMoveTime == 0) { return false; @@ -92,10 +123,12 @@ public class GestureStroke { final int deltaLength = getDistance( mXCoordinates.get(lastIndex), mYCoordinates.get(lastIndex), mDetectFastMoveX, mDetectFastMoveY); + final int startGestureLengthThreshold = getStartGestureLengthThreshold(deltaTime); final boolean isStartOfAGesture = deltaTime > START_GESTURE_DURATION_THRESHOLD - && deltaLength > mStartGestureLengthThreshold; + && deltaLength > startGestureLengthThreshold; if (DEBUG) { - Log.d(TAG, "isStartOfAGesture: dT=" + deltaTime + " dL=" + deltaLength + Log.d(TAG, "[" + mPointerId + "] isStartOfAGesture: dT=" + deltaTime + + " dL=" + deltaLength + " tL=" + startGestureLengthThreshold + " points=" + size + (isStartOfAGesture ? " Detect start of a gesture" : "")); } return isStartOfAGesture; @@ -109,6 +142,7 @@ public class GestureStroke { mYCoordinates.setLength(0); mLastMajorEventTime = 0; mDetectFastMoveTime = 0; + mAfterFastTyping = false; } private void appendPoint(final int x, final int y, final int time) { @@ -135,12 +169,13 @@ public class GestureStroke { final int pixelsPerSec = pixels * MSEC_PER_SEC; if (DEBUG) { final float speed = (float)pixelsPerSec / msecs / mKeyWidth; - Log.d(TAG, String.format("Speed=%.3f keyWidth/sec", speed)); + Log.d(TAG, String.format("[" + mPointerId + "] speed=%.3f", speed)); } // Equivalent to (pixels / msecs < mStartSpeedThreshold / MSEC_PER_SEC) if (mDetectFastMoveTime == 0 && pixelsPerSec > mDetectFastMoveSpeedThreshold * msecs) { if (DEBUG) { - Log.d(TAG, "Detect fast move: T=" + time + " points = " + size); + Log.d(TAG, "[" + mPointerId + "] detect fast move: T=" + + time + " points = " + size); } mDetectFastMoveTime = time; mDetectFastMoveX = x; diff --git a/java/src/com/android/inputmethod/keyboard/internal/KeyboardBuilder.java b/java/src/com/android/inputmethod/keyboard/internal/KeyboardBuilder.java index 31c7cb565..b314a3795 100644 --- a/java/src/com/android/inputmethod/keyboard/internal/KeyboardBuilder.java +++ b/java/src/com/android/inputmethod/keyboard/internal/KeyboardBuilder.java @@ -177,9 +177,9 @@ public class KeyboardBuilder<KP extends KeyboardParams> { return this; } - // TODO: Remove this method. - public void setTouchPositionCorrectionEnabled(final boolean enabled) { - mParams.mTouchPositionCorrection.setEnabled(enabled); + // For test only + public void disableTouchPositionCorrectionDataForTest() { + mParams.mTouchPositionCorrection.setEnabled(false); } public void setProximityCharsCorrectionEnabled(final boolean enabled) { @@ -314,7 +314,6 @@ public class KeyboardBuilder<KP extends KeyboardParams> { final int resourceId = keyboardAttr.getResourceId( R.styleable.Keyboard_touchPositionCorrectionData, 0); - params.mTouchPositionCorrection.setEnabled(resourceId != 0); if (resourceId != 0) { final String[] data = mResources.getStringArray(resourceId); params.mTouchPositionCorrection.load(data); diff --git a/java/src/com/android/inputmethod/keyboard/internal/TouchPositionCorrection.java b/java/src/com/android/inputmethod/keyboard/internal/TouchPositionCorrection.java index 69dc01cd6..811a620b3 100644 --- a/java/src/com/android/inputmethod/keyboard/internal/TouchPositionCorrection.java +++ b/java/src/com/android/inputmethod/keyboard/internal/TouchPositionCorrection.java @@ -21,10 +21,10 @@ import com.android.inputmethod.latin.LatinImeLogger; public class TouchPositionCorrection { private static final int TOUCH_POSITION_CORRECTION_RECORD_SIZE = 3; - public boolean mEnabled; - public float[] mXs; - public float[] mYs; - public float[] mRadii; + private boolean mEnabled; + private float[] mXs; + private float[] mYs; + private float[] mRadii; public void load(final String[] data) { final int dataLength = data.length; @@ -53,24 +53,41 @@ public class TouchPositionCorrection { mRadii[index] = value; } } + mEnabled = dataLength > 0; } catch (NumberFormatException e) { if (LatinImeLogger.sDBG) { throw new RuntimeException( "the number format for touch position correction data is invalid"); } + mEnabled = false; mXs = null; mYs = null; mRadii = null; } } - // TODO: Remove this method. + // For test only public void setEnabled(final boolean enabled) { mEnabled = enabled; } public boolean isValid() { - return mEnabled && mXs != null && mYs != null && mRadii != null - && mXs.length > 0 && mYs.length > 0 && mRadii.length > 0; + return mEnabled; + } + + public int getRows() { + return mRadii.length; + } + + public float getX(final int row) { + return mXs[row]; + } + + public float getY(final int row) { + return mYs[row]; + } + + public float getRadius(final int row) { + return mRadii[row]; } } diff --git a/java/src/com/android/inputmethod/latin/UserHistoryDictIOUtils.java b/java/src/com/android/inputmethod/latin/UserHistoryDictIOUtils.java index 4a3d11aa1..05255a6b3 100644 --- a/java/src/com/android/inputmethod/latin/UserHistoryDictIOUtils.java +++ b/java/src/com/android/inputmethod/latin/UserHistoryDictIOUtils.java @@ -100,6 +100,11 @@ public class UserHistoryDictIOUtils { @Override public int limit() { + return mBuffer.length - 1; + } + + @Override + public int capacity() { return mBuffer.length; } } diff --git a/java/src/com/android/inputmethod/latin/makedict/BinaryDictIOUtils.java b/java/src/com/android/inputmethod/latin/makedict/BinaryDictIOUtils.java index ac0fb0ece..19da5124a 100644 --- a/java/src/com/android/inputmethod/latin/makedict/BinaryDictIOUtils.java +++ b/java/src/com/android/inputmethod/latin/makedict/BinaryDictIOUtils.java @@ -157,47 +157,63 @@ public class BinaryDictIOUtils { final int wordLen = word.codePointCount(0, word.length()); for (int depth = 0; depth < Constants.Dictionary.MAX_WORD_LENGTH; ++depth) { if (wordPos >= wordLen) return FormatSpec.NOT_VALID_WORD; - int groupOffset = buffer.position() - header.mHeaderSize; - final int charGroupCount = BinaryDictInputOutput.readCharGroupCount(buffer); - groupOffset += BinaryDictInputOutput.getGroupCountSize(charGroupCount); - - for (int i = 0; i < charGroupCount; ++i) { - final int charGroupPos = buffer.position(); - final CharGroupInfo currentInfo = BinaryDictInputOutput.readCharGroup(buffer, - buffer.position(), header.mFormatOptions); - boolean same = true; - for (int p = 0, j = word.offsetByCodePoints(0, wordPos); - p < currentInfo.mCharacters.length; - ++p, j = word.offsetByCodePoints(j, 1)) { - if (wordPos + p >= wordLen - || word.codePointAt(j) != currentInfo.mCharacters[p]) { - same = false; - break; + + do { + int groupOffset = buffer.position() - header.mHeaderSize; + final int charGroupCount = BinaryDictInputOutput.readCharGroupCount(buffer); + groupOffset += BinaryDictInputOutput.getGroupCountSize(charGroupCount); + + boolean foundNextCharGroup = false; + for (int i = 0; i < charGroupCount; ++i) { + final int charGroupPos = buffer.position(); + final CharGroupInfo currentInfo = BinaryDictInputOutput.readCharGroup(buffer, + buffer.position(), header.mFormatOptions); + boolean same = true; + for (int p = 0, j = word.offsetByCodePoints(0, wordPos); + p < currentInfo.mCharacters.length; + ++p, j = word.offsetByCodePoints(j, 1)) { + if (wordPos + p >= wordLen + || word.codePointAt(j) != currentInfo.mCharacters[p]) { + same = false; + break; + } } - } - if (same) { - if (wordPos + currentInfo.mCharacters.length == wordLen) { - if (currentInfo.mFrequency == CharGroup.NOT_A_TERMINAL) { + if (same) { + // found the group matches the word. + if (wordPos + currentInfo.mCharacters.length == wordLen) { + if (currentInfo.mFrequency == CharGroup.NOT_A_TERMINAL) { + return FormatSpec.NOT_VALID_WORD; + } else { + return charGroupPos; + } + } + wordPos += currentInfo.mCharacters.length; + if (currentInfo.mChildrenAddress == FormatSpec.NO_CHILDREN_ADDRESS) { return FormatSpec.NOT_VALID_WORD; - } else { - return charGroupPos; } + foundNextCharGroup = true; + buffer.position(currentInfo.mChildrenAddress); + break; } - wordPos += currentInfo.mCharacters.length; - if (currentInfo.mChildrenAddress == FormatSpec.NO_CHILDREN_ADDRESS) { - return FormatSpec.NOT_VALID_WORD; - } - buffer.position(currentInfo.mChildrenAddress); - break; + groupOffset = currentInfo.mEndAddress; } - groupOffset = currentInfo.mEndAddress; - // not found - if (i >= charGroupCount - 1) { + // If we found the next char group, it is under the file pointer. + // But if not, we are at the end of this node so we expect to have + // a forward link address that we need to consult and possibly resume + // search on the next node in the linked list. + if (foundNextCharGroup) break; + if (!header.mFormatOptions.mSupportsDynamicUpdate) { return FormatSpec.NOT_VALID_WORD; } - } + + final int forwardLinkAddress = buffer.readUnsignedInt24(); + if (forwardLinkAddress == FormatSpec.NO_FORWARD_LINK_ADDRESS) { + return FormatSpec.NOT_VALID_WORD; + } + buffer.position(forwardLinkAddress); + } while(true); } return FormatSpec.NOT_VALID_WORD; } diff --git a/java/src/com/android/inputmethod/latin/makedict/BinaryDictInputOutput.java b/java/src/com/android/inputmethod/latin/makedict/BinaryDictInputOutput.java index 4806bf9dc..f9339de08 100644 --- a/java/src/com/android/inputmethod/latin/makedict/BinaryDictInputOutput.java +++ b/java/src/com/android/inputmethod/latin/makedict/BinaryDictInputOutput.java @@ -36,7 +36,6 @@ import java.util.Arrays; import java.util.HashMap; import java.util.Iterator; import java.util.Map; -import java.util.Stack; import java.util.TreeMap; /** @@ -64,6 +63,7 @@ public class BinaryDictInputOutput { public void position(int newPosition); public void put(final byte b); public int limit(); + public int capacity(); } public static final class ByteBufferWrapper implements FusionDictionaryBufferInterface { @@ -113,6 +113,11 @@ public class BinaryDictInputOutput { public int limit() { return mBuffer.limit(); } + + @Override + public int capacity() { + return mBuffer.capacity(); + } } /** @@ -412,6 +417,10 @@ public class BinaryDictInputOutput { } } + private static final int UINT8_MAX = 0xFF; + private static final int UINT16_MAX = 0xFFFF; + private static final int UINT24_MAX = 0xFFFFFF; + /** * Compute the size, in bytes, that an address will occupy. * @@ -423,17 +432,25 @@ public class BinaryDictInputOutput { * @return the byte size. */ private static int getByteSize(final int address) { - assert(address < 0x1000000); + assert(address <= UINT24_MAX); if (!hasChildrenAddress(address)) { return 0; - } else if (Math.abs(address) < 0x100) { + } else if (Math.abs(address) <= UINT8_MAX) { return 1; - } else if (Math.abs(address) < 0x10000) { + } else if (Math.abs(address) <= UINT16_MAX) { return 2; } else { return 3; } } + + private static final int SINT8_MAX = 0x7F; + private static final int SINT16_MAX = 0x7FFF; + private static final int SINT24_MAX = 0x7FFFFF; + private static final int MSB8 = 0x80; + private static final int MSB16 = 0x8000; + private static final int MSB24 = 0x800000; + // End utility methods. // This method is responsible for finding a nice ordering of the nodes that favors run-time @@ -509,13 +526,19 @@ public class BinaryDictInputOutput { } int groupSize = getGroupHeaderSize(group, formatOptions); if (group.isTerminal()) groupSize += FormatSpec.GROUP_FREQUENCY_SIZE; - if (null != group.mChildren) { + if (null == group.mChildren && formatOptions.mSupportsDynamicUpdate) { + groupSize += FormatSpec.SIGNED_CHILDREN_ADDRESS_SIZE; + } else if (null != group.mChildren) { final int offsetBasePoint = groupSize + node.mCachedAddress + size; final int offset = group.mChildren.mCachedAddress - offsetBasePoint; // assign my address to children's parent address group.mChildren.mCachedParentAddress = group.mCachedAddress - group.mChildren.mCachedAddress; - groupSize += getByteSize(offset); + if (formatOptions.mSupportsDynamicUpdate) { + groupSize += FormatSpec.SIGNED_CHILDREN_ADDRESS_SIZE; + } else { + groupSize += getByteSize(offset); + } } groupSize += getShortcutListSize(group.mShortcutTargets); if (null != group.mBigrams) { @@ -669,27 +692,52 @@ public class BinaryDictInputOutput { } } + /** + * Helper method to write a variable-size signed address to a file. + * + * @param buffer the buffer to write to. + * @param index the index in the buffer to write the address to. + * @param address the address to write. + * @return the size in bytes the address actually took. + */ + private static int writeVariableSignedAddress(final byte[] buffer, int index, + final int address) { + if (!hasChildrenAddress(address)) { + buffer[index] = buffer[index + 1] = buffer[index + 2] = 0; + } else { + final int absAddress = Math.abs(address); + buffer[index++] = (byte)((address < 0 ? MSB8 : 0) | (0xFF & (absAddress >> 16))); + buffer[index++] = (byte)(0xFF & (absAddress >> 8)); + buffer[index++] = (byte)(0xFF & absAddress); + } + return 3; + } + private static byte makeCharGroupFlags(final CharGroup group, final int groupAddress, - final int childrenOffset) { + final int childrenOffset, final FormatOptions formatOptions) { byte flags = 0; if (group.mChars.length > 1) flags |= FormatSpec.FLAG_HAS_MULTIPLE_CHARS; if (group.mFrequency >= 0) { flags |= FormatSpec.FLAG_IS_TERMINAL; } if (null != group.mChildren) { - switch (getByteSize(childrenOffset)) { - case 1: - flags |= FormatSpec.FLAG_GROUP_ADDRESS_TYPE_ONEBYTE; - break; - case 2: - flags |= FormatSpec.FLAG_GROUP_ADDRESS_TYPE_TWOBYTES; - break; - case 3: - flags |= FormatSpec.FLAG_GROUP_ADDRESS_TYPE_THREEBYTES; - break; - default: - throw new RuntimeException("Node with a strange address"); - } + final int byteSize = formatOptions.mSupportsDynamicUpdate + ? FormatSpec.SIGNED_CHILDREN_ADDRESS_SIZE : getByteSize(childrenOffset); + switch (byteSize) { + case 1: + flags |= FormatSpec.FLAG_GROUP_ADDRESS_TYPE_ONEBYTE; + break; + case 2: + flags |= FormatSpec.FLAG_GROUP_ADDRESS_TYPE_TWOBYTES; + break; + case 3: + flags |= FormatSpec.FLAG_GROUP_ADDRESS_TYPE_THREEBYTES; + break; + default: + throw new RuntimeException("Node with a strange address"); + } + } else if (formatOptions.mSupportsDynamicUpdate) { + flags |= FormatSpec.FLAG_GROUP_ADDRESS_TYPE_THREEBYTES; } if (null != group.mShortcutTargets) { if (DBG && 0 == group.mShortcutTargets.size()) { @@ -808,6 +856,25 @@ public class BinaryDictInputOutput { + (frequency & FormatSpec.FLAG_ATTRIBUTE_FREQUENCY); } + private static final int writeParentAddress(final byte[] buffer, final int index, + final int address, final FormatOptions formatOptions) { + if (supportsDynamicUpdate(formatOptions)) { + if (address == FormatSpec.NO_PARENT_ADDRESS) { + buffer[index] = buffer[index + 1] = buffer[index + 2] = 0; + } else { + final int absAddress = Math.abs(address); + assert(absAddress <= SINT24_MAX); + buffer[index] = (byte)((address < 0 ? MSB8 : 0) + | ((absAddress >> 16) & 0xFF)); + buffer[index + 1] = (byte)((absAddress >> 8) & 0xFF); + buffer[index + 2] = (byte)(absAddress & 0xFF); + } + return index + 3; + } else { + return index; + } + } + /** * Write a node to memory. The node is expected to have its final position cached. * @@ -854,22 +921,15 @@ public class BinaryDictInputOutput { final int childrenOffset = null == group.mChildren ? FormatSpec.NO_CHILDREN_ADDRESS : group.mChildren.mCachedAddress - groupAddress; - byte flags = makeCharGroupFlags(group, groupAddress, childrenOffset); + byte flags = makeCharGroupFlags(group, groupAddress, childrenOffset, formatOptions); buffer[index++] = flags; - if (supportsDynamicUpdate(formatOptions)) { - if (parentAddress == FormatSpec.NO_PARENT_ADDRESS) { - // this node is the root node. - buffer[index] = buffer[index + 1] = buffer[index + 2] = 0; - } else { - // write parent address. (version 3) - final int actualParentAddress = Math.abs(parentAddress - + (node.mCachedAddress - group.mCachedAddress)); - buffer[index] = (byte)((actualParentAddress >> 16) & 0xFF); - buffer[index + 1] = (byte)((actualParentAddress >> 8) & 0xFF); - buffer[index + 2] = (byte)(actualParentAddress & 0xFF); - } - index += 3; + if (parentAddress == FormatSpec.NO_PARENT_ADDRESS) { + index = writeParentAddress(buffer, index, parentAddress, formatOptions); + } else { + index = writeParentAddress(buffer, index, + parentAddress + (node.mCachedAddress - group.mCachedAddress), + formatOptions); } index = CharEncoding.writeCharArray(group.mChars, buffer, index); @@ -879,7 +939,13 @@ public class BinaryDictInputOutput { if (group.mFrequency >= 0) { buffer[index++] = (byte) group.mFrequency; } - final int shift = writeVariableAddress(buffer, index, childrenOffset); + + final int shift; + if (formatOptions.mSupportsDynamicUpdate) { + shift = writeVariableSignedAddress(buffer, index, childrenOffset); + } else { + shift = writeVariableAddress(buffer, index, childrenOffset); + } index += shift; groupAddress += shift; @@ -1104,6 +1170,58 @@ public class BinaryDictInputOutput { // Input methods: Read a binary dictionary to memory. // readDictionaryBinary is the public entry point for them. + private static int getChildrenAddressSize(final int optionFlags, + final FormatOptions formatOptions) { + if (formatOptions.mSupportsDynamicUpdate) return FormatSpec.SIGNED_CHILDREN_ADDRESS_SIZE; + switch (optionFlags & FormatSpec.MASK_GROUP_ADDRESS_TYPE) { + case FormatSpec.FLAG_GROUP_ADDRESS_TYPE_ONEBYTE: + return 1; + case FormatSpec.FLAG_GROUP_ADDRESS_TYPE_TWOBYTES: + return 2; + case FormatSpec.FLAG_GROUP_ADDRESS_TYPE_THREEBYTES: + return 3; + case FormatSpec.FLAG_GROUP_ADDRESS_TYPE_NOADDRESS: + default: + return 0; + } + } + + private static int readChildrenAddress(final FusionDictionaryBufferInterface buffer, + final int optionFlags, final FormatOptions options) { + if (options.mSupportsDynamicUpdate) { + final int address = buffer.readUnsignedInt24(); + if (address == 0) return FormatSpec.NO_CHILDREN_ADDRESS; + if ((address & MSB24) != 0) { + return -(address & SINT24_MAX); + } else { + return address; + } + } + int address; + switch (optionFlags & FormatSpec.MASK_GROUP_ADDRESS_TYPE) { + case FormatSpec.FLAG_GROUP_ADDRESS_TYPE_ONEBYTE: + return buffer.readUnsignedByte(); + case FormatSpec.FLAG_GROUP_ADDRESS_TYPE_TWOBYTES: + return buffer.readUnsignedShort(); + case FormatSpec.FLAG_GROUP_ADDRESS_TYPE_THREEBYTES: + return buffer.readUnsignedInt24(); + case FormatSpec.FLAG_GROUP_ADDRESS_TYPE_NOADDRESS: + default: + return FormatSpec.NO_CHILDREN_ADDRESS; + } + } + + private static int readParentAddress(final FusionDictionaryBufferInterface buffer, + final FormatOptions formatOptions) { + if (supportsDynamicUpdate(formatOptions)) { + final int parentAddress = buffer.readUnsignedInt24(); + final int sign = ((parentAddress & MSB24) != 0) ? -1 : 1; + return sign * (parentAddress & SINT24_MAX); + } else { + return FormatSpec.NO_PARENT_ADDRESS; + } + } + private static final int[] CHARACTER_BUFFER = new int[FormatSpec.MAX_WORD_LENGTH]; public static CharGroupInfo readCharGroup(final FusionDictionaryBufferInterface buffer, final int originalGroupAddress, final FormatOptions options) { @@ -1111,13 +1229,9 @@ public class BinaryDictInputOutput { final int flags = buffer.readUnsignedByte(); ++addressPointer; - final int parentAddress; + final int parentAddress = readParentAddress(buffer, options); if (supportsDynamicUpdate(options)) { - // read the parent address. (version 3) - parentAddress = -buffer.readUnsignedInt24(); addressPointer += 3; - } else { - parentAddress = FormatSpec.NO_PARENT_ADDRESS; } final int characters[]; @@ -1146,25 +1260,11 @@ public class BinaryDictInputOutput { } else { frequency = CharGroup.NOT_A_TERMINAL; } - int childrenAddress = addressPointer; - switch (flags & FormatSpec.MASK_GROUP_ADDRESS_TYPE) { - case FormatSpec.FLAG_GROUP_ADDRESS_TYPE_ONEBYTE: - childrenAddress += buffer.readUnsignedByte(); - addressPointer += 1; - break; - case FormatSpec.FLAG_GROUP_ADDRESS_TYPE_TWOBYTES: - childrenAddress += buffer.readUnsignedShort(); - addressPointer += 2; - break; - case FormatSpec.FLAG_GROUP_ADDRESS_TYPE_THREEBYTES: - childrenAddress += buffer.readUnsignedInt24(); - addressPointer += 3; - break; - case FormatSpec.FLAG_GROUP_ADDRESS_TYPE_NOADDRESS: - default: - childrenAddress = FormatSpec.NO_CHILDREN_ADDRESS; - break; + int childrenAddress = readChildrenAddress(buffer, flags, options); + if (childrenAddress != FormatSpec.NO_CHILDREN_ADDRESS) { + childrenAddress += addressPointer; } + addressPointer += getChildrenAddressSize(flags, options); ArrayList<WeightedString> shortcutTargets = null; if (0 != (flags & FormatSpec.FLAG_HAS_SHORTCUT_TARGETS)) { final int pointerBefore = buffer.position(); @@ -1250,6 +1350,7 @@ public class BinaryDictInputOutput { final String result; final int originalPointer = buffer.position(); + buffer.position(address); if (supportsDynamicUpdate(formatOptions)) { result = getWordAtAddressWithParentAddress(buffer, headerSize, address, formatOptions); @@ -1279,7 +1380,6 @@ public class BinaryDictInputOutput { sGetWordBuffer[index--] = currentInfo.mCharacters[currentInfo.mCharacters.length - i - 1]; } - if (currentInfo.mParentAddress == FormatSpec.NO_PARENT_ADDRESS) break; currentAddress = currentInfo.mParentAddress + currentInfo.mOriginalAddress; } diff --git a/java/src/com/android/inputmethod/latin/makedict/FormatSpec.java b/java/src/com/android/inputmethod/latin/makedict/FormatSpec.java index 63a61b46f..cab0661f6 100644 --- a/java/src/com/android/inputmethod/latin/makedict/FormatSpec.java +++ b/java/src/com/android/inputmethod/latin/makedict/FormatSpec.java @@ -42,11 +42,13 @@ public final class FormatSpec { * ps * * f | - * o | IF HAS_LINKEDLIST_NODE (defined in the file header) + * o | IF SUPPORTS_DYNAMIC_UPDATE (defined in the file header) * r | forward link address, 3byte - * w | the address must be positive. - * a | - * rdlinkaddress + * w | 1 byte = bbbbbbbb match + * a | case 1xxxxxxx => -((xxxxxxx << 16) + (next byte << 8) + next byte) + * r | otherwise => (xxxxxxx << 16) + (next byte << 8) + next byte + * d | + * linkaddress */ /* Node(CharGroup) layout is as follows: @@ -63,11 +65,13 @@ public final class FormatSpec { * | is blacklisted ? 1 bit, 1 = yes, 0 = no : FLAG_IS_BLACKLISTED * * p | - * a | IF HAS_PARENT_ADDRESS (defined in the file header) + * a | IF SUPPORTS_DYNAMIC_UPDATE (defined in the file header) * r | parent address, 3byte - * e | the address must be negative, so the absolute value of the address is stored. - * n | - * taddress + * e | 1 byte = bbbbbbbb match + * n | case 1xxxxxxx => -((0xxxxxxx << 16) + (next byte << 8) + next byte) + * t | otherwise => (bbbbbbbb << 16) + (next byte << 8) + next byte + * a | + * ddress * * c | IF FLAG_HAS_MULTIPLE_CHARS * h | char, char, char, char n * (1 or 3 bytes) : use CharGroupInfo for i/o helpers @@ -206,6 +210,7 @@ public final class FormatSpec { // This option needs to be the same numeric value as the one in binary_format.h. static final int NOT_VALID_WORD = -99; + static final int SIGNED_CHILDREN_ADDRESS_SIZE = 3; /** * Options about file format. |