diff options
Diffstat (limited to 'java/src')
31 files changed, 741 insertions, 341 deletions
diff --git a/java/src/com/android/inputmethod/accessibility/KeyCodeDescriptionMapper.java b/java/src/com/android/inputmethod/accessibility/KeyCodeDescriptionMapper.java index 6a01b0190..ea86d98cb 100644 --- a/java/src/com/android/inputmethod/accessibility/KeyCodeDescriptionMapper.java +++ b/java/src/com/android/inputmethod/accessibility/KeyCodeDescriptionMapper.java @@ -209,7 +209,7 @@ public final class KeyCodeDescriptionMapper { private String getDescriptionForActionKey(final Context context, final Keyboard keyboard, final Key key) { final KeyboardId keyboardId = keyboard.mId; - final int actionId = keyboardId.imeActionId(); + final int actionId = keyboardId.imeAction(); final int resId; // Always use the label, if available. diff --git a/java/src/com/android/inputmethod/keyboard/KeyboardId.java b/java/src/com/android/inputmethod/keyboard/KeyboardId.java index f9ff7b089..02116ca08 100644 --- a/java/src/com/android/inputmethod/keyboard/KeyboardId.java +++ b/java/src/com/android/inputmethod/keyboard/KeyboardId.java @@ -172,12 +172,7 @@ public final class KeyboardId { } public int imeAction() { - return InputTypeUtils.getActionIdFromEditorInfo(mEditorInfo); - } - - public int imeActionId() { - final int actionId = imeAction(); - return actionId == InputTypeUtils.IME_ACTION_CUSTOM_LABEL ? mEditorInfo.actionId : actionId; + return InputTypeUtils.getImeOptionsActionIdFromEditorInfo(mEditorInfo); } @Override diff --git a/java/src/com/android/inputmethod/keyboard/ProximityInfo.java b/java/src/com/android/inputmethod/keyboard/ProximityInfo.java index b5ba98d85..0fdbb0d05 100644 --- a/java/src/com/android/inputmethod/keyboard/ProximityInfo.java +++ b/java/src/com/android/inputmethod/keyboard/ProximityInfo.java @@ -80,17 +80,17 @@ public final class ProximityInfo { mNativeProximityInfo = createNativeProximityInfo(touchPositionCorrection); } - public static ProximityInfo createDummyProximityInfo() { + private static ProximityInfo createDummyProximityInfo() { return new ProximityInfo("", 1, 1, 1, 1, 1, 1, EMPTY_KEY_ARRAY, null); } - public static ProximityInfo createSpellCheckerProximityInfo(final int[] proximity, + public static ProximityInfo createSpellCheckerProximityInfo(final int[] proximityCharsArray, final int rowSize, final int gridWidth, final int gridHeight) { final ProximityInfo spellCheckerProximityInfo = createDummyProximityInfo(); spellCheckerProximityInfo.mNativeProximityInfo = spellCheckerProximityInfo.setProximityInfoNative("", rowSize, gridWidth, gridHeight, gridWidth, gridHeight, - 1, proximity, 0, null, null, null, null, null, null, null, null); + 1, proximityCharsArray, 0, null, null, null, null, null, null, null, null); return spellCheckerProximityInfo; } @@ -100,15 +100,13 @@ public final class ProximityInfo { } // TODO: Stop passing proximityCharsArray - private native long setProximityInfoNative( - String locale, int maxProximityCharsSize, int displayWidth, - int displayHeight, int gridWidth, int gridHeight, - int mostCommonKeyWidth, int[] proximityCharsArray, - int keyCount, int[] keyXCoordinates, int[] keyYCoordinates, - int[] keyWidths, int[] keyHeights, int[] keyCharCodes, + private static native long setProximityInfoNative(String locale, int maxProximityCharsSize, + int displayWidth, int displayHeight, int gridWidth, int gridHeight, + int mostCommonKeyWidth, int[] proximityCharsArray, int keyCount, int[] keyXCoordinates, + int[] keyYCoordinates, int[] keyWidths, int[] keyHeights, int[] keyCharCodes, float[] sweetSpotCenterX, float[] sweetSpotCenterY, float[] sweetSpotRadii); - private native void releaseProximityInfoNative(long nativeProximityInfo); + private static native void releaseProximityInfoNative(long nativeProximityInfo); private static boolean needsProximityInfo(final Key key) { // Don't include special keys into ProximityInfo. @@ -232,10 +230,10 @@ public final class ProximityInfo { } // TODO: Stop passing proximityCharsArray - return setProximityInfoNative(mLocaleStr, MAX_PROXIMITY_CHARS_SIZE, - mKeyboardMinWidth, mKeyboardHeight, mGridWidth, mGridHeight, mMostCommonKeyWidth, - proximityCharsArray, keyCount, keyXCoordinates, keyYCoordinates, keyWidths, - keyHeights, keyCharCodes, sweetSpotCenterXs, sweetSpotCenterYs, sweetSpotRadii); + return setProximityInfoNative(mLocaleStr, MAX_PROXIMITY_CHARS_SIZE, mKeyboardMinWidth, + mKeyboardHeight, mGridWidth, mGridHeight, mMostCommonKeyWidth, proximityCharsArray, + keyCount, keyXCoordinates, keyYCoordinates, keyWidths, keyHeights, keyCharCodes, + sweetSpotCenterXs, sweetSpotCenterYs, sweetSpotRadii); } public long getNativeProximityInfo() { diff --git a/java/src/com/android/inputmethod/keyboard/internal/GesturePreviewTrail.java b/java/src/com/android/inputmethod/keyboard/internal/GesturePreviewTrail.java index a8407254f..4a8407cb5 100644 --- a/java/src/com/android/inputmethod/keyboard/internal/GesturePreviewTrail.java +++ b/java/src/com/android/inputmethod/keyboard/internal/GesturePreviewTrail.java @@ -138,6 +138,7 @@ final class GesturePreviewTrail { } private final RoundedLine mRoundedLine = new RoundedLine(); + private final Rect mRoundedLineBounds = new Rect(); /** * Draw gesture preview trail @@ -149,6 +150,8 @@ final class GesturePreviewTrail { */ public boolean drawGestureTrail(final Canvas canvas, final Paint paint, final Rect outBoundsRect, final Params params) { + // Initialize bounds rectangle. + outBoundsRect.setEmpty(); final int trailSize = mEventTimes.getLength(); if (trailSize == 0) { return false; @@ -171,39 +174,32 @@ final class GesturePreviewTrail { if (startIndex < trailSize) { paint.setColor(params.mTrailColor); paint.setStyle(Paint.Style.FILL); - final RoundedLine line = mRoundedLine; + final RoundedLine roundedLine = mRoundedLine; int p1x = getXCoordValue(xCoords[startIndex]); int p1y = yCoords[startIndex]; final int lastTime = sinceDown - eventTimes[startIndex]; - float maxWidth = getWidth(lastTime, params); - float r1 = maxWidth / 2.0f; - // Initialize bounds rectangle. - outBoundsRect.set(p1x, p1y, p1x, p1y); + float r1 = getWidth(lastTime, params) / 2.0f; for (int i = startIndex + 1; i < trailSize; i++) { final int elapsedTime = sinceDown - eventTimes[i]; final int p2x = getXCoordValue(xCoords[i]); final int p2y = yCoords[i]; - final float width = getWidth(elapsedTime, params); - final float r2 = width / 2.0f; + final float r2 = getWidth(elapsedTime, params) / 2.0f; // Draw trail line only when the current point isn't a down point. if (!isDownEventXCoord(xCoords[i])) { - final Path path = line.makePath(p1x, p1y, r1, p2x, p2y, r2); + final Path path = roundedLine.makePath(p1x, p1y, r1, p2x, p2y, r2); if (path != null) { final int alpha = getAlpha(elapsedTime, params); paint.setAlpha(alpha); canvas.drawPath(path, paint); // Take union for the bounds. - outBoundsRect.union(p2x, p2y); - maxWidth = Math.max(maxWidth, width); + roundedLine.getBounds(mRoundedLineBounds); + outBoundsRect.union(mRoundedLineBounds); } } p1x = p2x; p1y = p2y; r1 = r2; } - // Take care of trail line width. - final int inset = -((int)maxWidth + 1); - outBoundsRect.inset(inset, inset); } final int newSize = trailSize - startIndex; diff --git a/java/src/com/android/inputmethod/keyboard/internal/KeyboardCodesSet.java b/java/src/com/android/inputmethod/keyboard/internal/KeyboardCodesSet.java index 428e31ccd..0d852dde2 100644 --- a/java/src/com/android/inputmethod/keyboard/internal/KeyboardCodesSet.java +++ b/java/src/com/android/inputmethod/keyboard/internal/KeyboardCodesSet.java @@ -51,6 +51,7 @@ public final class KeyboardCodesSet { "key_action_enter", "key_action_next", "key_action_previous", + "key_shift_enter", "key_language_switch", "key_research", "key_unspecified", @@ -86,6 +87,7 @@ public final class KeyboardCodesSet { Constants.CODE_ACTION_ENTER, Constants.CODE_ACTION_NEXT, Constants.CODE_ACTION_PREVIOUS, + Constants.CODE_SHIFT_ENTER, Constants.CODE_LANGUAGE_SWITCH, Constants.CODE_RESEARCH, Constants.CODE_UNSPECIFIED, diff --git a/java/src/com/android/inputmethod/keyboard/internal/KeyboardTextsSet.java b/java/src/com/android/inputmethod/keyboard/internal/KeyboardTextsSet.java index 6fefb809b..6ad7f9ba2 100644 --- a/java/src/com/android/inputmethod/keyboard/internal/KeyboardTextsSet.java +++ b/java/src/com/android/inputmethod/keyboard/internal/KeyboardTextsSet.java @@ -528,7 +528,7 @@ public final class KeyboardTextsSet { // U+064E: "َ" ARABIC FATHA // U+0640: "ـ" ARABIC TATWEEL // In order to make Tatweel easily distinguishable from other punctuations, we use consecutive Tatweels only for its displayed label. - /* 57 */ "!fixedColumnOrder!7,\u0655,\u0654,\u0652,\u064D,\u064C,\u064B,\u0651,\u0656,\u0670,\u0653,\u0650,\u064F,\u064E,\u0640\u0640\u0640|\u0640", + /* 57 */ "!fixedColumnOrder!7, \u0655|\u0655, \u0654|\u0654, \u0652|\u0652, \u064D|\u064D, \u064C|\u064C, \u064B|\u064B, \u0651|\u0651, \u0656|\u0656, \u0670|\u0670, \u0653|\u0653, \u0650|\u0650, \u064F|\u064F, \u064E|\u064E,\u0640\u0640\u0640|\u0640", /* 58 */ "\u0651", // U+0661: "١" ARABIC-INDIC DIGIT ONE /* 59 */ "\u0661", @@ -1327,7 +1327,7 @@ public final class KeyboardTextsSet { // U+064E: "َ" ARABIC FATHA // U+0640: "ـ" ARABIC TATWEEL // In order to make Tatweel easily distinguishable from other punctuations, we use consecutive Tatweels only for its displayed label. - /* 57 */ "!fixedColumnOrder!7,\u0655,\u0652,\u0651,\u064C,\u064D,\u064B,\u0654,\u0656,\u0670,\u0653,\u064F,\u0650,\u064E,\u0640\u0640\u0640|\u0640", + /* 57 */ "!fixedColumnOrder!7, \u0655|\u0655, \u0652|\u0652, \u0651|\u0651, \u064C|\u064C, \u064D|\u064D, \u064B|\u064B, \u0654|\u0654, \u0656|\u0656, \u0670|\u0670, \u0653|\u0653, \u064F|\u064F, \u0650|\u0650, \u064E|\u064E,\u0640\u0640\u0640|\u0640", /* 58 */ "\u064B", // U+06F1: "۱" EXTENDED ARABIC-INDIC DIGIT ONE /* 59 */ "\u06F1", diff --git a/java/src/com/android/inputmethod/keyboard/internal/PreviewPlacerView.java b/java/src/com/android/inputmethod/keyboard/internal/PreviewPlacerView.java index bfb7b1fe0..7c87467bb 100644 --- a/java/src/com/android/inputmethod/keyboard/internal/PreviewPlacerView.java +++ b/java/src/com/android/inputmethod/keyboard/internal/PreviewPlacerView.java @@ -52,7 +52,8 @@ public final class PreviewPlacerView extends RelativeLayout { private int mOffscreenOffsetY; private Bitmap mOffscreenBuffer; private final Canvas mOffscreenCanvas = new Canvas(); - private final Rect mOffscreenDirtyRect = new Rect(); + private final Rect mOffscreenSrcRect = new Rect(); + private final Rect mDirtyRect = new Rect(); private final Rect mGesturePreviewTrailBoundsRect = new Rect(); // per trail private final GestureFloatingPreviewText mGestureFloatingPreviewText; private boolean mShowSlidingKeyInputPreview; @@ -193,6 +194,7 @@ public final class PreviewPlacerView extends RelativeLayout { mOffscreenBuffer = Bitmap.createBitmap( mOffscreenWidth, mOffscreenHeight, Bitmap.Config.ARGB_8888); mOffscreenCanvas.setBitmap(mOffscreenBuffer); + mOffscreenCanvas.translate(0, mOffscreenOffsetY); } @Override @@ -205,19 +207,18 @@ public final class PreviewPlacerView extends RelativeLayout { mayAllocateOffscreenBuffer(); // Draw gesture trails to offscreen buffer. final boolean needsUpdatingGesturePreviewTrail = drawGestureTrails( - mOffscreenCanvas, mGesturePaint, mOffscreenDirtyRect); + mOffscreenCanvas, mGesturePaint, mDirtyRect); + if (needsUpdatingGesturePreviewTrail) { + mDrawingHandler.postUpdateGestureTrailPreview(); + } // Transfer offscreen buffer to screen. - if (!mOffscreenDirtyRect.isEmpty()) { - canvas.translate(0, - mOffscreenOffsetY); - canvas.drawBitmap(mOffscreenBuffer, mOffscreenDirtyRect, mOffscreenDirtyRect, - mGesturePaint); - canvas.translate(0, mOffscreenOffsetY); + if (!mDirtyRect.isEmpty()) { + mOffscreenSrcRect.set(mDirtyRect); + mOffscreenSrcRect.offset(0, mOffscreenOffsetY); + canvas.drawBitmap(mOffscreenBuffer, mOffscreenSrcRect, mDirtyRect, null); // Note: Defer clearing the dirty rectangle here because we will get cleared // rectangle on the canvas. } - if (needsUpdatingGesturePreviewTrail) { - mDrawingHandler.postUpdateGestureTrailPreview(); - } } mGestureFloatingPreviewText.onDraw(canvas); if (mShowSlidingKeyInputPreview) { @@ -235,10 +236,8 @@ public final class PreviewPlacerView extends RelativeLayout { offscreenCanvas.drawRect(dirtyRect, paint); } dirtyRect.setEmpty(); - - // Draw gesture trails to offscreen buffer. - offscreenCanvas.translate(0, mOffscreenOffsetY); boolean needsUpdatingGesturePreviewTrail = false; + // Draw gesture trails to offscreen buffer. synchronized (mGesturePreviewTrails) { // Trails count == fingers count that have ever been active. final int trailsCount = mGesturePreviewTrails.size(); @@ -251,20 +250,9 @@ public final class PreviewPlacerView extends RelativeLayout { dirtyRect.union(mGesturePreviewTrailBoundsRect); } } - offscreenCanvas.translate(0, -mOffscreenOffsetY); - - // Clip dirty rectangle with offscreen buffer width/height. - dirtyRect.offset(0, mOffscreenOffsetY); - clipRect(dirtyRect, 0, 0, mOffscreenWidth, mOffscreenHeight); return needsUpdatingGesturePreviewTrail; } - private static void clipRect(final Rect out, final int left, final int top, final int right, - final int bottom) { - out.set(Math.max(out.left, left), Math.max(out.top, top), Math.min(out.right, right), - Math.min(out.bottom, bottom)); - } - public void setGestureFloatingPreviewText(final SuggestedWords suggestedWords) { if (!mGestureFloatingPreviewText.isPreviewEnabled()) return; mGestureFloatingPreviewText.setSuggetedWords(suggestedWords); diff --git a/java/src/com/android/inputmethod/keyboard/internal/RoundedLine.java b/java/src/com/android/inputmethod/keyboard/internal/RoundedLine.java index 1f5252077..cd6efc4b7 100644 --- a/java/src/com/android/inputmethod/keyboard/internal/RoundedLine.java +++ b/java/src/com/android/inputmethod/keyboard/internal/RoundedLine.java @@ -15,6 +15,7 @@ package com.android.inputmethod.keyboard.internal; import android.graphics.Path; +import android.graphics.Rect; import android.graphics.RectF; public final class RoundedLine { @@ -100,4 +101,10 @@ public final class RoundedLine { mPath.close(); return mPath; } + + public void getBounds(final Rect outBounds) { + // Reuse mArc1 as working variable + mPath.computeBounds(mArc1, true /* unused */); + mArc1.roundOut(outBounds); + } } diff --git a/java/src/com/android/inputmethod/latin/BinaryDictionary.java b/java/src/com/android/inputmethod/latin/BinaryDictionary.java index 448d25c73..27af3d1e0 100644 --- a/java/src/com/android/inputmethod/latin/BinaryDictionary.java +++ b/java/src/com/android/inputmethod/latin/BinaryDictionary.java @@ -35,24 +35,16 @@ public final class BinaryDictionary extends Dictionary { public static final String DICTIONARY_PACK_AUTHORITY = "com.android.inputmethod.latin.dictionarypack"; - /** - * There is a difference between what java and native code can handle. - * This value should only be used in BinaryDictionary.java - * It is necessary to keep it at this value because some languages e.g. German have - * really long words. - */ + // Must be identical to MAX_WORD_LENGTH in native/jni/src/defines.h private static final int MAX_WORD_LENGTH = Constants.Dictionary.MAX_WORD_LENGTH; - private static final int MAX_WORDS = 18; - private static final int MAX_SPACES = 16; - - private static final int MAX_PREDICTIONS = 60; - private static final int MAX_RESULTS = Math.max(MAX_PREDICTIONS, MAX_WORDS); + // Must be identical to MAX_RESULTS in native/jni/src/defines.h + private static final int MAX_RESULTS = 18; private long mNativeDict; private final Locale mLocale; private final int[] mInputCodePoints = new int[MAX_WORD_LENGTH]; private final int[] mOutputCodePoints = new int[MAX_WORD_LENGTH * MAX_RESULTS]; - private final int[] mSpaceIndices = new int[MAX_SPACES]; + private final int[] mSpaceIndices = new int[MAX_RESULTS]; private final int[] mOutputScores = new int[MAX_RESULTS]; private final int[] mOutputTypes = new int[MAX_RESULTS]; @@ -80,16 +72,14 @@ public final class BinaryDictionary extends Dictionary { /** * Constructor for the binary dictionary. This is supposed to be called from the * dictionary factory. - * @param context the context to access the environment from. * @param filename the name of the file to read through native code. * @param offset the offset of the dictionary data within the file. * @param length the length of the binary data. * @param useFullEditDistance whether to use the full edit distance in suggestions * @param dictType the dictionary type, as a human-readable string */ - public BinaryDictionary(final Context context, final String filename, final long offset, - final long length, final boolean useFullEditDistance, final Locale locale, - final String dictType) { + public BinaryDictionary(final String filename, final long offset, final long length, + final boolean useFullEditDistance, final Locale locale, final String dictType) { super(dictType); mLocale = locale; mUseFullEditDistance = useFullEditDistance; @@ -100,24 +90,22 @@ public final class BinaryDictionary extends Dictionary { JniUtils.loadNativeLibrary(); } - private native long openNative(String sourceDir, long dictOffset, long dictSize, - int maxWordLength, int maxWords, int maxPredictions); - private native void closeNative(long dict); - private native int getFrequencyNative(long dict, int[] word); - private native boolean isValidBigramNative(long dict, int[] word1, int[] word2); - private native int getSuggestionsNative(long dict, long proximityInfo, long traverseSession, - int[] xCoordinates, int[] yCoordinates, int[] times, int[] pointerIds, - int[] inputCodePoints, int codesSize, int commitPoint, boolean isGesture, - int[] prevWordCodePointArray, boolean useFullEditDistance, int[] outputCodePoints, - int[] outputScores, int[] outputIndices, int[] outputTypes); + private static native long openNative(String sourceDir, long dictOffset, long dictSize); + private static native void closeNative(long dict); + private static native int getFrequencyNative(long dict, int[] word); + private static native boolean isValidBigramNative(long dict, int[] word1, int[] word2); + private static native int getSuggestionsNative(long dict, long proximityInfo, + long traverseSession, int[] xCoordinates, int[] yCoordinates, int[] times, + int[] pointerIds, int[] inputCodePoints, int inputSize, int commitPoint, + boolean isGesture, int[] prevWordCodePointArray, boolean useFullEditDistance, + int[] outputCodePoints, int[] outputScores, int[] outputIndices, int[] outputTypes); private static native float calcNormalizedScoreNative(int[] before, int[] after, int score); private static native int editDistanceNative(int[] before, int[] after); // TODO: Move native dict into session private final void loadDictionary(final String path, final long startOffset, final long length) { - mNativeDict = openNative(path, startOffset, length, MAX_WORD_LENGTH, MAX_WORDS, - MAX_PREDICTIONS); + mNativeDict = openNative(path, startOffset, length); } @Override @@ -146,16 +134,14 @@ public final class BinaryDictionary extends Dictionary { } final InputPointers ips = composer.getInputPointers(); - final int codesSize = isGesture ? ips.getPointerSize() : composerSize; + final int inputSize = isGesture ? ips.getPointerSize() : composerSize; // proximityInfo and/or prevWordForBigrams may not be null. - final int tmpCount = getSuggestionsNative(mNativeDict, - proximityInfo.getNativeProximityInfo(), getTraverseSession(sessionId).getSession(), - ips.getXCoordinates(), ips.getYCoordinates(), ips.getTimes(), ips.getPointerIds(), - mInputCodePoints, codesSize, 0 /* commitPoint */, isGesture, prevWordCodePointArray, + final int count = getSuggestionsNative(mNativeDict, proximityInfo.getNativeProximityInfo(), + getTraverseSession(sessionId).getSession(), ips.getXCoordinates(), + ips.getYCoordinates(), ips.getTimes(), ips.getPointerIds(), mInputCodePoints, + inputSize, 0 /* commitPoint */, isGesture, prevWordCodePointArray, mUseFullEditDistance, mOutputCodePoints, mOutputScores, mSpaceIndices, mOutputTypes); - final int count = Math.min(tmpCount, MAX_PREDICTIONS); - final ArrayList<SuggestedWordInfo> suggestions = CollectionUtils.newArrayList(); for (int j = 0; j < count; ++j) { if (composerSize > 0 && mOutputScores[j] < 1) break; diff --git a/java/src/com/android/inputmethod/latin/Constants.java b/java/src/com/android/inputmethod/latin/Constants.java index 3a7772452..483504cfb 100644 --- a/java/src/com/android/inputmethod/latin/Constants.java +++ b/java/src/com/android/inputmethod/latin/Constants.java @@ -127,6 +127,7 @@ public final class Constants { } public static final class Dictionary { + // Must be identical to MAX_WORD_LENGTH in native/jni/src/defines.h public static final int MAX_WORD_LENGTH = 48; private Dictionary() { @@ -183,8 +184,9 @@ public final class Constants { public static final int CODE_ACTION_PREVIOUS = -9; public static final int CODE_LANGUAGE_SWITCH = -10; public static final int CODE_RESEARCH = -11; + public static final int CODE_SHIFT_ENTER = -12; // Code value representing the code is not specified. - public static final int CODE_UNSPECIFIED = -12; + public static final int CODE_UNSPECIFIED = -13; public static boolean isLetterCode(final int code) { return code >= CODE_SPACE; diff --git a/java/src/com/android/inputmethod/latin/DicTraverseSession.java b/java/src/com/android/inputmethod/latin/DicTraverseSession.java index ce1b64660..534e2116b 100644 --- a/java/src/com/android/inputmethod/latin/DicTraverseSession.java +++ b/java/src/com/android/inputmethod/latin/DicTraverseSession.java @@ -23,10 +23,10 @@ public final class DicTraverseSession { JniUtils.loadNativeLibrary(); } - private native long setDicTraverseSessionNative(String locale); - private native void initDicTraverseSessionNative(long nativeDicTraverseSession, + private static native long setDicTraverseSessionNative(String locale); + private static native void initDicTraverseSessionNative(long nativeDicTraverseSession, long dictionary, int[] previousWord, int previousWordLength); - private native void releaseDicTraverseSessionNative(long nativeDicTraverseSession); + private static native void releaseDicTraverseSessionNative(long nativeDicTraverseSession); private long mNativeDicTraverseSession; diff --git a/java/src/com/android/inputmethod/latin/DictionaryFactory.java b/java/src/com/android/inputmethod/latin/DictionaryFactory.java index f381973ae..22cf5b38d 100644 --- a/java/src/com/android/inputmethod/latin/DictionaryFactory.java +++ b/java/src/com/android/inputmethod/latin/DictionaryFactory.java @@ -58,9 +58,8 @@ public final class DictionaryFactory { BinaryDictionaryGetter.getDictionaryFiles(locale, context); if (null != assetFileList) { for (final AssetFileAddress f : assetFileList) { - final BinaryDictionary binaryDictionary = - new BinaryDictionary(context, f.mFilename, f.mOffset, f.mLength, - useFullEditDistance, locale, Dictionary.TYPE_MAIN); + final BinaryDictionary binaryDictionary = new BinaryDictionary(f.mFilename, + f.mOffset, f.mLength, useFullEditDistance, locale, Dictionary.TYPE_MAIN); if (binaryDictionary.isValidDictionary()) { dictList.add(binaryDictionary); } @@ -112,7 +111,7 @@ public final class DictionaryFactory { Log.e(TAG, "sourceDir is not a file: " + sourceDir); return null; } - return new BinaryDictionary(context, sourceDir, afd.getStartOffset(), afd.getLength(), + return new BinaryDictionary(sourceDir, afd.getStartOffset(), afd.getLength(), false /* useFullEditDistance */, locale, Dictionary.TYPE_MAIN); } catch (android.content.res.Resources.NotFoundException e) { Log.e(TAG, "Could not find the resource"); @@ -130,17 +129,16 @@ public final class DictionaryFactory { /** * Create a dictionary from passed data. This is intended for unit tests only. - * @param context the test context to create this data from. * @param dictionary the file to read * @param startOffset the offset in the file where the data starts * @param length the length of the data * @param useFullEditDistance whether to use the full edit distance in suggestions * @return the created dictionary, or null. */ - public static Dictionary createDictionaryForTest(Context context, File dictionary, - long startOffset, long length, final boolean useFullEditDistance, Locale locale) { + public static Dictionary createDictionaryForTest(File dictionary, long startOffset, long length, + final boolean useFullEditDistance, Locale locale) { if (dictionary.isFile()) { - return new BinaryDictionary(context, dictionary.getAbsolutePath(), startOffset, length, + return new BinaryDictionary(dictionary.getAbsolutePath(), startOffset, length, useFullEditDistance, locale, Dictionary.TYPE_MAIN); } else { Log.e(TAG, "Could not find the file. path=" + dictionary.getAbsolutePath()); diff --git a/java/src/com/android/inputmethod/latin/ExpandableBinaryDictionary.java b/java/src/com/android/inputmethod/latin/ExpandableBinaryDictionary.java index 47adaa8ed..2c7fdcc93 100644 --- a/java/src/com/android/inputmethod/latin/ExpandableBinaryDictionary.java +++ b/java/src/com/android/inputmethod/latin/ExpandableBinaryDictionary.java @@ -279,9 +279,8 @@ abstract public class ExpandableBinaryDictionary extends Dictionary { final long length = file.length(); // Build the new binary dictionary - final BinaryDictionary newBinaryDictionary = - new BinaryDictionary(mContext, filename, 0, length, true /* useFullEditDistance */, - null, mDictType); + final BinaryDictionary newBinaryDictionary = new BinaryDictionary(filename, 0, length, + true /* useFullEditDistance */, null, mDictType); if (mBinaryDictionary != null) { // Ensure all threads accessing the current dictionary have finished before swapping in diff --git a/java/src/com/android/inputmethod/latin/InputTypeUtils.java b/java/src/com/android/inputmethod/latin/InputTypeUtils.java index 55414b809..e2eacb3f0 100644 --- a/java/src/com/android/inputmethod/latin/InputTypeUtils.java +++ b/java/src/com/android/inputmethod/latin/InputTypeUtils.java @@ -105,7 +105,7 @@ public final class InputTypeUtils implements InputType { return true; } - public static int getActionIdFromEditorInfo(final EditorInfo editorInfo) { + public static int getImeOptionsActionIdFromEditorInfo(final EditorInfo editorInfo) { final int actionId = editorInfo.imeOptions & EditorInfo.IME_MASK_ACTION; if ((editorInfo.imeOptions & EditorInfo.IME_FLAG_NO_ENTER_ACTION) != 0) { return EditorInfo.IME_ACTION_NONE; @@ -115,4 +115,9 @@ public final class InputTypeUtils implements InputType { return actionId; } } + + public static int getConcreteActionIdFromEditorInfo(final EditorInfo editorInfo) { + final int actionId = getImeOptionsActionIdFromEditorInfo(editorInfo); + return actionId == InputTypeUtils.IME_ACTION_CUSTOM_LABEL ? editorInfo.actionId : actionId; + } } diff --git a/java/src/com/android/inputmethod/latin/LastComposedWord.java b/java/src/com/android/inputmethod/latin/LastComposedWord.java index 488a6fcf2..a4019e906 100644 --- a/java/src/com/android/inputmethod/latin/LastComposedWord.java +++ b/java/src/com/android/inputmethod/latin/LastComposedWord.java @@ -45,19 +45,21 @@ public final class LastComposedWord { public final String mCommittedWord; public final String mSeparatorString; public final String mPrevWord; + public final int mCapitalizedMode; public final InputPointers mInputPointers = new InputPointers(Constants.Dictionary.MAX_WORD_LENGTH); private boolean mActive; public static final LastComposedWord NOT_A_COMPOSED_WORD = - new LastComposedWord(null, null, "", "", NOT_A_SEPARATOR, null); + new LastComposedWord(null, null, "", "", NOT_A_SEPARATOR, null, + WordComposer.CAPS_MODE_OFF); // Warning: this is using the passed objects as is and fully expects them to be // immutable. Do not fiddle with their contents after you passed them to this constructor. public LastComposedWord(final int[] primaryKeyCodes, final InputPointers inputPointers, - final String typedWord, final String committedWord, - final String separatorString, final String prevWord) { + final String typedWord, final String committedWord, final String separatorString, + final String prevWord, final int capitalizedMode) { mPrimaryKeyCodes = primaryKeyCodes; if (inputPointers != null) { mInputPointers.copy(inputPointers); @@ -67,6 +69,7 @@ public final class LastComposedWord { mSeparatorString = separatorString; mActive = true; mPrevWord = prevWord; + mCapitalizedMode = capitalizedMode; } public void deactivate() { diff --git a/java/src/com/android/inputmethod/latin/LatinIME.java b/java/src/com/android/inputmethod/latin/LatinIME.java index 152118d98..d02c4df7e 100644 --- a/java/src/com/android/inputmethod/latin/LatinIME.java +++ b/java/src/com/android/inputmethod/latin/LatinIME.java @@ -166,6 +166,7 @@ public final class LatinIME extends InputMethodService implements KeyboardAction private boolean mExpectingUpdateSelection; private int mDeleteCount; private long mLastKeyTime; + private int mActionId; // Member variables for remembering the current device orientation. private int mDisplayOrientation; @@ -754,6 +755,7 @@ public final class LatinIME extends InputMethodService implements KeyboardAction mLastSelectionStart = editorInfo.initialSelStart; mLastSelectionEnd = editorInfo.initialSelEnd; + mActionId = InputTypeUtils.getConcreteActionIdFromEditorInfo(editorInfo); mHandler.cancelUpdateSuggestionStrip(); mHandler.cancelDoubleSpacePeriodTimer(); @@ -1223,7 +1225,8 @@ public final class LatinIME extends InputMethodService implements KeyboardAction } mPositionalInfoForUserDictPendingAddition = new PositionalInfoForUserDictPendingAddition( - word, mLastSelectionEnd, getCurrentInputEditorInfo()); + word, mLastSelectionEnd, getCurrentInputEditorInfo(), + mLastComposedWord.mCapitalizedMode); mUserDictionary.addWordToUserDictionary(word, 128); } @@ -1272,10 +1275,6 @@ public final class LatinIME extends InputMethodService implements KeyboardAction return mOptionsDialog != null && mOptionsDialog.isShowing(); } - private static int getActionId(final Keyboard keyboard) { - return keyboard != null ? keyboard.mId.imeActionId() : EditorInfo.IME_ACTION_NONE; - } - private void performEditorAction(final int actionId) { mConnection.performEditorAction(actionId); } @@ -1301,13 +1300,13 @@ public final class LatinIME extends InputMethodService implements KeyboardAction } private void sendKeyCodePoint(final int code) { + if (ProductionFlag.IS_EXPERIMENTAL) { + ResearchLogger.latinIME_sendKeyCodePoint(code); + } // TODO: Remove this special handling of digit letters. // For backward compatibility. See {@link InputMethodService#sendKeyChar(char)}. if (code >= '0' && code <= '9') { sendDownUpKeyEventForBackwardCompatibility(code - '0' + KeyEvent.KEYCODE_0); - if (ProductionFlag.IS_EXPERIMENTAL) { - ResearchLogger.latinIME_sendKeyCodePoint(code); - } return; } @@ -1327,6 +1326,9 @@ public final class LatinIME extends InputMethodService implements KeyboardAction // Implementation of {@link KeyboardActionListener}. @Override public void onCodeInput(final int primaryCode, final int x, final int y) { + if (ProductionFlag.IS_EXPERIMENTAL) { + ResearchLogger.latinIME_onCodeInput(primaryCode, x, y); + } final long when = SystemClock.uptimeMillis(); if (primaryCode != Constants.CODE_DELETE || when > mLastKeyTime + QUICK_PRESS) { mDeleteCount = 0; @@ -1366,9 +1368,6 @@ public final class LatinIME extends InputMethodService implements KeyboardAction case Constants.CODE_SHORTCUT: mSubtypeSwitcher.switchToShortcutIME(this); break; - case Constants.CODE_ACTION_ENTER: - performEditorAction(getActionId(switcher.getKeyboard())); - break; case Constants.CODE_ACTION_NEXT: performEditorAction(EditorInfo.IME_ACTION_NEXT); break; @@ -1383,32 +1382,19 @@ public final class LatinIME extends InputMethodService implements KeyboardAction ResearchLogger.getInstance().onResearchKeySelected(this); } break; - default: - mSpaceState = SPACE_STATE_NONE; - if (mSettings.getCurrent().isWordSeparator(primaryCode)) { - didAutoCorrect = handleSeparator(primaryCode, x, y, spaceState); - } else { - if (SPACE_STATE_PHANTOM == spaceState) { - if (ProductionFlag.IS_INTERNAL) { - if (mWordComposer.isComposingWord() && mWordComposer.isBatchMode()) { - Stats.onAutoCorrection( - "", mWordComposer.getTypedWord(), " ", mWordComposer); - } - } - commitTyped(LastComposedWord.NOT_A_SEPARATOR); - } - final int keyX, keyY; - final Keyboard keyboard = mKeyboardSwitcher.getKeyboard(); - if (keyboard != null && keyboard.hasProximityCharsCorrection(primaryCode)) { - keyX = x; - keyY = y; - } else { - keyX = Constants.NOT_A_COORDINATE; - keyY = Constants.NOT_A_COORDINATE; - } - handleCharacter(primaryCode, keyX, keyY, spaceState); + case Constants.CODE_ACTION_ENTER: + if (EditorInfo.IME_ACTION_NONE != mActionId + && EditorInfo.IME_ACTION_UNSPECIFIED != mActionId) { + performEditorAction(mActionId); + break; } - mExpectingUpdateSelection = true; + didAutoCorrect = handleNonSpecialCharacter(Constants.CODE_ENTER, x, y, spaceState); + break; + case Constants.CODE_SHIFT_ENTER: + didAutoCorrect = handleNonSpecialCharacter(Constants.CODE_ENTER, x, y, spaceState); + break; + default: + didAutoCorrect = handleNonSpecialCharacter(primaryCode, x, y, spaceState); break; } switcher.onCodeInput(primaryCode); @@ -1420,9 +1406,38 @@ public final class LatinIME extends InputMethodService implements KeyboardAction mEnteredText = null; } mConnection.endBatchEdit(); - if (ProductionFlag.IS_EXPERIMENTAL) { - ResearchLogger.latinIME_onCodeInput(primaryCode, x, y); + } + + private boolean handleNonSpecialCharacter(final int primaryCode, final int x, final int y, + final int spaceState) { + mSpaceState = SPACE_STATE_NONE; + final boolean didAutoCorrect; + if (mSettings.getCurrent().isWordSeparator(primaryCode)) { + didAutoCorrect = handleSeparator(primaryCode, x, y, spaceState); + } else { + didAutoCorrect = false; + if (SPACE_STATE_PHANTOM == spaceState) { + if (ProductionFlag.IS_INTERNAL) { + if (mWordComposer.isComposingWord() && mWordComposer.isBatchMode()) { + Stats.onAutoCorrection( + "", mWordComposer.getTypedWord(), " ", mWordComposer); + } + } + commitTyped(LastComposedWord.NOT_A_SEPARATOR); + } + final int keyX, keyY; + final Keyboard keyboard = mKeyboardSwitcher.getKeyboard(); + if (keyboard != null && keyboard.hasProximityCharsCorrection(primaryCode)) { + keyX = x; + keyY = y; + } else { + keyX = Constants.NOT_A_COORDINATE; + keyY = Constants.NOT_A_COORDINATE; + } + handleCharacter(primaryCode, keyX, keyY, spaceState); } + mExpectingUpdateSelection = true; + return didAutoCorrect; } // Called from PointerTracker through the KeyboardActionListener interface @@ -1479,12 +1494,7 @@ public final class LatinIME extends InputMethodService implements KeyboardAction mSpaceState = SPACE_STATE_PHANTOM; } else { final int codePointBeforeCursor = mConnection.getCodePointBeforeCursor(); - // TODO: reverse this logic. We should have the means to determine whether a character - // should usually be followed by a space, and it should be more readable. - if (Constants.NOT_A_CODE != codePointBeforeCursor - && !Character.isWhitespace(codePointBeforeCursor) - && !mSettings.getCurrent().isPhantomSpacePromotingSymbol(codePointBeforeCursor) - && !mSettings.getCurrent().isWeakSpaceStripper(codePointBeforeCursor)) { + if (mSettings.getCurrent().isUsuallyFollowedBySpace(codePointBeforeCursor)) { mSpaceState = SPACE_STATE_PHANTOM; } } @@ -1667,7 +1677,7 @@ public final class LatinIME extends InputMethodService implements KeyboardAction if (mWordComposer.isBatchMode()) { if (ProductionFlag.IS_EXPERIMENTAL) { final String word = mWordComposer.getTypedWord(); - ResearchLogger.latinIME_handleBackspace_batch(word); + ResearchLogger.latinIME_handleBackspace_batch(word, 1); ResearchLogger.getInstance().uncommitCurrentLogUnit( word, false /* dumpCurrentLogUnit */); } @@ -1718,14 +1728,17 @@ public final class LatinIME extends InputMethodService implements KeyboardAction // We should backspace one char and restart suggestion if at the end of a word. if (mLastSelectionStart != mLastSelectionEnd) { // If there is a selection, remove it. - final int lengthToDelete = mLastSelectionEnd - mLastSelectionStart; + final int numCharsDeleted = mLastSelectionEnd - mLastSelectionStart; mConnection.setSelection(mLastSelectionEnd, mLastSelectionEnd); // Reset mLastSelectionEnd to mLastSelectionStart. This is what is supposed to // happen, and if it's wrong, the next call to onUpdateSelection will correct it, // but we want to set it right away to avoid it being used with the wrong values // later (typically, in a subsequent press on backspace). mLastSelectionEnd = mLastSelectionStart; - mConnection.deleteSurroundingText(lengthToDelete, 0); + mConnection.deleteSurroundingText(numCharsDeleted, 0); + if (ProductionFlag.IS_EXPERIMENTAL) { + ResearchLogger.latinIME_handleBackspace(numCharsDeleted); + } } else { // There is no selection, just delete one character. if (NOT_A_CURSOR_POSITION == mLastSelectionEnd) { @@ -1742,8 +1755,14 @@ public final class LatinIME extends InputMethodService implements KeyboardAction } else { mConnection.deleteSurroundingText(1, 0); } + if (ProductionFlag.IS_EXPERIMENTAL) { + ResearchLogger.latinIME_handleBackspace(1); + } if (mDeleteCount > DELETE_ACCELERATE_AT) { mConnection.deleteSurroundingText(1, 0); + if (ProductionFlag.IS_EXPERIMENTAL) { + ResearchLogger.latinIME_handleBackspace(1); + } } } if (mSettings.getCurrent().isSuggestionsRequested(mDisplayOrientation)) { @@ -1752,25 +1771,22 @@ public final class LatinIME extends InputMethodService implements KeyboardAction } } + /* + * Strip a trailing space if necessary and returns whether it's a swap weak space situation. + */ private boolean maybeStripSpace(final int code, final int spaceState, final boolean isFromSuggestionStrip) { if (Constants.CODE_ENTER == code && SPACE_STATE_SWAP_PUNCTUATION == spaceState) { mConnection.removeTrailingSpace(); return false; - } else if ((SPACE_STATE_WEAK == spaceState - || SPACE_STATE_SWAP_PUNCTUATION == spaceState) + } + if ((SPACE_STATE_WEAK == spaceState || SPACE_STATE_SWAP_PUNCTUATION == spaceState) && isFromSuggestionStrip) { - if (mSettings.getCurrent().isWeakSpaceSwapper(code)) { - return true; - } else { - if (mSettings.getCurrent().isWeakSpaceStripper(code)) { - mConnection.removeTrailingSpace(); - } - return false; - } - } else { - return false; + if (mSettings.getCurrent().isUsuallyPrecededBySpace(code)) return false; + if (mSettings.getCurrent().isUsuallyFollowedBySpace(code)) return true; + mConnection.removeTrailingSpace(); } + return false; } private void handleCharacter(final int primaryCode, final int x, @@ -1778,7 +1794,7 @@ public final class LatinIME extends InputMethodService implements KeyboardAction boolean isComposingWord = mWordComposer.isComposingWord(); if (SPACE_STATE_PHANTOM == spaceState && - !mSettings.getCurrent().isSymbolExcludedFromWordSeparators(primaryCode)) { + !mSettings.getCurrent().isWordConnector(primaryCode)) { if (isComposingWord) { // Sanity check throw new RuntimeException("Should not be composing here"); @@ -1790,7 +1806,7 @@ public final class LatinIME extends InputMethodService implements KeyboardAction // dozen milliseconds. Avoid calling it as much as possible, since we are on the UI // thread here. if (!isComposingWord && (isAlphabet(primaryCode) - || mSettings.getCurrent().isSymbolExcludedFromWordSeparators(primaryCode)) + || mSettings.getCurrent().isWordConnector(primaryCode)) && mSettings.getCurrent().isSuggestionsRequested(mDisplayOrientation) && !mConnection.isCursorTouchingWord(mSettings.getCurrent())) { // Reset entirely the composing state anyway, then start composing a new word unless @@ -1844,7 +1860,7 @@ 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.IS_EXPERIMENTAL) { - ResearchLogger.latinIME_handleSeparator(); + ResearchLogger.recordTimeForLogUnitSplit(); } boolean didAutoCorrect = false; // Handle separator @@ -1862,7 +1878,7 @@ public final class LatinIME extends InputMethodService implements KeyboardAction Constants.SUGGESTION_STRIP_COORDINATE == x); if (SPACE_STATE_PHANTOM == spaceState && - mSettings.getCurrent().isPhantomSpacePromotingSymbol(primaryCode)) { + mSettings.getCurrent().isUsuallyPrecededBySpace(primaryCode)) { promotePhantomSpace(); } sendKeyCodePoint(primaryCode); @@ -1877,16 +1893,13 @@ public final class LatinIME extends InputMethodService implements KeyboardAction } mHandler.startDoubleSpacePeriodTimer(); - if (!mConnection.isCursorTouchingWord(mSettings.getCurrent())) { - mHandler.postUpdateSuggestionStrip(); - } + mHandler.postUpdateSuggestionStrip(); } else { if (swapWeakSpace) { swapSwapperAndSpace(); mSpaceState = SPACE_STATE_SWAP_PUNCTUATION; } else if (SPACE_STATE_PHANTOM == spaceState - && !mSettings.getCurrent().isWeakSpaceStripper(primaryCode) - && !mSettings.getCurrent().isPhantomSpacePromotingSymbol(primaryCode)) { + && mSettings.getCurrent().isUsuallyFollowedBySpace(primaryCode)) { // If we are in phantom space state, and the user presses a separator, we want to // stay in phantom space state so that the next keypress has a chance to add the // space. For example, if I type "Good dat", pick "day" from the suggestion strip @@ -2125,7 +2138,7 @@ public final class LatinIME extends InputMethodService implements KeyboardAction Constants.SUGGESTION_STRIP_COORDINATE, Constants.SUGGESTION_STRIP_COORDINATE); if (ProductionFlag.IS_EXPERIMENTAL) { ResearchLogger.latinIME_punctuationSuggestion(index, suggestion, - false /* isBatchMode */); + false /* isBatchMode */, suggestedWords.mIsPrediction); } return; } @@ -2135,9 +2148,9 @@ public final class LatinIME extends InputMethodService implements KeyboardAction // In the batch input mode, a manually picked suggested word should just replace // the current batch input text and there is no need for a phantom space. && !mWordComposer.isBatchMode()) { - int firstChar = Character.codePointAt(suggestion, 0); - if ((!mSettings.getCurrent().isWeakSpaceStripper(firstChar)) - && (!mSettings.getCurrent().isWeakSpaceSwapper(firstChar))) { + final int firstChar = Character.codePointAt(suggestion, 0); + if (!mSettings.getCurrent().isWordSeparator(firstChar) + || mSettings.getCurrent().isUsuallyPrecededBySpace(firstChar)) { promotePhantomSpace(); } } @@ -2312,7 +2325,7 @@ public final class LatinIME extends InputMethodService implements KeyboardAction } if (ProductionFlag.IS_EXPERIMENTAL) { ResearchLogger.latinIME_revertCommit(committedWord, originallyTypedWord, - mWordComposer.isBatchMode()); + mWordComposer.isBatchMode(), mLastComposedWord.mSeparatorString); ResearchLogger.getInstance().uncommitCurrentLogUnit(committedWord, true /* dumpCurrentLogUnit */); } @@ -2327,6 +2340,9 @@ public final class LatinIME extends InputMethodService implements KeyboardAction public void promotePhantomSpace() { if (mSettings.getCurrent().shouldInsertSpacesAutomatically()) { sendKeyCodePoint(Constants.CODE_SPACE); + if (ProductionFlag.IS_EXPERIMENTAL) { + ResearchLogger.latinIME_promotePhantomSpace(); + } } } diff --git a/java/src/com/android/inputmethod/latin/PositionalInfoForUserDictPendingAddition.java b/java/src/com/android/inputmethod/latin/PositionalInfoForUserDictPendingAddition.java index 1fd25636c..a33cefcd6 100644 --- a/java/src/com/android/inputmethod/latin/PositionalInfoForUserDictPendingAddition.java +++ b/java/src/com/android/inputmethod/latin/PositionalInfoForUserDictPendingAddition.java @@ -33,13 +33,15 @@ public final class PositionalInfoForUserDictPendingAddition { final private String mOriginalWord; final private int mCursorPos; // Position of the cursor after the word final private EditorInfo mEditorInfo; // On what binding this has been added + final private int mCapitalizedMode; private String mActualWordBeingAdded; public PositionalInfoForUserDictPendingAddition(final String word, final int cursorPos, - final EditorInfo editorInfo) { + final EditorInfo editorInfo, final int capitalizedMode) { mOriginalWord = word; mCursorPos = cursorPos; mEditorInfo = editorInfo; + mCapitalizedMode = capitalizedMode; } public void setActualWordBeingAdded(final String actualWordBeingAdded) { diff --git a/java/src/com/android/inputmethod/latin/RichInputConnection.java b/java/src/com/android/inputmethod/latin/RichInputConnection.java index f7268fc33..0e75533f5 100644 --- a/java/src/com/android/inputmethod/latin/RichInputConnection.java +++ b/java/src/com/android/inputmethod/latin/RichInputConnection.java @@ -577,11 +577,11 @@ public final class RichInputConnection { final CharSequence before = getTextBeforeCursor(1, 0); final CharSequence after = getTextAfterCursor(1, 0); if (!TextUtils.isEmpty(before) && !settingsValues.isWordSeparator(before.charAt(0)) - && !settingsValues.isSymbolExcludedFromWordSeparators(before.charAt(0))) { + && !settingsValues.isWordConnector(before.charAt(0))) { return true; } if (!TextUtils.isEmpty(after) && !settingsValues.isWordSeparator(after.charAt(0)) - && !settingsValues.isSymbolExcludedFromWordSeparators(after.charAt(0))) { + && !settingsValues.isWordConnector(after.charAt(0))) { return true; } return false; @@ -633,12 +633,9 @@ public final class RichInputConnection { final char firstChar = word.charAt(0); // we just tested that word is not empty if (word.length() == 1 && !Character.isLetter(firstChar)) return null; - // We only suggest on words that start with a letter or a symbol that is excluded from - // word separators (see #handleCharacterWhileInBatchEdit). - if (!(Character.isLetter(firstChar) - || settings.isSymbolExcludedFromWordSeparators(firstChar))) { - return null; - } + // We don't restart suggestion if the first character is not a letter, because we don't + // start composing when the first character is not a letter. + if (!Character.isLetter(firstChar)) return null; return word; } diff --git a/java/src/com/android/inputmethod/latin/SeekBarDialog.java b/java/src/com/android/inputmethod/latin/SeekBarDialog.java index e576c0984..c736d1b1a 100644 --- a/java/src/com/android/inputmethod/latin/SeekBarDialog.java +++ b/java/src/com/android/inputmethod/latin/SeekBarDialog.java @@ -30,6 +30,8 @@ public final class SeekBarDialog implements DialogInterface.OnClickListener, public interface Listener { public void onPositiveButtonClick(final SeekBarDialog dialog); public void onNegativeButtonClick(final SeekBarDialog dialog); + public void onNeutralButtonClick(final SeekBarDialog dialog); + public void onDismiss(final SeekBarDialog dialog); public void onProgressChanged(final SeekBarDialog dialog); public void onStartTrackingTouch(final SeekBarDialog dialog); public void onStopTrackingTouch(final SeekBarDialog dialog); @@ -39,7 +41,11 @@ public final class SeekBarDialog implements DialogInterface.OnClickListener, @Override public void onPositiveButtonClick(final SeekBarDialog dialog) {} @Override - public void onNegativeButtonClick(final SeekBarDialog dialog) { dialog.dismiss(); } + public void onNegativeButtonClick(final SeekBarDialog dialog) {} + @Override + public void onNeutralButtonClick(final SeekBarDialog dialog) {} + @Override + public void onDismiss(final SeekBarDialog dialog) {} @Override public void onProgressChanged(final SeekBarDialog dialog) {} @Override @@ -63,6 +69,9 @@ public final class SeekBarDialog implements DialogInterface.OnClickListener, dialogBuilder.setView(builder.mView); dialogBuilder.setPositiveButton(android.R.string.ok, this); dialogBuilder.setNegativeButton(android.R.string.cancel, this); + if (builder.mNeutralButtonTextResId != 0) { + dialogBuilder.setNeutralButton(builder.mNeutralButtonTextResId, this); + } mDialog = dialogBuilder.create(); mListener = (builder.mListener == null) ? EMPTY_ADAPTER : builder.mListener; mValueView = (TextView)builder.mView.findViewById(R.id.seek_bar_dialog_value); @@ -101,15 +110,21 @@ public final class SeekBarDialog implements DialogInterface.OnClickListener, } @Override - public void onClick(final DialogInterface dialog, int which) { - if (which == DialogInterface.BUTTON_POSITIVE) { + public void onClick(final DialogInterface dialog, final int which) { + switch (which) { + case DialogInterface.BUTTON_POSITIVE: mListener.onPositiveButtonClick(this); - return; - } - if (which == DialogInterface.BUTTON_NEGATIVE) { + break; + case DialogInterface.BUTTON_NEGATIVE: mListener.onNegativeButtonClick(this); + break; + case DialogInterface.BUTTON_NEUTRAL: + mListener.onNeutralButtonClick(this); + break; + default: return; } + mListener.onDismiss(this); } @Override @@ -135,6 +150,7 @@ public final class SeekBarDialog implements DialogInterface.OnClickListener, final AlertDialog.Builder mDialogBuilder; final View mView; + int mNeutralButtonTextResId; int mMaxValue; int mValueFormatResId; int mValue; @@ -150,8 +166,14 @@ public final class SeekBarDialog implements DialogInterface.OnClickListener, return this; } + public Builder setNeutralButtonText(final int resId) { + mNeutralButtonTextResId = resId; + return this; + } + public Builder setMaxValue(final int max) { mMaxValue = max; + mValue = Math.min(mValue, max); return this; } @@ -161,7 +183,7 @@ public final class SeekBarDialog implements DialogInterface.OnClickListener, } public Builder setValue(final int value) { - mValue = value; + mValue = Math.min(value, mMaxValue); return this; } diff --git a/java/src/com/android/inputmethod/latin/Settings.java b/java/src/com/android/inputmethod/latin/Settings.java index c5930a935..866bef0f2 100644 --- a/java/src/com/android/inputmethod/latin/Settings.java +++ b/java/src/com/android/inputmethod/latin/Settings.java @@ -167,19 +167,21 @@ public final class Settings implements SharedPreferences.OnSharedPreferenceChang public static float readKeypressSoundVolume(final SharedPreferences prefs, final Resources res) { final float volume = prefs.getFloat(PREF_KEYPRESS_SOUND_VOLUME, -1.0f); - if (volume >= 0) { - return volume; - } + return (volume >= 0) ? volume : readDefaultKeypressSoundVolume(res); + } + + public static float readDefaultKeypressSoundVolume(final Resources res) { return Float.parseFloat( ResourceUtils.getDeviceOverrideValue(res, R.array.keypress_volumes)); } - public static int readVibrationDuration(final SharedPreferences prefs, + public static int readKeypressVibrationDuration(final SharedPreferences prefs, final Resources res) { final int ms = prefs.getInt(PREF_VIBRATION_DURATION_SETTINGS, -1); - if (ms >= 0) { - return ms; - } + return (ms >= 0) ? ms : readDefaultKeypressVibrationDuration(res); + } + + public static int readDefaultKeypressVibrationDuration(final Resources res) { return Integer.parseInt( ResourceUtils.getDeviceOverrideValue(res, R.array.keypress_vibration_durations)); } diff --git a/java/src/com/android/inputmethod/latin/SettingsFragment.java b/java/src/com/android/inputmethod/latin/SettingsFragment.java index 507a37b7c..6a4371835 100644 --- a/java/src/com/android/inputmethod/latin/SettingsFragment.java +++ b/java/src/com/android/inputmethod/latin/SettingsFragment.java @@ -180,7 +180,7 @@ public final class SettingsFragment extends InputMethodSettingsFragment }); mKeypressVibrationDurationSettingsPref.setSummary( res.getString(R.string.settings_keypress_vibration_duration, - Settings.readVibrationDuration(prefs, res))); + Settings.readKeypressVibrationDuration(prefs, res))); } mKeypressSoundVolumeSettingsPref = @@ -308,10 +308,29 @@ public final class SettingsFragment extends InputMethodSettingsFragment final Context context = getActivity(); final PreferenceScreen settingsPref = mKeypressVibrationDurationSettingsPref; final SeekBarDialog.Listener listener = new SeekBarDialog.Adapter() { + private void writePreference(final SharedPreferences sp, final int value) { + sp.edit().putInt(Settings.PREF_VIBRATION_DURATION_SETTINGS, value).apply(); + } + + private void feedbackSettingsValue(final int value) { + AudioAndHapticFeedbackManager.getInstance().vibrate(value); + } + @Override public void onPositiveButtonClick(final SeekBarDialog dialog) { - final int ms = dialog.getValue(); - sp.edit().putInt(Settings.PREF_VIBRATION_DURATION_SETTINGS, ms).apply(); + writePreference(sp, dialog.getValue()); + } + + @Override + public void onNeutralButtonClick(final SeekBarDialog dialog) { + final int defaultValue = + Settings.readDefaultKeypressVibrationDuration(context.getResources()); + dialog.setValue(defaultValue, false /* fromUser */); + writePreference(sp, defaultValue); + } + + @Override + public void onDismiss(final SeekBarDialog dialog) { if (settingsPref != null) { settingsPref.setSummary(dialog.getValueText()); } @@ -319,13 +338,13 @@ public final class SettingsFragment extends InputMethodSettingsFragment @Override public void onStopTrackingTouch(final SeekBarDialog dialog) { - final int ms = dialog.getValue(); - AudioAndHapticFeedbackManager.getInstance().vibrate(ms); + feedbackSettingsValue(dialog.getValue()); } }; - final int currentMs = Settings.readVibrationDuration(sp, getResources()); + final int currentMs = Settings.readKeypressVibrationDuration(sp, getResources()); final SeekBarDialog.Builder builder = new SeekBarDialog.Builder(context); builder.setTitle(R.string.prefs_keypress_vibration_duration_settings) + .setNeutralButtonText(R.string.button_default) .setListener(listener) .setMaxValue(AudioAndHapticFeedbackManager.MAX_KEYPRESS_VIBRATION_DURATION) .setValueFromat(R.string.settings_keypress_vibration_duration) @@ -348,10 +367,29 @@ public final class SettingsFragment extends InputMethodSettingsFragment final AudioManager am = (AudioManager) context.getSystemService(Context.AUDIO_SERVICE); final PreferenceScreen settingsPref = mKeypressSoundVolumeSettingsPref; final SeekBarDialog.Listener listener = new SeekBarDialog.Adapter() { + private void writePreference(final SharedPreferences sp, final float value) { + sp.edit().putFloat(Settings.PREF_KEYPRESS_SOUND_VOLUME, value).apply(); + } + + private void feedbackSettingsValue(final float value) { + am.playSoundEffect(AudioManager.FX_KEYPRESS_STANDARD, value); + } + @Override public void onPositiveButtonClick(final SeekBarDialog dialog) { - final float volume = dialog.getValue() / PERCENT_FLOAT; - sp.edit().putFloat(Settings.PREF_KEYPRESS_SOUND_VOLUME, volume).apply(); + writePreference(sp, dialog.getValue() / PERCENT_FLOAT); + } + + @Override + public void onNeutralButtonClick(final SeekBarDialog dialog) { + final float defaultValue = + Settings.readDefaultKeypressSoundVolume(context.getResources()); + dialog.setValue((int)(defaultValue * PERCENT_INT), false /* fromUser */); + writePreference(sp, defaultValue); + } + + @Override + public void onDismiss(final SeekBarDialog dialog) { if (settingsPref != null) { settingsPref.setSummary(dialog.getValueText()); } @@ -359,13 +397,13 @@ public final class SettingsFragment extends InputMethodSettingsFragment @Override public void onStopTrackingTouch(final SeekBarDialog dialog) { - final float volume = dialog.getValue() / PERCENT_FLOAT; - am.playSoundEffect(AudioManager.FX_KEYPRESS_STANDARD, volume); + feedbackSettingsValue(dialog.getValue() / PERCENT_FLOAT); } }; final SeekBarDialog.Builder builder = new SeekBarDialog.Builder(context); final int currentVolumeInt = getCurrentKeyPressSoundVolumePercent(sp, getResources()); builder.setTitle(R.string.prefs_keypress_sound_volume_settings) + .setNeutralButtonText(R.string.button_default) .setListener(listener) .setMaxValue(PERCENT_INT) .setValue(currentVolumeInt) diff --git a/java/src/com/android/inputmethod/latin/SettingsValues.java b/java/src/com/android/inputmethod/latin/SettingsValues.java index 9a2024618..1e3bdf0fd 100644 --- a/java/src/com/android/inputmethod/latin/SettingsValues.java +++ b/java/src/com/android/inputmethod/latin/SettingsValues.java @@ -37,11 +37,10 @@ public final class SettingsValues { // From resources: public final int mDelayUpdateOldSuggestions; - public final String mWeakSpaceStrippers; - public final String mWeakSpaceSwappers; - private final String mPhantomSpacePromotingSymbols; + public final int[] mSymbolsPrecededBySpace; + public final int[] mSymbolsFollowedBySpace; + public final int[] mWordConnectors; public final SuggestedWords mSuggestPuncList; - private final String mSymbolsExcludedFromWordSeparators; public final String mWordSeparators; public final CharSequence mHintToSaveText; @@ -79,25 +78,19 @@ public final class SettingsValues { final InputAttributes inputAttributes) { // Get the resources mDelayUpdateOldSuggestions = res.getInteger(R.integer.config_delay_update_old_suggestions); - mWeakSpaceStrippers = res.getString(R.string.weak_space_stripping_symbols); - mWeakSpaceSwappers = res.getString(R.string.weak_space_swapping_symbols); - mPhantomSpacePromotingSymbols = res.getString(R.string.phantom_space_promoting_symbols); - if (LatinImeLogger.sDBG) { - final int length = mWeakSpaceStrippers.length(); - for (int i = 0; i < length; i = mWeakSpaceStrippers.offsetByCodePoints(i, 1)) { - if (isWeakSpaceSwapper(mWeakSpaceStrippers.codePointAt(i))) { - throw new RuntimeException("Char code " + mWeakSpaceStrippers.codePointAt(i) - + " is both a weak space swapper and stripper."); - } - } - } + mSymbolsPrecededBySpace = + StringUtils.toCodePointArray(res.getString(R.string.symbols_preceded_by_space)); + Arrays.sort(mSymbolsPrecededBySpace); + mSymbolsFollowedBySpace = + StringUtils.toCodePointArray(res.getString(R.string.symbols_followed_by_space)); + Arrays.sort(mSymbolsFollowedBySpace); + mWordConnectors = + StringUtils.toCodePointArray(res.getString(R.string.symbols_word_connectors)); + Arrays.sort(mWordConnectors); final String[] suggestPuncsSpec = KeySpecParser.parseCsvString( res.getString(R.string.suggested_punctuations), null); mSuggestPuncList = createSuggestPuncList(suggestPuncsSpec); - mSymbolsExcludedFromWordSeparators = - res.getString(R.string.symbols_excluded_from_word_separators); - mWordSeparators = createWordSeparators(mWeakSpaceStrippers, mWeakSpaceSwappers, - mSymbolsExcludedFromWordSeparators, res); + mWordSeparators = res.getString(R.string.symbols_word_separators); mHintToSaveText = res.getText(R.string.hint_add_to_dictionary); // Store the input attributes @@ -128,7 +121,7 @@ public final class SettingsValues { mBigramPredictionEnabled = readBigramPredictionEnabled(prefs, res); // Compute other readable settings - mKeypressVibrationDuration = Settings.readVibrationDuration(prefs, res); + mKeypressVibrationDuration = Settings.readKeypressVibrationDuration(prefs, res); mKeypressSoundVolume = Settings.readKeypressSoundVolume(prefs, res); mKeyPreviewPopupDismissDelay = Settings.readKeyPreviewPopupDismissDelay(prefs, res); mAutoCorrectionThreshold = readAutoCorrectionThreshold(res, @@ -169,25 +162,16 @@ public final class SettingsValues { return mWordSeparators.contains(String.valueOf((char)code)); } - public boolean isSymbolExcludedFromWordSeparators(final int code) { - return mSymbolsExcludedFromWordSeparators.contains(String.valueOf((char)code)); - } - - // TODO: use "Phantom" instead of "Weak" in this method name - public boolean isWeakSpaceStripper(final int code) { - // TODO: this does not work if the code does not fit in a char - return mWeakSpaceStrippers.contains(String.valueOf((char)code)); + public boolean isWordConnector(final int code) { + return Arrays.binarySearch(mWordConnectors, code) >= 0; } - // TODO: use "Phantom" instead of "Weak" in this method name - public boolean isWeakSpaceSwapper(final int code) { - // TODO: this does not work if the code does not fit in a char - return mWeakSpaceSwappers.contains(String.valueOf((char)code)); + public boolean isUsuallyPrecededBySpace(final int code) { + return Arrays.binarySearch(mSymbolsPrecededBySpace, code) >= 0; } - public boolean isPhantomSpacePromotingSymbol(final int code) { - // TODO: this does not work if the code does not fit in a char - return mPhantomSpacePromotingSymbols.contains(String.valueOf((char)code)); + public boolean isUsuallyFollowedBySpace(final int code) { + return Arrays.binarySearch(mSymbolsFollowedBySpace, code) >= 0; } public boolean shouldInsertSpacesAutomatically() { @@ -239,18 +223,6 @@ public final class SettingsValues { false /* isPrediction */); } - private static String createWordSeparators(final String weakSpaceStrippers, - final String weakSpaceSwappers, final String symbolsExcludedFromWordSeparators, - final Resources res) { - String wordSeparators = weakSpaceStrippers + weakSpaceSwappers - + res.getString(R.string.phantom_space_promoting_symbols); - for (int i = symbolsExcludedFromWordSeparators.length() - 1; i >= 0; --i) { - wordSeparators = wordSeparators.replace( - symbolsExcludedFromWordSeparators.substring(i, i + 1), ""); - } - return wordSeparators; - } - private static final int SUGGESTION_VISIBILITY_SHOW_VALUE = R.string.prefs_suggestion_visibility_show_value; private static final int SUGGESTION_VISIBILITY_SHOW_ONLY_PORTRAIT_VALUE = diff --git a/java/src/com/android/inputmethod/latin/Suggest.java b/java/src/com/android/inputmethod/latin/Suggest.java index 3dc2ba95b..2abf75da4 100644 --- a/java/src/com/android/inputmethod/latin/Suggest.java +++ b/java/src/com/android/inputmethod/latin/Suggest.java @@ -72,9 +72,8 @@ public final class Suggest { } @UsedForTesting - Suggest(final Context context, final File dictionary, - final long startOffset, final long length, final Locale locale) { - final Dictionary mainDict = DictionaryFactory.createDictionaryForTest(context, dictionary, + Suggest(final File dictionary, final long startOffset, final long length, final Locale locale) { + final Dictionary mainDict = DictionaryFactory.createDictionaryForTest(dictionary, startOffset, length /* useFullEditDistance */, false, locale); mLocale = locale; mMainDictionary = mainDict; diff --git a/java/src/com/android/inputmethod/latin/WordComposer.java b/java/src/com/android/inputmethod/latin/WordComposer.java index 4f1759079..b9ec4979d 100644 --- a/java/src/com/android/inputmethod/latin/WordComposer.java +++ b/java/src/com/android/inputmethod/latin/WordComposer.java @@ -350,7 +350,7 @@ public final class WordComposer { mPrimaryKeyCodes = new int[MAX_WORD_LENGTH]; final LastComposedWord lastComposedWord = new LastComposedWord(primaryKeyCodes, mInputPointers, mTypedWord.toString(), committedWord, separatorString, - prevWord); + prevWord, mCapitalizedMode); mInputPointers.reset(); if (type != LastComposedWord.COMMIT_TYPE_DECIDED_WORD && type != LastComposedWord.COMMIT_TYPE_MANUAL_PICK) { @@ -374,6 +374,7 @@ public final class WordComposer { mTypedWord.setLength(0); mTypedWord.append(lastComposedWord.mTypedWord); refreshSize(); + mCapitalizedMode = lastComposedWord.mCapitalizedMode; mAutoCorrection = null; // This will be filled by the next call to updateSuggestion. mIsResumed = true; } diff --git a/java/src/com/android/inputmethod/latin/spellcheck/AndroidSpellCheckerService.java b/java/src/com/android/inputmethod/latin/spellcheck/AndroidSpellCheckerService.java index 89d6c9010..907c0cdca 100644 --- a/java/src/com/android/inputmethod/latin/spellcheck/AndroidSpellCheckerService.java +++ b/java/src/com/android/inputmethod/latin/spellcheck/AndroidSpellCheckerService.java @@ -81,6 +81,7 @@ public final class AndroidSpellCheckerService extends SpellCheckerService public static final int SCRIPT_LATIN = 0; public static final int SCRIPT_CYRILLIC = 1; + public static final int SCRIPT_GREEK = 2; public static final String SINGLE_QUOTE = "\u0027"; public static final String APOSTROPHE = "\u2019"; private static final TreeMap<String, Integer> mLanguageToScript; @@ -94,18 +95,23 @@ public final class AndroidSpellCheckerService extends SpellCheckerService // IMPORTANT: this only contains languages - do not write countries in there. // Only the language is searched from the map. mLanguageToScript = CollectionUtils.newTreeMap(); - mLanguageToScript.put("en", SCRIPT_LATIN); - mLanguageToScript.put("fr", SCRIPT_LATIN); - mLanguageToScript.put("de", SCRIPT_LATIN); - mLanguageToScript.put("nl", SCRIPT_LATIN); mLanguageToScript.put("cs", SCRIPT_LATIN); + mLanguageToScript.put("da", SCRIPT_LATIN); + mLanguageToScript.put("de", SCRIPT_LATIN); + mLanguageToScript.put("el", SCRIPT_GREEK); + mLanguageToScript.put("en", SCRIPT_LATIN); mLanguageToScript.put("es", SCRIPT_LATIN); - mLanguageToScript.put("it", SCRIPT_LATIN); + mLanguageToScript.put("fi", SCRIPT_LATIN); + mLanguageToScript.put("fr", SCRIPT_LATIN); mLanguageToScript.put("hr", SCRIPT_LATIN); + mLanguageToScript.put("it", SCRIPT_LATIN); + mLanguageToScript.put("lt", SCRIPT_LATIN); + mLanguageToScript.put("lv", SCRIPT_LATIN); + mLanguageToScript.put("nb", SCRIPT_LATIN); + mLanguageToScript.put("nl", SCRIPT_LATIN); mLanguageToScript.put("pt", SCRIPT_LATIN); + mLanguageToScript.put("sl", SCRIPT_LATIN); mLanguageToScript.put("ru", SCRIPT_CYRILLIC); - // TODO: Make a persian proximity, and activate the Farsi subtype. - // mLanguageToScript.put("fa", SCRIPT_PERSIAN); } @Override public void onCreate() { diff --git a/java/src/com/android/inputmethod/latin/spellcheck/AndroidWordLevelSpellCheckerSession.java b/java/src/com/android/inputmethod/latin/spellcheck/AndroidWordLevelSpellCheckerSession.java index 470943be1..6581978c9 100644 --- a/java/src/com/android/inputmethod/latin/spellcheck/AndroidWordLevelSpellCheckerSession.java +++ b/java/src/com/android/inputmethod/latin/spellcheck/AndroidWordLevelSpellCheckerSession.java @@ -143,8 +143,17 @@ public abstract class AndroidWordLevelSpellCheckerSession extends Session { case AndroidSpellCheckerService.SCRIPT_CYRILLIC: // All Cyrillic characters are in the 400~52F block. There are some in the upper // Unicode range, but they are archaic characters that are not used in modern - // russian and are not used by our dictionary. + // Russian and are not used by our dictionary. return codePoint >= 0x400 && codePoint <= 0x52F && Character.isLetter(codePoint); + case AndroidSpellCheckerService.SCRIPT_GREEK: + // 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 + // they are probably okay. + return (codePoint >= 0x370 && codePoint <= 0x3FF) + || (codePoint >= 0x1F00 && codePoint <= 0x1FFF) + || codePoint == 0xF2; default: // Should never come here throw new RuntimeException("Impossible value of script: " + script); diff --git a/java/src/com/android/inputmethod/latin/spellcheck/SpellCheckerProximityInfo.java b/java/src/com/android/inputmethod/latin/spellcheck/SpellCheckerProximityInfo.java index 6c0d79c2b..572a826a5 100644 --- a/java/src/com/android/inputmethod/latin/spellcheck/SpellCheckerProximityInfo.java +++ b/java/src/com/android/inputmethod/latin/spellcheck/SpellCheckerProximityInfo.java @@ -73,6 +73,12 @@ public final class SpellCheckerProximityInfo { // to spell check has been entered with one of the keyboards above. Also, specifically // to English, many spelling errors consist of the last vowel of the word being wrong // because in English vowels tend to merge with each other in pronunciation. + /* + The Qwerty layout this represents looks like the following: + q w e r t y u i o p + a s d f g h j k l + z x c v b n m + */ final static int[] PROXIMITY = { // Proximity for row 1. This must have exactly ROW_SIZE entries for each letter, // and exactly PROXIMITY_GRID_WIDTH letters for a row. Pad with NUL's. @@ -127,10 +133,13 @@ public final class SpellCheckerProximityInfo { final private static TreeMap<Integer, Integer> INDICES = CollectionUtils.newTreeMap(); // TODO: The following table is solely based on the keyboard layout. Consult with Russian // speakers on commonly misspelled words/letters. - final static int[] PROXIMITY = { - // Proximity for row 1. This must have exactly ROW_SIZE entries for each letter, - // and exactly PROXIMITY_GRID_WIDTH letters for a row. Pad with NUL's. - // The number of rows must be exactly PROXIMITY_GRID_HEIGHT. + /* + The Russian layout this represents looks like the following: + й ц у к е н г ш щ з х + ф ы в а п р о л д ж э + я ч с м и т ь б ю + + This gives us the following table: 'й', 'ц', 'ф', 'ы', NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, 'ц', 'й', 'ф', 'ы', 'в', 'у', NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, 'у', 'ц', 'ы', 'в', 'а', 'к', NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, @@ -143,7 +152,6 @@ public final class SpellCheckerProximityInfo { 'з', 'щ', 'д', 'ж', 'э', 'х', NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, 'х', 'з', 'ж', 'э', NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, - // Proximity for row 2. See comment above about size. 'ф', 'й', 'ц', 'ы', 'я', NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, 'ы', 'й', 'ц', 'у', 'ф', 'в', 'я', 'ч', NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, 'в', 'ц', 'у', 'к', 'ы', 'а', 'я', 'ч', 'с', NUL, NUL, NUL, NUL, NUL, NUL, NUL, @@ -156,7 +164,6 @@ public final class SpellCheckerProximityInfo { 'ж', 'щ', 'з', 'х', 'д', 'э', 'б', 'ю', NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, 'э', 'з', 'х', 'ю', NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, - // Proximity for row 3. See comment above about size. 'я', 'ф', 'ы', 'в', 'ч', NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, 'ч', 'ы', 'в', 'а', 'я', 'с', NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, 'с', 'в', 'а', 'п', 'ч', 'м', NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, @@ -166,6 +173,249 @@ public final class SpellCheckerProximityInfo { 'ь', 'о', 'л', 'д', 'т', 'б', NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, 'б', 'л', 'д', 'ж', 'ь', 'ю', NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, 'ю', 'д', 'ж', 'э', 'б', NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, + + Using the following characters: + */ + private static final int CY_SHORT_I = '\u0439'; // й + private static final int CY_TSE = '\u0446'; // ц + private static final int CY_U = '\u0443'; // у + private static final int CY_KA = '\u043A'; // к + private static final int CY_IE = '\u0435'; // е + private static final int CY_EN = '\u043D'; // н + private static final int CY_GHE = '\u0433'; // г + private static final int CY_SHA = '\u0448'; // ш + private static final int CY_SHCHA = '\u0449'; // щ + private static final int CY_ZE = '\u0437'; // з + private static final int CY_HA = '\u0445'; // х + private static final int CY_EF = '\u0444'; // ф + private static final int CY_YERU = '\u044B'; // ы + private static final int CY_VE = '\u0432'; // в + private static final int CY_A = '\u0430'; // а + private static final int CY_PE = '\u043F'; // п + private static final int CY_ER = '\u0440'; // р + private static final int CY_O = '\u043E'; // о + private static final int CY_EL = '\u043B'; // л + private static final int CY_DE = '\u0434'; // д + private static final int CY_ZHE = '\u0436'; // ж + private static final int CY_E = '\u044D'; // э + private static final int CY_YA = '\u044F'; // я + private static final int CY_CHE = '\u0447'; // ч + private static final int CY_ES = '\u0441'; // с + private static final int CY_EM = '\u043C'; // м + private static final int CY_I = '\u0438'; // и + private static final int CY_TE = '\u0442'; // т + private static final int CY_SOFT_SIGN = '\u044C'; // ь + private static final int CY_BE = '\u0431'; // б + private static final int CY_YU = '\u044E'; // ю + final static int[] PROXIMITY = { + // Proximity for row 1. This must have exactly ROW_SIZE entries for each letter, + // and exactly PROXIMITY_GRID_WIDTH letters for a row. Pad with NUL's. + // The number of rows must be exactly PROXIMITY_GRID_HEIGHT. + CY_SHORT_I, CY_TSE, CY_EF, CY_YERU, NUL, NUL, NUL, NUL, + NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, + CY_TSE, CY_SHORT_I, CY_EF, CY_YERU, CY_VE, CY_U, NUL, NUL, + NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, + CY_U, CY_TSE, CY_YERU, CY_VE, CY_A, CY_KA, NUL, NUL, + NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, + CY_KA, CY_U, CY_VE, CY_A, CY_PE, CY_IE, NUL, NUL, + NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, + CY_IE, CY_KA, CY_A, CY_PE, CY_ER, CY_EN, NUL, NUL, + NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, + CY_EN, CY_IE, CY_PE, CY_ER, CY_O, CY_GHE, NUL, NUL, + NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, + CY_GHE, CY_EN, CY_ER, CY_O, CY_EL, CY_SHA, NUL, NUL, + NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, + CY_SHA, CY_GHE, CY_O, CY_EL, CY_DE, CY_SHCHA, NUL, NUL, + NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, + CY_SHCHA, CY_SHA, CY_EL, CY_DE, CY_ZHE, CY_ZE, NUL, NUL, + NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, + CY_ZE, CY_SHCHA, CY_DE, CY_ZHE, CY_E, CY_HA, NUL, NUL, + NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, + CY_HA, CY_ZE, CY_ZHE, CY_E, NUL, NUL, NUL, NUL, + NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, + + // Proximity for row 2. See comment above about size. + CY_EF, CY_SHORT_I, CY_TSE, CY_YERU, CY_YA, NUL, NUL, NUL, + NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, + CY_YERU, CY_SHORT_I, CY_TSE, CY_U, CY_EF, CY_VE, CY_YA, CY_CHE, + NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, + CY_VE, CY_TSE, CY_U, CY_KA, CY_YERU, CY_A, CY_YA, CY_CHE, + CY_ES, NUL, NUL, NUL, NUL, NUL, NUL, NUL, + CY_A, CY_U, CY_KA, CY_IE, CY_VE, CY_PE, CY_CHE, CY_ES, + CY_EM, NUL, NUL, NUL, NUL, NUL, NUL, NUL, + CY_PE, CY_KA, CY_IE, CY_EN, CY_A, CY_ER, CY_ES, CY_EM, + CY_I, NUL, NUL, NUL, NUL, NUL, NUL, NUL, + CY_ER, CY_IE, CY_EN, CY_GHE, CY_PE, CY_O, CY_EM, CY_I, + CY_TE, NUL, NUL, NUL, NUL, NUL, NUL, NUL, + CY_O, CY_EN, CY_GHE, CY_SHA, CY_ER, CY_EL, CY_I, CY_TE, + CY_SOFT_SIGN, NUL, NUL, NUL, NUL, NUL, NUL, NUL, + CY_EL, CY_GHE, CY_SHA, CY_SHCHA, CY_O, CY_DE, CY_TE, CY_SOFT_SIGN, + CY_BE, NUL, NUL, NUL, NUL, NUL, NUL, NUL, + CY_DE, CY_SHA, CY_SHCHA, CY_ZE, CY_EL, CY_ZHE, CY_SOFT_SIGN, CY_BE, + CY_YU, NUL, NUL, NUL, NUL, NUL, NUL, NUL, + CY_ZHE, CY_SHCHA, CY_ZE, CY_HA, CY_DE, CY_E, CY_BE, CY_YU, + NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, + CY_E, CY_ZE, CY_HA, CY_YU, NUL, NUL, NUL, NUL, + NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, + + // Proximity for row 3. See comment above about size. + CY_YA, CY_EF, CY_YERU, CY_VE, CY_CHE, NUL, NUL, NUL, + NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, + CY_CHE, CY_YERU, CY_VE, CY_A, CY_YA, CY_ES, NUL, NUL, + NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, + CY_ES, CY_VE, CY_A, CY_PE, CY_CHE, CY_EM, NUL, NUL, + NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, + CY_EM, CY_A, CY_PE, CY_ER, CY_ES, CY_I, NUL, NUL, + NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, + CY_I, CY_PE, CY_ER, CY_O, CY_EM, CY_TE, NUL, NUL, + NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, + CY_TE, CY_ER, CY_O, CY_EL, CY_I, CY_SOFT_SIGN, NUL, NUL, + NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, + CY_SOFT_SIGN, CY_O, CY_EL, CY_DE, CY_TE, CY_BE, NUL, NUL, + NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, + CY_BE, CY_EL, CY_DE, CY_ZHE, CY_SOFT_SIGN, CY_YU, NUL, NUL, + NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, + CY_YU, CY_DE, CY_ZHE, CY_E, CY_BE, NUL, NUL, NUL, + NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, + NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, + NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, + }; + static { + buildProximityIndices(PROXIMITY, INDICES); + } + static int getIndexOf(int characterCode) { + return computeIndex(characterCode, INDICES); + } + } + + private static final class Greek { + final private static TreeMap<Integer, Integer> INDICES = CollectionUtils.newTreeMap(); + // TODO: The following table is solely based on the keyboard layout. Consult with Greek + // speakers on commonly misspelled words/letters. + /* + The Greek layout this represents looks like the following: + ; ς ε ρ τ υ θ ι ο π + α σ δ φ γ η ξ κ λ + ζ χ ψ ω β ν μ + + This gives us the following table: + 'ς', 'ε', 'α', 'σ', NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, + 'ε', 'ς', 'ρ', 'σ', 'δ', NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, + 'ρ', 'ε', 'τ', 'δ', 'φ', NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, + 'τ', 'ρ', 'υ', 'φ', 'γ', NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, + 'υ', 'τ', 'θ', 'γ', 'η', NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, + 'θ', 'υ', 'ι', 'η', 'ξ', NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, + 'ι', 'θ', 'ο', 'ξ', 'κ', NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, + 'ο', 'ι', 'π', 'κ', 'λ', NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, + 'π', 'ο', 'λ', NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, + + 'α', 'ς', 'σ', 'ζ', NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, + 'σ', 'ς', 'ε', 'α', 'δ', 'ζ', 'χ', NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, + 'δ', 'ε', 'ρ', 'σ', 'φ', 'ζ', 'χ', 'ψ', NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, + 'φ', 'ρ', 'τ', 'δ', 'γ', 'χ', 'ψ', 'ω', NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, + 'γ', 'τ', 'υ', 'φ', 'η', 'ψ', 'ω', 'β', NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, + 'η', 'υ', 'θ', 'γ', 'ξ', 'ω', 'β', 'ν', NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, + 'ξ', 'θ', 'ι', 'η', 'κ', 'β', 'ν', 'μ', NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, + 'κ', 'ι', 'ο', 'ξ', 'λ', 'ν', 'μ', NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, + 'λ', 'ο', 'π', 'κ', 'μ', NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, + + 'ζ', 'α', 'σ', 'δ', 'χ', NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, + 'χ', 'σ', 'δ', 'φ', 'ζ', 'ψ', NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, + 'ψ', 'δ', 'φ', 'γ', 'χ', 'ω', NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, + 'ω', 'φ', 'γ', 'η', 'ψ', 'β', NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, + 'β', 'γ', 'η', 'ξ', 'ω', 'ν', NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, + 'ν', 'η', 'ξ', 'κ', 'β', 'μ', NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, + 'μ', 'ξ', 'κ', 'λ', 'ν', NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, + + Using the following characters: + */ + private static final int GR_FINAL_SIGMA = '\u03C2'; // ς + private static final int GR_EPSILON = '\u03B5'; // ε + private static final int GR_RHO = '\u03C1'; // ρ + private static final int GR_TAU = '\u03C4'; // τ + private static final int GR_UPSILON = '\u03C5'; // υ + private static final int GR_THETA = '\u03B8'; // θ + private static final int GR_IOTA = '\u03B9'; // ι + private static final int GR_OMICRON = '\u03BF'; // ο + private static final int GR_PI = '\u03C0'; // π + private static final int GR_ALPHA = '\u03B1'; // α + private static final int GR_SIGMA = '\u03C3'; // σ + private static final int GR_DELTA = '\u03B4'; // δ + private static final int GR_PHI = '\u03C6'; // φ + private static final int GR_GAMMA = '\u03B3'; // γ + private static final int GR_ETA = '\u03B7'; // η + private static final int GR_XI = '\u03BE'; // ξ + private static final int GR_KAPPA = '\u03BA'; // κ + private static final int GR_LAMDA = '\u03BB'; // λ + private static final int GR_ZETA = '\u03B6'; // ζ + private static final int GR_CHI = '\u03C7'; // χ + private static final int GR_PSI = '\u03C8'; // ψ + private static final int GR_OMEGA = '\u03C9'; // ω + private static final int GR_BETA = '\u03B2'; // β + private static final int GR_NU = '\u03BD'; // ν + private static final int GR_MU = '\u03BC'; // μ + final static int[] PROXIMITY = { + // Proximity for row 1. This must have exactly ROW_SIZE entries for each letter, + // and exactly PROXIMITY_GRID_WIDTH letters for a row. Pad with NUL's. + // The number of rows must be exactly PROXIMITY_GRID_HEIGHT. + GR_FINAL_SIGMA, GR_EPSILON, GR_ALPHA, GR_SIGMA, NUL, NUL, NUL, + NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, + GR_EPSILON, GR_FINAL_SIGMA, GR_RHO, GR_SIGMA, GR_DELTA, NUL, NUL, NUL, + NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, + GR_RHO, GR_EPSILON, GR_TAU, GR_DELTA, GR_PHI, NUL, NUL, NUL, + NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, + GR_TAU, GR_RHO, GR_UPSILON, GR_PHI, GR_GAMMA, NUL, NUL, NUL, + NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, + GR_UPSILON, GR_TAU, GR_THETA, GR_GAMMA, GR_ETA, NUL, NUL, NUL, + NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, + GR_THETA, GR_UPSILON, GR_IOTA, GR_ETA, GR_XI, NUL, NUL, NUL, + NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, + GR_IOTA, GR_THETA, GR_OMICRON, GR_XI, GR_KAPPA, NUL, NUL, NUL, + NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, + GR_OMICRON, GR_IOTA, GR_PI, GR_KAPPA, GR_LAMDA, NUL, NUL, NUL, NUL, NUL, + NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, + GR_PI, GR_OMICRON, GR_LAMDA, NUL, NUL, NUL, NUL, NUL, + NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, + NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, + NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, + + GR_ALPHA, GR_FINAL_SIGMA, GR_SIGMA, GR_ZETA, NUL, NUL, NUL, NUL, + NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, + GR_SIGMA, GR_FINAL_SIGMA, GR_EPSILON, GR_ALPHA, GR_DELTA, GR_ZETA, GR_CHI, NUL, + NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, + GR_DELTA, GR_EPSILON, GR_RHO, GR_SIGMA, GR_PHI, GR_ZETA, GR_CHI, GR_PSI, + NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, + GR_PHI, GR_RHO, GR_TAU, GR_DELTA, GR_GAMMA, GR_CHI, GR_PSI, GR_OMEGA, + NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, + GR_GAMMA, GR_TAU, GR_UPSILON, GR_PHI, GR_ETA, GR_PSI, GR_OMEGA, GR_BETA, + NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, + GR_ETA, GR_UPSILON, GR_THETA, GR_GAMMA, GR_XI, GR_OMEGA, GR_BETA, GR_NU, + NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, + GR_XI, GR_THETA, GR_IOTA, GR_ETA, GR_KAPPA, GR_BETA, GR_NU, GR_MU, + NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, + GR_KAPPA, GR_IOTA, GR_OMICRON, GR_XI, GR_LAMDA, GR_NU, GR_MU, NUL, NUL, + NUL, NUL, NUL, NUL, NUL, NUL, NUL, + GR_LAMDA, GR_OMICRON, GR_PI, GR_KAPPA, GR_MU, NUL, NUL, NUL, + NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, + NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, + NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, + + GR_ZETA, GR_ALPHA, GR_SIGMA, GR_DELTA, GR_CHI, NUL, NUL, NUL, + NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, + GR_CHI, GR_SIGMA, GR_DELTA, GR_PHI, GR_ZETA, GR_PSI, NUL, NUL, + NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, + GR_PSI, GR_DELTA, GR_PHI, GR_GAMMA, GR_CHI, GR_OMEGA, NUL, NUL, + NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, + GR_OMEGA, GR_PHI, GR_GAMMA, GR_ETA, GR_PSI, GR_BETA, NUL, NUL, + NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, + GR_BETA, GR_GAMMA, GR_ETA, GR_XI, GR_OMEGA, GR_NU, NUL, NUL, + NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, + GR_NU, GR_ETA, GR_XI, GR_KAPPA, GR_BETA, GR_MU, NUL, NUL, + NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, + GR_MU, GR_XI, GR_KAPPA, GR_LAMDA, GR_NU, NUL, NUL, NUL, + NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, + NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, + NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, }; @@ -183,6 +433,8 @@ public final class SpellCheckerProximityInfo { return Latin.PROXIMITY; case AndroidSpellCheckerService.SCRIPT_CYRILLIC: return Cyrillic.PROXIMITY; + case AndroidSpellCheckerService.SCRIPT_GREEK: + return Greek.PROXIMITY; default: throw new RuntimeException("Wrong script supplied: " + script); } @@ -194,6 +446,8 @@ public final class SpellCheckerProximityInfo { return Latin.getIndexOf(codePoint); case AndroidSpellCheckerService.SCRIPT_CYRILLIC: return Cyrillic.getIndexOf(codePoint); + case AndroidSpellCheckerService.SCRIPT_GREEK: + return Greek.getIndexOf(codePoint); default: throw new RuntimeException("Wrong script supplied: " + script); } diff --git a/java/src/com/android/inputmethod/research/LogUnit.java b/java/src/com/android/inputmethod/research/LogUnit.java index 7b45ff175..cfba28909 100644 --- a/java/src/com/android/inputmethod/research/LogUnit.java +++ b/java/src/com/android/inputmethod/research/LogUnit.java @@ -133,7 +133,7 @@ import java.util.Map; // will not have been opened for writing. if (jsonWriter == null) { jsonWriter = researchLog.getValidJsonWriterLocked(); - outputLogUnitStart(jsonWriter); + outputLogUnitStart(jsonWriter, isIncludingPrivateData); } outputLogStatementToLocked(jsonWriter, mLogStatementList.get(i), mValuesList.get(i), mTimeList.get(i)); @@ -169,11 +169,14 @@ import java.util.Map; private static final String LOG_UNIT_BEGIN_KEY = "logUnitStart"; private static final String LOG_UNIT_END_KEY = "logUnitEnd"; - private void outputLogUnitStart(final JsonWriter jsonWriter) { + private void outputLogUnitStart(final JsonWriter jsonWriter, + final boolean isIncludingPrivateData) { try { jsonWriter.beginObject(); jsonWriter.name(CURRENT_TIME_KEY).value(System.currentTimeMillis()); - jsonWriter.name(WORD_KEY).value(getWord()); + if (isIncludingPrivateData) { + jsonWriter.name(WORD_KEY).value(getWord()); + } jsonWriter.name(EVENT_TYPE_KEY).value(LOG_UNIT_BEGIN_KEY); jsonWriter.endObject(); } catch (IOException e) { diff --git a/java/src/com/android/inputmethod/research/ResearchLog.java b/java/src/com/android/inputmethod/research/ResearchLog.java index a2356e6a3..5edb46e27 100644 --- a/java/src/com/android/inputmethod/research/ResearchLog.java +++ b/java/src/com/android/inputmethod/research/ResearchLog.java @@ -16,6 +16,7 @@ package com.android.inputmethod.research; +import android.content.Context; import android.util.JsonWriter; import android.util.Log; @@ -23,7 +24,7 @@ import com.android.inputmethod.latin.define.ProductionFlag; import java.io.BufferedWriter; import java.io.File; -import java.io.FileWriter; +import java.io.FileOutputStream; import java.io.IOException; import java.io.OutputStream; import java.io.OutputStreamWriter; @@ -50,6 +51,8 @@ public class ResearchLog { /* package */ final ScheduledExecutorService mExecutor; /* package */ final File mFile; + private final Context mContext; + private JsonWriter mJsonWriter = NULL_JSON_WRITER; // true if at least one byte of data has been written out to the log file. This must be // remembered because JsonWriter requires that calls matching calls to beginObject and @@ -78,12 +81,13 @@ public class ResearchLog { } } - public ResearchLog(final File outputFile) { + public ResearchLog(final File outputFile, Context context) { if (outputFile == null) { throw new IllegalArgumentException(); } mExecutor = Executors.newSingleThreadScheduledExecutor(); mFile = outputFile; + mContext = context; } public synchronized void close(final Runnable onClosed) { @@ -206,7 +210,9 @@ public class ResearchLog { public JsonWriter getValidJsonWriterLocked() { try { if (mJsonWriter == NULL_JSON_WRITER) { - mJsonWriter = new JsonWriter(new BufferedWriter(new FileWriter(mFile))); + final FileOutputStream fos = + mContext.openFileOutput(mFile.getName(), Context.MODE_PRIVATE); + mJsonWriter = new JsonWriter(new BufferedWriter(new OutputStreamWriter(fos))); mJsonWriter.beginArray(); mHasWrittenData = true; } diff --git a/java/src/com/android/inputmethod/research/ResearchLogger.java b/java/src/com/android/inputmethod/research/ResearchLogger.java index 4d0808271..79a11fbaf 100644 --- a/java/src/com/android/inputmethod/research/ResearchLogger.java +++ b/java/src/com/android/inputmethod/research/ResearchLogger.java @@ -324,11 +324,22 @@ public class ResearchLogger implements SharedPreferences.OnSharedPreferenceChang sIsLogging = enableLogging; } + private static int sLogFileCounter = 0; + private File createLogFile(File filesDir) { final StringBuilder sb = new StringBuilder(); sb.append(FILENAME_PREFIX).append('-'); sb.append(mUUIDString).append('-'); - sb.append(TIMESTAMP_DATEFORMAT.format(new Date())); + sb.append(TIMESTAMP_DATEFORMAT.format(new Date())).append('-'); + // Sometimes logFiles are created within milliseconds of each other. Append a counter to + // separate these. + if (sLogFileCounter < Integer.MAX_VALUE) { + sLogFileCounter++; + } else { + // Wrap the counter, in the unlikely event of overflow. + sLogFileCounter = 0; + } + sb.append(sLogFileCounter); sb.append(FILENAME_SUFFIX); return new File(filesDir, sb.toString()); } @@ -374,12 +385,12 @@ public class ResearchLogger implements SharedPreferences.OnSharedPreferenceChang return; } if (mMainLogBuffer == null) { - mMainResearchLog = new ResearchLog(createLogFile(mFilesDir)); + mMainResearchLog = new ResearchLog(createLogFile(mFilesDir), mLatinIME); mMainLogBuffer = new MainLogBuffer(mMainResearchLog); mMainLogBuffer.setSuggest(mSuggest); } if (mFeedbackLogBuffer == null) { - mFeedbackLog = new ResearchLog(createLogFile(mFilesDir)); + mFeedbackLog = new ResearchLog(createLogFile(mFilesDir), mLatinIME); // LogBuffer is one more than FEEDBACK_WORD_BUFFER_SIZE, because it must also hold // the feedback LogUnit itself. mFeedbackLogBuffer = new FixedLogBuffer(FEEDBACK_WORD_BUFFER_SIZE + 1); @@ -599,7 +610,7 @@ public class ResearchLogger implements SharedPreferences.OnSharedPreferenceChang uploadNow(); } }); - mFeedbackLog = new ResearchLog(createLogFile(mFilesDir)); + mFeedbackLog = new ResearchLog(createLogFile(mFilesDir), mLatinIME); } public void uploadNow() { @@ -730,6 +741,8 @@ public class ResearchLogger implements SharedPreferences.OnSharedPreferenceChang } } + private static final LogStatement LOGSTATEMENT_UNCOMMIT_CURRENT_LOGUNIT = + new LogStatement("UncommitCurrentLogUnit", false, false); public void uncommitCurrentLogUnit(final String expectedWord, final boolean dumpCurrentLogUnit) { // The user has deleted this word and returned to the previous. Check that the word in the @@ -768,6 +781,7 @@ public class ResearchLogger implements SharedPreferences.OnSharedPreferenceChang if (mFeedbackLogBuffer != null) { mFeedbackLogBuffer.unshiftIn(); } + enqueueEvent(LOGSTATEMENT_UNCOMMIT_CURRENT_LOGUNIT); if (DEBUG) { Log.d(TAG, "uncommitCurrentLogUnit (dump=" + dumpCurrentLogUnit + ") back to " + (mCurrentLogUnit.hasWord() ? ": '" + mCurrentLogUnit.getWord() + "'" : "")); @@ -1162,7 +1176,7 @@ public class ResearchLogger implements SharedPreferences.OnSharedPreferenceChang suggestion == null ? null : scrubbedWord, Constants.SUGGESTION_STRIP_COORDINATE, Constants.SUGGESTION_STRIP_COORDINATE); researchLogger.commitCurrentLogUnitAsWord(scrubbedWord, Long.MAX_VALUE, isBatchMode); - researchLogger.mStatistics.recordManualSuggestion(); + researchLogger.mStatistics.recordManualSuggestion(SystemClock.uptimeMillis()); } /** @@ -1172,21 +1186,21 @@ public class ResearchLogger implements SharedPreferences.OnSharedPreferenceChang */ private static final LogStatement LOGSTATEMENT_LATINIME_PUNCTUATIONSUGGESTION = new LogStatement("LatinIMEPunctuationSuggestion", false, false, "index", "suggestion", - "x", "y"); + "x", "y", "isPrediction"); public static void latinIME_punctuationSuggestion(final int index, final String suggestion, - final boolean isBatchMode) { + final boolean isBatchMode, final boolean isPrediction) { final ResearchLogger researchLogger = getInstance(); researchLogger.enqueueEvent(LOGSTATEMENT_LATINIME_PUNCTUATIONSUGGESTION, index, suggestion, - Constants.SUGGESTION_STRIP_COORDINATE, Constants.SUGGESTION_STRIP_COORDINATE); + Constants.SUGGESTION_STRIP_COORDINATE, Constants.SUGGESTION_STRIP_COORDINATE, + isPrediction); researchLogger.commitCurrentLogUnitAsWord(suggestion, Long.MAX_VALUE, isBatchMode); } /** * Log a call to LatinIME.sendKeyCodePoint(). * - * SystemResponse: The IME is simulating a hardware keypress. This happens for numbers; other - * input typically goes through RichInputConnection.setComposingText() and - * RichInputConnection.commitText(). + * SystemResponse: The IME is inserting text into the TextView for numbers, fixed strings, or + * some other unusual mechanism. */ private static final LogStatement LOGSTATEMENT_LATINIME_SENDKEYCODEPOINT = new LogStatement("LatinIMESendKeyCodePoint", true, false, "code"); @@ -1200,6 +1214,24 @@ public class ResearchLogger implements SharedPreferences.OnSharedPreferenceChang } /** + * Log a call to LatinIME.promotePhantomSpace(). + * + * SystemResponse: The IME is inserting a real space in place of a phantom space. + */ + private static final LogStatement LOGSTATEMENT_LATINIME_PROMOTEPHANTOMSPACE = + new LogStatement("LatinIMEPromotPhantomSpace", false, false); + public static void latinIME_promotePhantomSpace() { + final ResearchLogger researchLogger = getInstance(); + final LogUnit logUnit; + if (researchLogger.mMainLogBuffer == null) { + logUnit = researchLogger.mCurrentLogUnit; + } else { + logUnit = researchLogger.mMainLogBuffer.peekLastLogUnit(); + } + researchLogger.enqueueEvent(logUnit, LOGSTATEMENT_LATINIME_PROMOTEPHANTOMSPACE); + } + + /** * Log a call to LatinIME.swapSwapperAndSpace(). * * SystemResponse: A symbol has been swapped with a space character. E.g. punctuation may swap @@ -1211,7 +1243,12 @@ public class ResearchLogger implements SharedPreferences.OnSharedPreferenceChang public static void latinIME_swapSwapperAndSpace(final CharSequence originalCharacters, final String charactersAfterSwap) { final ResearchLogger researchLogger = getInstance(); - final LogUnit logUnit = researchLogger.mMainLogBuffer.peekLastLogUnit(); + final LogUnit logUnit; + if (researchLogger.mMainLogBuffer == null) { + logUnit = null; + } else { + logUnit = researchLogger.mMainLogBuffer.peekLastLogUnit(); + } if (logUnit != null) { researchLogger.enqueueEvent(logUnit, LOGSTATEMENT_LATINIME_SWAPSWAPPERANDSPACE, originalCharacters, charactersAfterSwap); @@ -1277,20 +1314,27 @@ public class ResearchLogger implements SharedPreferences.OnSharedPreferenceChang */ private static final LogStatement LOGSTATEMENT_LATINIME_REVERTCOMMIT = new LogStatement("LatinIMERevertCommit", true, false, "committedWord", - "originallyTypedWord"); + "originallyTypedWord", "separatorString"); public static void latinIME_revertCommit(final String committedWord, - final String originallyTypedWord, final boolean isBatchMode) { + final String originallyTypedWord, final boolean isBatchMode, + final String separatorString) { final ResearchLogger researchLogger = getInstance(); // TODO: Verify that mCurrentLogUnit has been restored and contains the reverted word. - final LogUnit logUnit = researchLogger.mMainLogBuffer.peekLastLogUnit(); + final LogUnit logUnit; + if (researchLogger.mMainLogBuffer == null) { + logUnit = null; + } else { + logUnit = researchLogger.mMainLogBuffer.peekLastLogUnit(); + } if (originallyTypedWord.length() > 0 && hasLetters(originallyTypedWord)) { if (logUnit != null) { logUnit.setWord(originallyTypedWord); } } researchLogger.enqueueEvent(logUnit != null ? logUnit : researchLogger.mCurrentLogUnit, - LOGSTATEMENT_LATINIME_REVERTCOMMIT, committedWord, originallyTypedWord); - researchLogger.mStatistics.recordRevertCommit(); + LOGSTATEMENT_LATINIME_REVERTCOMMIT, committedWord, originallyTypedWord, + separatorString); + researchLogger.mStatistics.recordRevertCommit(SystemClock.uptimeMillis()); researchLogger.commitCurrentLogUnitAsWord(originallyTypedWord, Long.MAX_VALUE, isBatchMode); } @@ -1620,28 +1664,62 @@ public class ResearchLogger implements SharedPreferences.OnSharedPreferenceChang final ResearchLogger researchLogger = getInstance(); researchLogger.enqueueEvent(LOGSTATEMENT_LATINIME_ONENDBATCHINPUT, enteredText, enteredWordPos); - researchLogger.mStatistics.recordGestureInput(enteredText.length()); + researchLogger.mStatistics.recordGestureInput(enteredText.length(), + SystemClock.uptimeMillis()); } /** - * Log a call to LatinIME.handleBackspace(). + * Log a call to LatinIME.handleBackspace() that is not a batch delete. + * + * UserInput: The user is deleting one or more characters by hitting the backspace key once. + * The covers single character deletes as well as deleting selections. + */ + private static final LogStatement LOGSTATEMENT_LATINIME_HANDLEBACKSPACE = + new LogStatement("LatinIMEHandleBackspace", true, false, "numCharacters"); + public static void latinIME_handleBackspace(final int numCharacters) { + final ResearchLogger researchLogger = getInstance(); + researchLogger.enqueueEvent(LOGSTATEMENT_LATINIME_HANDLEBACKSPACE, numCharacters); + } + + /** + * Log a call to LatinIME.handleBackspace() that is a batch delete. * * UserInput: The user is deleting a gestured word by hitting the backspace key once. */ private static final LogStatement LOGSTATEMENT_LATINIME_HANDLEBACKSPACE_BATCH = - new LogStatement("LatinIMEHandleBackspaceBatch", true, false, "deletedText"); - public static void latinIME_handleBackspace_batch(final CharSequence deletedText) { + new LogStatement("LatinIMEHandleBackspaceBatch", true, false, "deletedText", + "numCharacters"); + public static void latinIME_handleBackspace_batch(final CharSequence deletedText, + final int numCharacters) { + final ResearchLogger researchLogger = getInstance(); + researchLogger.enqueueEvent(LOGSTATEMENT_LATINIME_HANDLEBACKSPACE_BATCH, deletedText, + numCharacters); + researchLogger.mStatistics.recordGestureDelete(deletedText.length(), + SystemClock.uptimeMillis()); + } + + /** + * Log a long interval between user operation. + * + * UserInput: The user has not done anything for a while. + */ + private static final LogStatement LOGSTATEMENT_ONUSERPAUSE = new LogStatement("OnUserPause", + false, false, "intervalInMs"); + public static void onUserPause(final long interval) { final ResearchLogger researchLogger = getInstance(); - researchLogger.enqueueEvent(LOGSTATEMENT_LATINIME_HANDLEBACKSPACE_BATCH, deletedText); - researchLogger.mStatistics.recordGestureDelete(); + researchLogger.enqueueEvent(LOGSTATEMENT_ONUSERPAUSE, interval); } - public static void latinIME_handleSeparator() { - // Reset the saved down event time. For tapping, motion events, etc. before the separator - // are assigned to the previous LogUnit, and events after the separator are assigned to the - // next LogUnit. In the case of multitap, this might capture down events corresponding to - // the next word, however it should not be more than a character or two. - getInstance().setSavedDownEventTime(SystemClock.uptimeMillis()); + /** + * Record the current time in case the LogUnit is later split. + * + * If the current logUnitis split, then tapping, motion events, etc. before this time should + * be assigned to one LogUnit, and events after this time should go into the following LogUnit. + */ + public static void recordTimeForLogUnitSplit() { + final ResearchLogger researchLogger = getInstance(); + researchLogger.setSavedDownEventTime(SystemClock.uptimeMillis()); + researchLogger.mSavedDownEventTime = Long.MAX_VALUE; } /** diff --git a/java/src/com/android/inputmethod/research/Statistics.java b/java/src/com/android/inputmethod/research/Statistics.java index e9c02c919..a9202651e 100644 --- a/java/src/com/android/inputmethod/research/Statistics.java +++ b/java/src/com/android/inputmethod/research/Statistics.java @@ -134,17 +134,9 @@ public class Statistics { if (DEBUG) { Log.d(TAG, "recordChar() called"); } - final long delta = time - mLastTapTime; if (codePoint == Constants.CODE_DELETE) { mDeleteKeyCount++; - if (delta < MIN_DELETION_INTERMISSION) { - if (mIsLastKeyDeleteKey) { - mDuringRepeatedDeleteKeysCounter.add(delta); - } else { - mBeforeDeleteKeyCounter.add(delta); - } - } - mIsLastKeyDeleteKey = true; + recordUserAction(time, true /* isDeletion */); } else { mCharCount++; if (Character.isDigit(codePoint)) { @@ -156,14 +148,8 @@ public class Statistics { if (Character.isSpaceChar(codePoint)) { mSpaceCount++; } - if (mIsLastKeyDeleteKey && delta < MIN_DELETION_INTERMISSION) { - mAfterDeleteKeyCounter.add(delta); - } else if (!mIsLastKeyDeleteKey && delta < MIN_TYPING_INTERMISSION) { - mKeyCounter.add(delta); - } - mIsLastKeyDeleteKey = false; + recordUserAction(time, false /* isDeletion */); } - mLastTapTime = time; } public void recordWordEntered(final boolean isDictionaryWord) { @@ -177,9 +163,10 @@ public class Statistics { mSplitWordsCount++; } - public void recordGestureInput(final int numCharsEntered) { + public void recordGestureInput(final int numCharsEntered, final long time) { mGesturesInputCount++; mGesturesCharsCount += numCharsEntered; + recordUserAction(time, false /* isDeletion */); } public void setIsEmptyUponStarting(final boolean isEmpty) { @@ -187,14 +174,43 @@ public class Statistics { mIsEmptinessStateKnown = true; } - public void recordGestureDelete() { + public void recordGestureDelete(final int length, final long time) { mGesturesDeletedCount++; + recordUserAction(time, true /* isDeletion */); } - public void recordManualSuggestion() { + + public void recordManualSuggestion(final long time) { mManualSuggestionsCount++; + recordUserAction(time, false /* isDeletion */); } - public void recordRevertCommit() { + public void recordRevertCommit(final long time) { mRevertCommitsCount++; + recordUserAction(time, true /* isDeletion */); + } + + private void recordUserAction(final long time, final boolean isDeletion) { + final long delta = time - mLastTapTime; + if (isDeletion) { + if (delta < MIN_DELETION_INTERMISSION) { + if (mIsLastKeyDeleteKey) { + mDuringRepeatedDeleteKeysCounter.add(delta); + } else { + mBeforeDeleteKeyCounter.add(delta); + } + } else { + ResearchLogger.onUserPause(delta); + } + } else { + if (mIsLastKeyDeleteKey && delta < MIN_DELETION_INTERMISSION) { + mAfterDeleteKeyCounter.add(delta); + } else if (!mIsLastKeyDeleteKey && delta < MIN_TYPING_INTERMISSION) { + mKeyCounter.add(delta); + } else { + ResearchLogger.onUserPause(delta); + } + } + mIsLastKeyDeleteKey = isDeletion; + mLastTapTime = time; } } |