diff options
Diffstat (limited to 'java/src')
9 files changed, 345 insertions, 59 deletions
diff --git a/java/src/com/android/inputmethod/keyboard/internal/GesturePreviewTrail.java b/java/src/com/android/inputmethod/keyboard/internal/GesturePreviewTrail.java index b047fe038..e3e6d39e4 100644 --- a/java/src/com/android/inputmethod/keyboard/internal/GesturePreviewTrail.java +++ b/java/src/com/android/inputmethod/keyboard/internal/GesturePreviewTrail.java @@ -44,6 +44,7 @@ final class GesturePreviewTrail { // The wall time of the zero value in {@link #mEventTimes} private long mCurrentTimeBase; private int mTrailStartIndex; + private int mLastInterpolatedDrawIndex; static final class Params { public final int mTrailColor; @@ -96,6 +97,17 @@ final class GesturePreviewTrail { } final int[] eventTimes = mEventTimes.getPrimitiveArray(); final int strokeId = stroke.getGestureStrokeId(); + // Because interpolation algorithm in {@link GestureStrokeWithPreviewPoints} can't determine + // the interpolated points in the last segment of gesture stroke, it may need recalculation + // of interpolation when new segments are added to the stroke. + // {@link #mLastInterpolatedDrawIndex} holds the start index of the last segment. It may + // be updated by the interpolation + // {@link GestureStrokeWithPreviewPoints#interpolatePreviewStroke} + // or by animation {@link #drawGestureTrail(Canvas,Paint,Rect,Params)} below. + final int lastInterpolatedIndex = (strokeId == mCurrentStrokeId) + ? mLastInterpolatedDrawIndex : trailSize; + mLastInterpolatedDrawIndex = stroke.interpolateStrokeAndReturnStartIndexOfLastSegment( + lastInterpolatedIndex, mEventTimes, mXCoordinates, mYCoordinates); if (strokeId != mCurrentStrokeId) { final int elapsedTime = (int)(downTime - mCurrentTimeBase); for (int i = mTrailStartIndex; i < trailSize; i++) { @@ -216,6 +228,10 @@ final class GesturePreviewTrail { System.arraycopy(eventTimes, startIndex, eventTimes, 0, newSize); System.arraycopy(xCoords, startIndex, xCoords, 0, newSize); System.arraycopy(yCoords, startIndex, yCoords, 0, newSize); + // The start index of the last segment of the stroke + // {@link mLastInterpolatedDrawIndex} should also be updated because all array + // elements have just been shifted for compaction. + mLastInterpolatedDrawIndex = Math.max(mLastInterpolatedDrawIndex - startIndex, 0); } mEventTimes.setLength(newSize); mXCoordinates.setLength(newSize); diff --git a/java/src/com/android/inputmethod/keyboard/internal/GestureStrokeWithPreviewPoints.java b/java/src/com/android/inputmethod/keyboard/internal/GestureStrokeWithPreviewPoints.java index fc81410ff..3315954c1 100644 --- a/java/src/com/android/inputmethod/keyboard/internal/GestureStrokeWithPreviewPoints.java +++ b/java/src/com/android/inputmethod/keyboard/internal/GestureStrokeWithPreviewPoints.java @@ -21,19 +21,32 @@ import com.android.inputmethod.latin.ResizableIntArray; public final class GestureStrokeWithPreviewPoints extends GestureStroke { public static final int PREVIEW_CAPACITY = 256; + private static final boolean ENABLE_INTERPOLATION = true; + 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; + private final HermiteInterpolator mInterpolator = new HermiteInterpolator(); + private int mLastInterpolatedPreviewIndex; - private int mMinPreviewSampleLengthSquare; + private int mMinPreviewSamplingDistanceSquared; private int mLastX; private int mLastY; + private double mMinPreviewSamplingDistance; + private double mDistanceFromLastSample; - // TODO: Move this to resource. - private static final float MIN_PREVIEW_SAMPLE_LENGTH_RATIO_TO_KEY_WIDTH = 0.1f; + // TODO: Move these constants to resource. + // The minimum linear distance between sample points for preview in keyWidth unit. + private static final float MIN_PREVIEW_SAMPLING_RATIO_TO_KEY_WIDTH = 0.1f; + // The minimum trail distance between sample points for preview in keyWidth unit when using + // interpolation. + private static final float MIN_PREVIEW_SAMPLING_RATIO_TO_KEY_WIDTH_WITH_INTERPOLATION = 0.2f; + // The angular threshold to use interpolation in radian. PI/12 is 15 degree. + private static final double INTERPOLATION_ANGULAR_THRESHOLD = Math.PI / 12.0d; + private static final int MAX_INTERPOLATION_PARTITION = 4; public GestureStrokeWithPreviewPoints(final int pointerId, final GestureStrokeParams params) { super(pointerId, params); @@ -44,6 +57,7 @@ public final class GestureStrokeWithPreviewPoints extends GestureStroke { super.reset(); mStrokeId++; mLastPreviewSize = 0; + mLastInterpolatedPreviewIndex = 0; mPreviewEventTimes.setLength(0); mPreviewXCoordinates.setLength(0); mPreviewYCoordinates.setLength(0); @@ -53,35 +67,49 @@ public final class GestureStrokeWithPreviewPoints extends GestureStroke { return mStrokeId; } - public int getGestureStrokePreviewSize() { - return mPreviewEventTimes.getLength(); - } - @Override public void setKeyboardGeometry(final int keyWidth, final int keyboardHeight) { super.setKeyboardGeometry(keyWidth, keyboardHeight); - final float sampleLength = keyWidth * MIN_PREVIEW_SAMPLE_LENGTH_RATIO_TO_KEY_WIDTH; - mMinPreviewSampleLengthSquare = (int)(sampleLength * sampleLength); + final float samplingRatioToKeyWidth = ENABLE_INTERPOLATION + ? MIN_PREVIEW_SAMPLING_RATIO_TO_KEY_WIDTH_WITH_INTERPOLATION + : MIN_PREVIEW_SAMPLING_RATIO_TO_KEY_WIDTH; + mMinPreviewSamplingDistance = keyWidth * samplingRatioToKeyWidth; + mMinPreviewSamplingDistanceSquared = (int)( + mMinPreviewSamplingDistance * mMinPreviewSamplingDistance); } - private boolean needsSampling(final int x, final int y) { + private boolean needsSampling(final int x, final int y, final boolean isMajorEvent) { + if (ENABLE_INTERPOLATION) { + mDistanceFromLastSample += Math.hypot(x - mLastX, y - mLastY); + mLastX = x; + mLastY = y; + if (mDistanceFromLastSample >= mMinPreviewSamplingDistance) { + mDistanceFromLastSample = 0.0d; + return true; + } + return false; + } + final int dx = x - mLastX; final int dy = y - mLastY; - return dx * dx + dy * dy >= mMinPreviewSampleLengthSquare; + if (isMajorEvent || dx * dx + dy * dy >= mMinPreviewSamplingDistanceSquared) { + mLastX = x; + mLastY = y; + return true; + } + return false; } @Override public boolean addPointOnKeyboard(final int x, final int y, final int time, final boolean isMajorEvent) { - final boolean onValidArea = super.addPointOnKeyboard(x, y, time, isMajorEvent); - if (isMajorEvent || needsSampling(x, y)) { + if (needsSampling(x, y, isMajorEvent)) { mPreviewEventTimes.add(time); mPreviewXCoordinates.add(x); mPreviewYCoordinates.add(y); - mLastX = x; - mLastY = y; } - return onValidArea; + return super.addPointOnKeyboard(x, y, time, isMajorEvent); + } public void appendPreviewStroke(final ResizableIntArray eventTimes, @@ -95,4 +123,82 @@ public final class GestureStrokeWithPreviewPoints extends GestureStroke { yCoords.append(mPreviewYCoordinates, mLastPreviewSize, length); mLastPreviewSize = mPreviewEventTimes.getLength(); } + + /** + * Calculate interpolated points between the last interpolated point and the end of the trail. + * And return the start index of the last interpolated segment of input arrays because it + * may need to recalculate the interpolated points in the segment if further segments are + * added to this stroke. + * + * @param lastInterpolatedIndex the start index of the last interpolated segment of + * <code>eventTimes</code>, <code>xCoords</code>, and <code>yCoords</code>. + * @param eventTimes the event time array of gesture preview trail to be drawn. + * @param xCoords the x-coordinates array of gesture preview trail to be drawn. + * @param yCoords the y-coordinates array of gesture preview trail to be drawn. + * @return the start index of the last interpolated segment of input arrays. + */ + public int interpolateStrokeAndReturnStartIndexOfLastSegment(final int lastInterpolatedIndex, + final ResizableIntArray eventTimes, final ResizableIntArray xCoords, + final ResizableIntArray yCoords) { + if (!ENABLE_INTERPOLATION) { + return lastInterpolatedIndex; + } + final int size = mPreviewEventTimes.getLength(); + final int[] pt = mPreviewEventTimes.getPrimitiveArray(); + final int[] px = mPreviewXCoordinates.getPrimitiveArray(); + final int[] py = mPreviewYCoordinates.getPrimitiveArray(); + mInterpolator.reset(px, py, 0, size); + // The last segment of gesture stroke needs to be interpolated again because the slope of + // the tangent at the last point isn't determined. + int lastInterpolatedDrawIndex = lastInterpolatedIndex; + int d1 = lastInterpolatedIndex; + for (int p2 = mLastInterpolatedPreviewIndex + 1; p2 < size; p2++) { + final int p1 = p2 - 1; + final int p0 = p1 - 1; + final int p3 = p2 + 1; + mLastInterpolatedPreviewIndex = p1; + lastInterpolatedDrawIndex = d1; + mInterpolator.setInterval(p0, p1, p2, p3); + final double m1 = Math.atan2(mInterpolator.mSlope1Y, mInterpolator.mSlope1X); + final double m2 = Math.atan2(mInterpolator.mSlope2Y, mInterpolator.mSlope2X); + final double dm = Math.abs(angularDiff(m2, m1)); + final int partition = Math.min((int)Math.ceil(dm / INTERPOLATION_ANGULAR_THRESHOLD), + MAX_INTERPOLATION_PARTITION); + final int t1 = eventTimes.get(d1); + final int dt = pt[p2] - pt[p1]; + d1++; + for (int i = 1; i < partition; i++) { + final float t = i / (float)partition; + mInterpolator.interpolate(t); + eventTimes.add(d1, (int)(dt * t) + t1); + xCoords.add(d1, (int)mInterpolator.mInterpolatedX); + yCoords.add(d1, (int)mInterpolator.mInterpolatedY); + d1++; + } + eventTimes.add(d1, pt[p2]); + xCoords.add(d1, px[p2]); + yCoords.add(d1, py[p2]); + } + return lastInterpolatedDrawIndex; + } + + private static final double TWO_PI = Math.PI * 2.0d; + + /** + * Calculate the angular of rotation from <code>a0</code> to <code>a1</code>. + * + * @param a1 the angular to which the rotation ends. + * @param a0 the angular from which the rotation starts. + * @return the angular rotation value from a0 to a1, normalized to [-PI, +PI]. + */ + private static double angularDiff(final double a1, final double a0) { + double deltaAngle = a1 - a0; + while (deltaAngle > Math.PI) { + deltaAngle -= TWO_PI; + } + while (deltaAngle < -Math.PI) { + deltaAngle += TWO_PI; + } + return deltaAngle; + } } diff --git a/java/src/com/android/inputmethod/keyboard/internal/HermiteInterpolator.java b/java/src/com/android/inputmethod/keyboard/internal/HermiteInterpolator.java new file mode 100644 index 000000000..0ec8153f5 --- /dev/null +++ b/java/src/com/android/inputmethod/keyboard/internal/HermiteInterpolator.java @@ -0,0 +1,166 @@ +/* + * Copyright (C) 2013 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.inputmethod.keyboard.internal; + +import com.android.inputmethod.annotations.UsedForTesting; + +/** + * Interpolates XY-coordinates using Cubic Hermite Curve. + */ +public final class HermiteInterpolator { + private int[] mXCoords; + private int[] mYCoords; + private int mMinPos; + private int mMaxPos; + + // Working variable to calculate interpolated value. + /** The coordinates of the start point of the interval. */ + public int mP1X, mP1Y; + /** The coordinates of the end point of the interval. */ + public int mP2X, mP2Y; + /** The slope of the tangent at the start point. */ + public float mSlope1X, mSlope1Y; + /** The slope of the tangent at the end point. */ + public float mSlope2X, mSlope2Y; + /** The interpolated coordinates. + * The return variables of {@link #interpolate(float)} to avoid instantiations. + */ + public float mInterpolatedX, mInterpolatedY; + + public HermiteInterpolator() { + // Nothing to do with here. + } + + /** + * Reset this interpolator to point XY-coordinates data. + * @param xCoords the array of x-coordinates. Valid data are in left-open interval + * <code>[minPos, maxPos)</code>. + * @param yCoords the array of y-coordinates. Valid data are in left-open interval + * <code>[minPos, maxPos)</code>. + * @param minPos the minimum index of left-open interval of valid data. + * @param maxPos the maximum index of left-open interval of valid data. + */ + @UsedForTesting + public void reset(final int[] xCoords, final int[] yCoords, final int minPos, + final int maxPos) { + mXCoords = xCoords; + mYCoords = yCoords; + mMinPos = minPos; + mMaxPos = maxPos; + } + + /** + * Set interpolation interval. + * <p> + * The start and end coordinates of the interval will be set in {@link #mP1X}, {@link #mP1Y}, + * {@link #mP2X}, and {@link #mP2Y}. The slope of the tangents at start and end points will be + * set in {@link #mSlope1X}, {@link #mSlope1Y}, {@link #mSlope2X}, and {@link #mSlope2Y}. + * + * @param p0 the index just before interpolation interval. If <code>p1</code> points the start + * of valid points, <code>p0</code> must be less than <code>minPos</code> of + * {@link #reset(int[],int[],int,int)}. + * @param p1 the start index of interpolation interval. + * @param p2 the end index of interpolation interval. + * @param p3 the index just after interpolation interval. If <code>p2</code> points the end of + * valid points, <code>p3</code> must be equal or greater than <code>maxPos</code> of + * {@link #reset(int[],int[],int,int)}. + */ + @UsedForTesting + public void setInterval(final int p0, final int p1, final int p2, final int p3) { + mP1X = mXCoords[p1]; + mP1Y = mYCoords[p1]; + mP2X = mXCoords[p2]; + mP2Y = mYCoords[p2]; + // A(ax,ay) is the vector p1->p2. + final int ax = mP2X - mP1X; + final int ay = mP2Y - mP1Y; + + // Calculate the slope of the tangent at p1. + if (p0 >= mMinPos) { + // p1 has previous valid point p0. + // The slope of the tangent is half of the vector p0->p2. + mSlope1X = (mP2X - mXCoords[p0]) / 2.0f; + mSlope1Y = (mP2Y - mYCoords[p0]) / 2.0f; + } else if (p3 < mMaxPos) { + // p1 has no previous valid point, but p2 has next valid point p3. + // B(bx,by) is the slope vector of the tangent at p2. + final float bx = (mXCoords[p3] - mP1X) / 2.0f; + final float by = (mYCoords[p3] - mP1Y) / 2.0f; + final float crossProdAB = ax * by - ay * bx; + final float dotProdAB = ax * bx + ay * by; + final float normASquare = ax * ax + ay * ay; + final float invHalfNormASquare = 1.0f / normASquare / 2.0f; + // The slope of the tangent is the mirror image of vector B to vector A. + mSlope1X = invHalfNormASquare * (dotProdAB * ax + crossProdAB * ay); + mSlope1Y = invHalfNormASquare * (dotProdAB * ay - crossProdAB * ax); + } else { + // p1 and p2 have no previous valid point. (Interval has only point p1 and p2) + mSlope1X = ax; + mSlope1Y = ay; + } + + // Calculate the slope of the tangent at p2. + if (p3 < mMaxPos) { + // p2 has next valid point p3. + // The slope of the tangent is half of the vector p1->p3. + mSlope2X = (mXCoords[p3] - mP1X) / 2.0f; + mSlope2Y = (mYCoords[p3] - mP1Y) / 2.0f; + } else if (p0 >= mMinPos) { + // p2 has no next valid point, but p1 has previous valid point p0. + // B(bx,by) is the slope vector of the tangent at p1. + final float bx = (mP2X - mXCoords[p0]) / 2.0f; + final float by = (mP2Y - mYCoords[p0]) / 2.0f; + final float crossProdAB = ax * by - ay * bx; + final float dotProdAB = ax * bx + ay * by; + final float normASquare = ax * ax + ay * ay; + final float invHalfNormASquare = 1.0f / normASquare / 2.0f; + // The slope of the tangent is the mirror image of vector B to vector A. + mSlope2X = invHalfNormASquare * (dotProdAB * ax + crossProdAB * ay); + mSlope2Y = invHalfNormASquare * (dotProdAB * ay - crossProdAB * ax); + } else { + // p1 and p2 has no previous valid point. (Interval has only point p1 and p2) + mSlope2X = ax; + mSlope2Y = ay; + } + } + + /** + * Calculate interpolation value at <code>t</code> in unit interval <code>[0,1]</code>. + * <p> + * On the unit interval [0,1], given a starting point p1 at t=0 and an ending point p2 at t=1 + * with the slope of the tangent m1 at p1 and m2 at p2, the polynomial of cubic Hermite curve + * can be defined by + * p(t) = (1+2t)(1-t)(1-t)*p1 + t(1-t)(1-t)*m1 + (3-2t)t^2*p2 + (t-1)t^2*m2 + * where t is an element of [0,1]. + * <p> + * The interpolated XY-coordinates will be set in {@link #mInterpolatedX} and + * {@link #mInterpolatedY}. + * + * @param t the interpolation parameter. The value must be in close interval <code>[0,1]</code>. + */ + @UsedForTesting + public void interpolate(final float t) { + final float omt = 1.0f - t; + final float tm2 = 2.0f * t; + final float k1 = 1.0f + tm2; + final float k2 = 3.0f - tm2; + final float omt2 = omt * omt; + final float t2 = t * t; + mInterpolatedX = (k1 * mP1X + t * mSlope1X) * omt2 + (k2 * mP2X - omt * mSlope2X) * t2; + mInterpolatedY = (k1 * mP1Y + t * mSlope1Y) * omt2 + (k2 * mP2Y - omt * mSlope2Y) * t2; + } +} diff --git a/java/src/com/android/inputmethod/latin/LatinIME.java b/java/src/com/android/inputmethod/latin/LatinIME.java index 92b68dcd7..3262a9a10 100644 --- a/java/src/com/android/inputmethod/latin/LatinIME.java +++ b/java/src/com/android/inputmethod/latin/LatinIME.java @@ -1143,11 +1143,11 @@ public final class LatinIME extends InputMethodService implements KeyboardAction if (!mWordComposer.isComposingWord()) return; final String typedWord = mWordComposer.getTypedWord(); if (typedWord.length() > 0) { - commitChosenWord(typedWord, LastComposedWord.COMMIT_TYPE_USER_TYPED_WORD, - separatorString); if (ProductionFlag.USES_DEVELOPMENT_ONLY_DIAGNOSTICS) { ResearchLogger.getInstance().onWordFinished(typedWord, mWordComposer.isBatchMode()); } + commitChosenWord(typedWord, LastComposedWord.COMMIT_TYPE_USER_TYPED_WORD, + separatorString); } } @@ -1905,7 +1905,6 @@ public final class LatinIME extends InputMethodService implements KeyboardAction private boolean handleSeparator(final int primaryCode, final int x, final int y, final int spaceState) { if (ProductionFlag.USES_DEVELOPMENT_ONLY_DIAGNOSTICS) { - ResearchLogger.recordTimeForLogUnitSplit(); ResearchLogger.latinIME_handleSeparator(primaryCode, mWordComposer.isComposingWord()); } boolean didAutoCorrect = false; diff --git a/java/src/com/android/inputmethod/latin/StringUtils.java b/java/src/com/android/inputmethod/latin/StringUtils.java index 90c3fcdd2..dcb514a5e 100644 --- a/java/src/com/android/inputmethod/latin/StringUtils.java +++ b/java/src/com/android/inputmethod/latin/StringUtils.java @@ -22,6 +22,10 @@ import java.util.ArrayList; import java.util.Locale; public final class StringUtils { + public static final int CAPITALIZE_NONE = 0; // No caps, or mixed case + public static final int CAPITALIZE_FIRST = 1; // First only + public static final int CAPITALIZE_ALL = 2; // All caps + private StringUtils() { // This utility class is not publicly instantiable. } @@ -171,4 +175,31 @@ public final class StringUtils { } return list.toArray(new String[list.size()]); } + + // This method assumes the text is not empty or null. + public static int getCapitalizationType(final String text) { + // If the first char is not uppercase, then the word is either all lower case or + // camel case, and in either case we return CAPITALIZE_NONE. + if (!Character.isUpperCase(text.codePointAt(0))) return CAPITALIZE_NONE; + final int len = text.length(); + int capsCount = 1; + int letterCount = 1; + for (int i = 1; i < len; i = text.offsetByCodePoints(i, 1)) { + if (1 != capsCount && letterCount != capsCount) break; + final int codePoint = text.codePointAt(i); + if (Character.isUpperCase(codePoint)) { + ++capsCount; + ++letterCount; + } else if (Character.isLetter(codePoint)) { + // We need to discount non-letters since they may not be upper-case, but may + // still be part of a word (e.g. single quote or dash, as in "IT'S" or "FULL-TIME") + ++letterCount; + } + } + // We know the first char is upper case. So we want to test if either every letter other + // than the first is lower case, or if they are all upper case. If the string is exactly + // one char long, then we will arrive here with letterCount 1, and this is correct, too. + if (1 == capsCount) return CAPITALIZE_FIRST; + return (letterCount == capsCount ? CAPITALIZE_ALL : CAPITALIZE_NONE); + } } diff --git a/java/src/com/android/inputmethod/latin/define/ProductionFlag.java b/java/src/com/android/inputmethod/latin/define/ProductionFlag.java index 699e47b6a..dc937fb25 100644 --- a/java/src/com/android/inputmethod/latin/define/ProductionFlag.java +++ b/java/src/com/android/inputmethod/latin/define/ProductionFlag.java @@ -28,5 +28,5 @@ public final class ProductionFlag { // USES_DEVELOPMENT_ONLY_DIAGNOSTICS must be false for any production build. public static final boolean USES_DEVELOPMENT_ONLY_DIAGNOSTICS_DEBUG = false; - public static final boolean IS_HARDWARE_KEYBOARD_SUPPORTED = true; + public static final boolean IS_HARDWARE_KEYBOARD_SUPPORTED = false; } diff --git a/java/src/com/android/inputmethod/latin/spellcheck/AndroidSpellCheckerService.java b/java/src/com/android/inputmethod/latin/spellcheck/AndroidSpellCheckerService.java index 38a26486d..8d3b062ff 100644 --- a/java/src/com/android/inputmethod/latin/spellcheck/AndroidSpellCheckerService.java +++ b/java/src/com/android/inputmethod/latin/spellcheck/AndroidSpellCheckerService.java @@ -58,10 +58,6 @@ public final class AndroidSpellCheckerService extends SpellCheckerService public static final String PREF_USE_CONTACTS_KEY = "pref_spellcheck_use_contacts"; - public static final int CAPITALIZE_NONE = 0; // No caps, or mixed case - public static final int CAPITALIZE_FIRST = 1; // First only - public static final int CAPITALIZE_ALL = 2; // All caps - private final static String[] EMPTY_STRING_ARRAY = new String[0]; private Map<String, DictionaryPool> mDictionaryPools = CollectionUtils.newSynchronizedTreeMap(); private Map<String, UserBinaryDictionary> mUserDictionaries = @@ -325,13 +321,13 @@ public final class AndroidSpellCheckerService extends SpellCheckerService } Collections.reverse(mSuggestions); StringUtils.removeDupes(mSuggestions); - if (CAPITALIZE_ALL == capitalizeType) { + if (StringUtils.CAPITALIZE_ALL == capitalizeType) { for (int i = 0; i < mSuggestions.size(); ++i) { // get(i) returns a CharSequence which is actually a String so .toString() // should return the same object. mSuggestions.set(i, mSuggestions.get(i).toString().toUpperCase(locale)); } - } else if (CAPITALIZE_FIRST == capitalizeType) { + } else if (StringUtils.CAPITALIZE_FIRST == capitalizeType) { for (int i = 0; i < mSuggestions.size(); ++i) { // Likewise mSuggestions.set(i, StringUtils.toTitleCase( @@ -438,31 +434,4 @@ public final class AndroidSpellCheckerService extends SpellCheckerService } return new DictAndProximity(dictionaryCollection, proximityInfo); } - - // This method assumes the text is not empty or null. - public static int getCapitalizationType(String text) { - // If the first char is not uppercase, then the word is either all lower case, - // and in either case we return CAPITALIZE_NONE. - if (!Character.isUpperCase(text.codePointAt(0))) return CAPITALIZE_NONE; - final int len = text.length(); - int capsCount = 1; - int letterCount = 1; - for (int i = 1; i < len; i = text.offsetByCodePoints(i, 1)) { - if (1 != capsCount && letterCount != capsCount) break; - final int codePoint = text.codePointAt(i); - if (Character.isUpperCase(codePoint)) { - ++capsCount; - ++letterCount; - } else if (Character.isLetter(codePoint)) { - // We need to discount non-letters since they may not be upper-case, but may - // still be part of a word (e.g. single quote or dash, as in "IT'S" or "FULL-TIME") - ++letterCount; - } - } - // We know the first char is upper case. So we want to test if either every letter other - // than the first is lower case, or if they are all upper case. If the string is exactly - // one char long, then we will arrive here with letterCount 1, and this is correct, too. - if (1 == capsCount) return CAPITALIZE_FIRST; - return (letterCount == capsCount ? CAPITALIZE_ALL : CAPITALIZE_NONE); - } } diff --git a/java/src/com/android/inputmethod/latin/spellcheck/AndroidWordLevelSpellCheckerSession.java b/java/src/com/android/inputmethod/latin/spellcheck/AndroidWordLevelSpellCheckerSession.java index 4f86a3175..b15063235 100644 --- a/java/src/com/android/inputmethod/latin/spellcheck/AndroidWordLevelSpellCheckerSession.java +++ b/java/src/com/android/inputmethod/latin/spellcheck/AndroidWordLevelSpellCheckerSession.java @@ -150,7 +150,7 @@ public abstract class AndroidWordLevelSpellCheckerSession extends Session { // Greek letters are either in the 370~3FF range (Greek & Coptic), or in the // 1F00~1FFF range (Greek extended). Our dictionary contains both sort of characters. // Our dictionary also contains a few words with 0xF2; it would be best to check - // if that's correct, but a Google search does return results for these words so + // if that's correct, but a web search does return results for these words so // they are probably okay. return (codePoint >= 0x370 && codePoint <= 0x3FF) || (codePoint >= 0x1F00 && codePoint <= 0x1FFF) @@ -214,14 +214,14 @@ public abstract class AndroidWordLevelSpellCheckerSession extends Session { // If the word is in there as is, then it's in the dictionary. If not, we'll test lower // case versions, but only if the word is not already all-lower case or mixed case. if (dict.isValidWord(text)) return true; - if (AndroidSpellCheckerService.CAPITALIZE_NONE == capitalizeType) return false; + if (StringUtils.CAPITALIZE_NONE == capitalizeType) return false; // If we come here, we have a capitalized word (either First- or All-). // Downcase the word and look it up again. If the word is only capitalized, we // tested all possibilities, so if it's still negative we can return false. final String lowerCaseText = text.toLowerCase(mLocale); if (dict.isValidWord(lowerCaseText)) return true; - if (AndroidSpellCheckerService.CAPITALIZE_FIRST == capitalizeType) return false; + if (StringUtils.CAPITALIZE_FIRST == capitalizeType) return false; // If the lower case version is not in the dictionary, it's still possible // that we have an all-caps version of a word that needs to be capitalized @@ -296,7 +296,7 @@ public abstract class AndroidWordLevelSpellCheckerSession extends Session { } } - final int capitalizeType = AndroidSpellCheckerService.getCapitalizationType(text); + final int capitalizeType = StringUtils.getCapitalizationType(text); boolean isInDict = true; DictAndProximity dictInfo = null; try { diff --git a/java/src/com/android/inputmethod/research/ResearchLogger.java b/java/src/com/android/inputmethod/research/ResearchLogger.java index 6e486ce70..fa124f3ba 100644 --- a/java/src/com/android/inputmethod/research/ResearchLogger.java +++ b/java/src/com/android/inputmethod/research/ResearchLogger.java @@ -1635,8 +1635,7 @@ public class ResearchLogger implements SharedPreferences.OnSharedPreferenceChang final String scrubbedAutoCorrection = scrubDigitsFromString(autoCorrection); final ResearchLogger researchLogger = getInstance(); researchLogger.mCurrentLogUnit.initializeSuggestions(suggestedWords); - researchLogger.commitCurrentLogUnitAsWord(scrubbedAutoCorrection, Long.MAX_VALUE, - isBatchMode); + researchLogger.onWordFinished(scrubbedAutoCorrection, isBatchMode); // Add the autocorrection logStatement at the end of the logUnit for the committed word. // We have to do this after calling commitCurrentLogUnitAsWord, because it may split the |