aboutsummaryrefslogtreecommitdiffstats
path: root/java/src
diff options
context:
space:
mode:
Diffstat (limited to 'java/src')
-rw-r--r--java/src/com/android/inputmethod/keyboard/Key.java6
-rw-r--r--java/src/com/android/inputmethod/keyboard/Keyboard.java23
-rw-r--r--java/src/com/android/inputmethod/keyboard/KeyboardId.java56
-rw-r--r--java/src/com/android/inputmethod/keyboard/LatinKeyboardView.java151
-rw-r--r--java/src/com/android/inputmethod/keyboard/ProximityInfo.java8
-rw-r--r--java/src/com/android/inputmethod/keyboard/internal/KeyboardIconsSet.java4
-rw-r--r--java/src/com/android/inputmethod/latin/BinaryDictionary.java11
-rw-r--r--java/src/com/android/inputmethod/latin/LatinIME.java34
-rw-r--r--java/src/com/android/inputmethod/latin/ResearchLogger.java304
-rw-r--r--java/src/com/android/inputmethod/latin/SubtypeSwitcher.java4
-rw-r--r--java/src/com/android/inputmethod/latin/UserHistoryDictionary.java9
-rw-r--r--java/src/com/android/inputmethod/latin/Utils.java34
-rw-r--r--java/src/com/android/inputmethod/latin/WordComposer.java49
-rw-r--r--java/src/com/android/inputmethod/latin/define/ProductionFlag.java21
-rw-r--r--java/src/com/android/inputmethod/latin/makedict/FusionDictionary.java182
-rw-r--r--java/src/com/android/inputmethod/latin/makedict/Word.java23
-rw-r--r--java/src/com/android/inputmethod/latin/spellcheck/AndroidSpellCheckerService.java17
-rw-r--r--java/src/com/android/inputmethod/latin/spellcheck/SpellCheckerProximityInfo.java51
18 files changed, 729 insertions, 258 deletions
diff --git a/java/src/com/android/inputmethod/keyboard/Key.java b/java/src/com/android/inputmethod/keyboard/Key.java
index b09a27540..0a2b010b6 100644
--- a/java/src/com/android/inputmethod/keyboard/Key.java
+++ b/java/src/com/android/inputmethod/keyboard/Key.java
@@ -180,7 +180,7 @@ public class Key {
mY = y;
mHitBox.set(x, y, x + width + 1, y + height);
- mHashCode = hashCode(this);
+ mHashCode = computeHashCode(this);
}
/**
@@ -334,7 +334,7 @@ public class Key {
mAltCode = adjustCaseOfCodeForKeyboardId(style.getInt(keyAttr,
R.styleable.Keyboard_Key_altCode, Keyboard.CODE_UNSPECIFIED), preserveCase,
params.mId);
- mHashCode = hashCode(this);
+ mHashCode = computeHashCode(this);
keyAttr.recycle();
@@ -366,7 +366,7 @@ public class Key {
}
}
- private static int hashCode(Key key) {
+ private static int computeHashCode(Key key) {
return Arrays.hashCode(new Object[] {
key.mX,
key.mY,
diff --git a/java/src/com/android/inputmethod/keyboard/Keyboard.java b/java/src/com/android/inputmethod/keyboard/Keyboard.java
index 2b1cc43cd..962379016 100644
--- a/java/src/com/android/inputmethod/keyboard/Keyboard.java
+++ b/java/src/com/android/inputmethod/keyboard/Keyboard.java
@@ -31,6 +31,7 @@ import com.android.inputmethod.keyboard.internal.KeyStyles;
import com.android.inputmethod.keyboard.internal.KeyboardIconsSet;
import com.android.inputmethod.latin.LatinImeLogger;
import com.android.inputmethod.latin.R;
+import com.android.inputmethod.latin.Utils;
import com.android.inputmethod.latin.XmlParseUtils;
import org.xmlpull.v1.XmlPullParser;
@@ -393,7 +394,7 @@ public class Keyboard {
* >Row row_attributes*<
* >!-- Row Content --<
* >Key key_attributes* /<
- * >Spacer horizontalGap="0.2in" /<
+ * >Spacer horizontalGap="32.0dp" /<
* >include keyboardLayout="@xml/other_keys"<
* ...
* >/Row<
@@ -715,22 +716,30 @@ public class Keyboard {
R.styleable.Keyboard_Key);
try {
final int displayHeight = mDisplayMetrics.heightPixels;
- final int keyboardHeight = (int)keyboardAttr.getDimension(
- R.styleable.Keyboard_keyboardHeight, displayHeight / 2);
- final int maxKeyboardHeight = (int)getDimensionOrFraction(keyboardAttr,
+ final String keyboardHeightString = Utils.getDeviceOverrideValue(
+ mResources, R.array.keyboard_heights, null);
+ final float keyboardHeight;
+ if (keyboardHeightString != null) {
+ keyboardHeight = Float.parseFloat(keyboardHeightString)
+ * mDisplayMetrics.density;
+ } else {
+ keyboardHeight = keyboardAttr.getDimension(
+ R.styleable.Keyboard_keyboardHeight, displayHeight / 2);
+ }
+ final float maxKeyboardHeight = getDimensionOrFraction(keyboardAttr,
R.styleable.Keyboard_maxKeyboardHeight, displayHeight, displayHeight / 2);
- int minKeyboardHeight = (int)getDimensionOrFraction(keyboardAttr,
+ float minKeyboardHeight = getDimensionOrFraction(keyboardAttr,
R.styleable.Keyboard_minKeyboardHeight, displayHeight, displayHeight / 2);
if (minKeyboardHeight < 0) {
// Specified fraction was negative, so it should be calculated against display
// width.
- minKeyboardHeight = -(int)getDimensionOrFraction(keyboardAttr,
+ minKeyboardHeight = -getDimensionOrFraction(keyboardAttr,
R.styleable.Keyboard_minKeyboardHeight, displayWidth, displayWidth / 2);
}
final Params params = mParams;
// Keyboard height will not exceed maxKeyboardHeight and will not be less than
// minKeyboardHeight.
- params.mOccupiedHeight = Math.max(
+ params.mOccupiedHeight = (int)Math.max(
Math.min(keyboardHeight, maxKeyboardHeight), minKeyboardHeight);
params.mOccupiedWidth = params.mId.mWidth;
params.mTopPadding = (int)getDimensionOrFraction(keyboardAttr,
diff --git a/java/src/com/android/inputmethod/keyboard/KeyboardId.java b/java/src/com/android/inputmethod/keyboard/KeyboardId.java
index 6703b9301..3b2b11e4e 100644
--- a/java/src/com/android/inputmethod/keyboard/KeyboardId.java
+++ b/java/src/com/android/inputmethod/keyboard/KeyboardId.java
@@ -70,23 +70,23 @@ public class KeyboardId {
public KeyboardId(int elementId, Locale locale, int orientation, int width, int mode,
EditorInfo editorInfo, boolean clobberSettingsKey, boolean shortcutKeyEnabled,
boolean hasShortcutKey, boolean languageSwitchKeyEnabled) {
- this.mLocale = locale;
- this.mOrientation = orientation;
- this.mWidth = width;
- this.mMode = mode;
- this.mElementId = elementId;
- this.mEditorInfo = editorInfo;
- this.mClobberSettingsKey = clobberSettingsKey;
- this.mShortcutKeyEnabled = shortcutKeyEnabled;
- this.mHasShortcutKey = hasShortcutKey;
- this.mLanguageSwitchKeyEnabled = languageSwitchKeyEnabled;
- this.mCustomActionLabel = (editorInfo.actionLabel != null)
+ mLocale = locale;
+ mOrientation = orientation;
+ mWidth = width;
+ mMode = mode;
+ mElementId = elementId;
+ mEditorInfo = editorInfo;
+ mClobberSettingsKey = clobberSettingsKey;
+ mShortcutKeyEnabled = shortcutKeyEnabled;
+ mHasShortcutKey = hasShortcutKey;
+ mLanguageSwitchKeyEnabled = languageSwitchKeyEnabled;
+ mCustomActionLabel = (editorInfo.actionLabel != null)
? editorInfo.actionLabel.toString() : null;
- this.mHashCode = hashCode(this);
+ mHashCode = computeHashCode(this);
}
- private static int hashCode(KeyboardId id) {
+ private static int computeHashCode(KeyboardId id) {
return Arrays.hashCode(new Object[] {
id.mOrientation,
id.mElementId,
@@ -109,21 +109,21 @@ public class KeyboardId {
private boolean equals(KeyboardId other) {
if (other == this)
return true;
- return other.mOrientation == this.mOrientation
- && other.mElementId == this.mElementId
- && other.mMode == this.mMode
- && other.mWidth == this.mWidth
- && other.passwordInput() == this.passwordInput()
- && other.mClobberSettingsKey == this.mClobberSettingsKey
- && other.mShortcutKeyEnabled == this.mShortcutKeyEnabled
- && other.mHasShortcutKey == this.mHasShortcutKey
- && other.mLanguageSwitchKeyEnabled == this.mLanguageSwitchKeyEnabled
- && other.isMultiLine() == this.isMultiLine()
- && other.imeAction() == this.imeAction()
- && TextUtils.equals(other.mCustomActionLabel, this.mCustomActionLabel)
- && other.navigateNext() == this.navigateNext()
- && other.navigatePrevious() == this.navigatePrevious()
- && other.mLocale.equals(this.mLocale);
+ return other.mOrientation == mOrientation
+ && other.mElementId == mElementId
+ && other.mMode == mMode
+ && other.mWidth == mWidth
+ && other.passwordInput() == passwordInput()
+ && other.mClobberSettingsKey == mClobberSettingsKey
+ && other.mShortcutKeyEnabled == mShortcutKeyEnabled
+ && other.mHasShortcutKey == mHasShortcutKey
+ && other.mLanguageSwitchKeyEnabled == mLanguageSwitchKeyEnabled
+ && other.isMultiLine() == isMultiLine()
+ && other.imeAction() == imeAction()
+ && TextUtils.equals(other.mCustomActionLabel, mCustomActionLabel)
+ && other.navigateNext() == navigateNext()
+ && other.navigatePrevious() == navigatePrevious()
+ && other.mLocale.equals(mLocale);
}
public boolean isAlphabetKeyboard() {
diff --git a/java/src/com/android/inputmethod/keyboard/LatinKeyboardView.java b/java/src/com/android/inputmethod/keyboard/LatinKeyboardView.java
index da7d01af4..3f6c37477 100644
--- a/java/src/com/android/inputmethod/keyboard/LatinKeyboardView.java
+++ b/java/src/com/android/inputmethod/keyboard/LatinKeyboardView.java
@@ -16,11 +16,8 @@
package com.android.inputmethod.keyboard;
-import android.animation.Animator;
import android.animation.AnimatorInflater;
-import android.animation.AnimatorListenerAdapter;
-import android.animation.ValueAnimator;
-import android.animation.ValueAnimator.AnimatorUpdateListener;
+import android.animation.ObjectAnimator;
import android.content.Context;
import android.content.pm.PackageManager;
import android.content.res.TypedArray;
@@ -49,12 +46,12 @@ import com.android.inputmethod.keyboard.internal.KeySpecParser;
import com.android.inputmethod.latin.LatinIME;
import com.android.inputmethod.latin.LatinImeLogger;
import com.android.inputmethod.latin.R;
+import com.android.inputmethod.latin.ResearchLogger;
import com.android.inputmethod.latin.StaticInnerHandlerWrapper;
import com.android.inputmethod.latin.StringUtils;
import com.android.inputmethod.latin.SubtypeUtils;
import com.android.inputmethod.latin.Utils;
import com.android.inputmethod.latin.Utils.UsabilityStudyLogUtils;
-import com.android.inputmethod.latin.Utils.UsabilityStudyLogUtils.LogGroup;
import java.util.Locale;
import java.util.WeakHashMap;
@@ -80,12 +77,12 @@ public class LatinKeyboardView extends KeyboardView implements PointerTracker.Ke
private Key mSpaceKey;
private Drawable mSpaceIcon;
// Stuff to draw language name on spacebar.
- private ValueAnimator mLanguageOnSpacebarFadeoutAnimator;
- private int mFinalAlphaOfLanguageOnSpacebar;
+ private final int mLanguageOnSpacebarFinalAlpha;
+ private ObjectAnimator mLanguageOnSpacebarFadeoutAnimator;
private static final int ALPHA_OPAQUE = 255;
private boolean mNeedsToDisplayLanguage;
private Locale mSpacebarLocale;
- private int mSpacebarTextAlpha = ALPHA_OPAQUE;
+ private int mLanguageOnSpacebarAnimAlpha = ALPHA_OPAQUE;
private final float mSpacebarTextRatio;
private float mSpacebarTextSize;
private final int mSpacebarTextColor;
@@ -100,8 +97,8 @@ public class LatinKeyboardView extends KeyboardView implements PointerTracker.Ke
private static final int SPACE_LED_LENGTH_PERCENT = 80;
// Stuff to draw altCodeWhileTyping keys.
- private ValueAnimator mAltCodeKeyWhileTypingFadeoutAnimator;
- private ValueAnimator mAltCodeKeyWhileTypingFadeinAnimator;
+ private ObjectAnimator mAltCodeKeyWhileTypingFadeoutAnimator;
+ private ObjectAnimator mAltCodeKeyWhileTypingFadeinAnimator;
private int mAltCodeKeyWhileTypingAnimAlpha = ALPHA_OPAQUE;
// More keys keyboard
@@ -231,8 +228,8 @@ public class LatinKeyboardView extends KeyboardView implements PointerTracker.Ke
removeMessages(MSG_LONGPRESS_KEY);
}
- private static void cancelAndStartAnimators(ValueAnimator animatorToCancel,
- ValueAnimator animatorToStart) {
+ public static void cancelAndStartAnimators(ObjectAnimator animatorToCancel,
+ ObjectAnimator animatorToStart) {
if (animatorToCancel != null && animatorToCancel.isStarted()) {
animatorToCancel.cancel();
}
@@ -366,6 +363,8 @@ public class LatinKeyboardView extends KeyboardView implements PointerTracker.Ke
mSpacebarTextColor = a.getColor(R.styleable.LatinKeyboardView_spacebarTextColor, 0);
mSpacebarTextShadowColor = a.getColor(
R.styleable.LatinKeyboardView_spacebarTextShadowColor, 0);
+ mLanguageOnSpacebarFinalAlpha = a.getInt(
+ R.styleable.LatinKeyboardView_languageOnSpacebarFinalAlpha, ALPHA_OPAQUE);
final int languageOnSpacebarFadeoutAnimatorResId = a.getResourceId(
R.styleable.LatinKeyboardView_languageOnSpacebarFadeoutAnimator, 0);
final int altCodeKeyWhileTypingFadeoutAnimatorResId = a.getResourceId(
@@ -387,55 +386,41 @@ public class LatinKeyboardView extends KeyboardView implements PointerTracker.Ke
PointerTracker.setParameters(mPointerTrackerParams);
- final ValueAnimator animator = loadValueAnimator(languageOnSpacebarFadeoutAnimatorResId);
+ mLanguageOnSpacebarFadeoutAnimator = loadObjectAnimator(
+ languageOnSpacebarFadeoutAnimatorResId, this);
+ mAltCodeKeyWhileTypingFadeoutAnimator = loadObjectAnimator(
+ altCodeKeyWhileTypingFadeoutAnimatorResId, this);
+ mAltCodeKeyWhileTypingFadeinAnimator = loadObjectAnimator(
+ altCodeKeyWhileTypingFadeinAnimatorResId, this);
+ }
+
+ private ObjectAnimator loadObjectAnimator(int resId, Object target) {
+ if (resId == 0) return null;
+ final ObjectAnimator animator = (ObjectAnimator)AnimatorInflater.loadAnimator(
+ getContext(), resId);
if (animator != null) {
- animator.addUpdateListener(new AnimatorUpdateListener() {
- @Override
- public void onAnimationUpdate(ValueAnimator animation) {
- mSpacebarTextAlpha = (Integer)animation.getAnimatedValue();
- invalidateKey(mSpaceKey);
- }
- });
- animator.addListener(new AnimatorListenerAdapter() {
- @Override
- public void onAnimationEnd(Animator a) {
- final ValueAnimator valueAnimator = (ValueAnimator)a;
- mFinalAlphaOfLanguageOnSpacebar = (Integer)valueAnimator.getAnimatedValue();
- }
- });
- // In order to get the final value of animator.
- animator.end();
- }
- mLanguageOnSpacebarFadeoutAnimator = animator;
-
- final ValueAnimator fadeout = loadValueAnimator(altCodeKeyWhileTypingFadeoutAnimatorResId);
- if (fadeout != null) {
- fadeout.addUpdateListener(new AnimatorUpdateListener() {
- @Override
- public void onAnimationUpdate(ValueAnimator animation) {
- mAltCodeKeyWhileTypingAnimAlpha = (Integer)animation.getAnimatedValue();
- updateAltCodeKeyWhileTyping();
- }
- });
+ animator.setTarget(target);
}
- mAltCodeKeyWhileTypingFadeoutAnimator = fadeout;
+ return animator;
+ }
- final ValueAnimator fadein = loadValueAnimator(altCodeKeyWhileTypingFadeinAnimatorResId);
- if (fadein != null) {
- fadein.addUpdateListener(new AnimatorUpdateListener() {
- @Override
- public void onAnimationUpdate(ValueAnimator animation) {
- mAltCodeKeyWhileTypingAnimAlpha = (Integer)animation.getAnimatedValue();
- updateAltCodeKeyWhileTyping();
- }
- });
- }
- mAltCodeKeyWhileTypingFadeinAnimator = fadein;
+ // Getter/setter methods for {@link ObjectAnimator}.
+ public int getLanguageOnSpacebarAnimAlpha() {
+ return mLanguageOnSpacebarAnimAlpha;
}
- private ValueAnimator loadValueAnimator(int resId) {
- if (resId == 0) return null;
- return (ValueAnimator)AnimatorInflater.loadAnimator(getContext(), resId);
+ public void setLanguageOnSpacebarAnimAlpha(int alpha) {
+ mLanguageOnSpacebarAnimAlpha = alpha;
+ invalidateKey(mSpaceKey);
+ }
+
+ public int getAltCodeKeyWhileTypingAnimAlpha() {
+ return mAltCodeKeyWhileTypingAnimAlpha;
+ }
+
+ public void setAltCodeKeyWhileTypingAnimAlpha(int alpha) {
+ mAltCodeKeyWhileTypingAnimAlpha = alpha;
+ updateAltCodeKeyWhileTyping();
}
public void setKeyboardActionListener(KeyboardActionListener listener) {
@@ -702,9 +687,22 @@ public class LatinKeyboardView extends KeyboardView implements PointerTracker.Ke
break;
}
if (!TextUtils.isEmpty(eventTag)) {
- UsabilityStudyLogUtils.getInstance().write(LogGroup.MOTION_EVENT,
+ final float size = me.getSize(index);
+ final float pressure = me.getPressure(index);
+ UsabilityStudyLogUtils.getInstance().write(
eventTag + eventTime + "," + id + "," + x + "," + y + ","
- + me.getSize(index) + "," + me.getPressure(index));
+ + size + "," + pressure);
+ }
+ }
+ if (ResearchLogger.sIsLogging) {
+ // TODO: remove redundant calculations of size and pressure by
+ // removing UsabilityStudyLog code once the ResearchLogger is mature enough
+ final float size = me.getSize(index);
+ final float pressure = me.getPressure(index);
+ if (action != MotionEvent.ACTION_MOVE) {
+ // Skip ACTION_MOVE events as they are logged below
+ ResearchLogger.getInstance().logMotionEvent(action, eventTime, id, x,
+ y, size, pressure);
}
}
@@ -752,8 +750,9 @@ public class LatinKeyboardView extends KeyboardView implements PointerTracker.Ke
if (action == MotionEvent.ACTION_MOVE) {
for (int i = 0; i < pointerCount; i++) {
+ final int pointerId = me.getPointerId(i);
final PointerTracker tracker = PointerTracker.getPointerTracker(
- me.getPointerId(i), this);
+ pointerId, this);
final int px, py;
if (mMoreKeysPanel != null
&& tracker.mPointerId == mMoreKeysPanelPointerTrackerId) {
@@ -765,10 +764,18 @@ public class LatinKeyboardView extends KeyboardView implements PointerTracker.Ke
}
tracker.onMoveEvent(px, py, eventTime);
if (ENABLE_USABILITY_STUDY_LOG) {
- UsabilityStudyLogUtils.getInstance().write(
- LogGroup.MOTION_EVENT,
- "[Move]" + eventTime + "," + me.getPointerId(i) + "," + px + "," + py
- + "," + me.getSize(i) + "," + me.getPressure(i));
+ final float pointerSize = me.getSize(i);
+ final float pointerPressure = me.getPressure(i);
+ UsabilityStudyLogUtils.getInstance().write("[Move]" + eventTime + ","
+ + pointerId + "," + px + "," + py + ","
+ + pointerSize + "," + pointerPressure);
+ }
+ if (ResearchLogger.sIsLogging) {
+ // TODO: earlier comment about redundant calculations applies here too
+ final float pointerSize = me.getSize(i);
+ final float pointerPressure = me.getPressure(i);
+ ResearchLogger.getInstance().logMotionEvent(action, eventTime, pointerId,
+ px, py, pointerSize, pointerPressure);
}
}
} else {
@@ -861,19 +868,21 @@ public class LatinKeyboardView extends KeyboardView implements PointerTracker.Ke
public void startDisplayLanguageOnSpacebar(boolean subtypeChanged,
boolean needsToDisplayLanguage) {
- final ValueAnimator animator = mLanguageOnSpacebarFadeoutAnimator;
- if (animator != null) {
- animator.cancel();
- }
+ final ObjectAnimator animator = mLanguageOnSpacebarFadeoutAnimator;
mNeedsToDisplayLanguage = needsToDisplayLanguage;
if (animator == null) {
mNeedsToDisplayLanguage = false;
} else {
if (subtypeChanged && needsToDisplayLanguage) {
- mSpacebarTextAlpha = ALPHA_OPAQUE;
+ setLanguageOnSpacebarAnimAlpha(ALPHA_OPAQUE);
+ if (animator.isStarted()) {
+ animator.cancel();
+ }
animator.start();
} else {
- mSpacebarTextAlpha = mFinalAlphaOfLanguageOnSpacebar;
+ if (!animator.isStarted()) {
+ mLanguageOnSpacebarAnimAlpha = mLanguageOnSpacebarFinalAlpha;
+ }
}
}
invalidateKey(mSpaceKey);
@@ -967,10 +976,10 @@ public class LatinKeyboardView extends KeyboardView implements PointerTracker.Ke
final float textHeight = -paint.ascent() + descent;
final float baseline = height / 2 + textHeight / 2;
paint.setColor(mSpacebarTextShadowColor);
- paint.setAlpha(mSpacebarTextAlpha);
+ paint.setAlpha(mLanguageOnSpacebarAnimAlpha);
canvas.drawText(language, width / 2, baseline - descent - 1, paint);
paint.setColor(mSpacebarTextColor);
- paint.setAlpha(mSpacebarTextAlpha);
+ paint.setAlpha(mLanguageOnSpacebarAnimAlpha);
canvas.drawText(language, width / 2, baseline - descent, paint);
}
diff --git a/java/src/com/android/inputmethod/keyboard/ProximityInfo.java b/java/src/com/android/inputmethod/keyboard/ProximityInfo.java
index 442413c0c..5c1808613 100644
--- a/java/src/com/android/inputmethod/keyboard/ProximityInfo.java
+++ b/java/src/com/android/inputmethod/keyboard/ProximityInfo.java
@@ -79,8 +79,12 @@ public class ProximityInfo {
final ProximityInfo spellCheckerProximityInfo = createDummyProximityInfo();
spellCheckerProximityInfo.mNativeProximityInfo =
spellCheckerProximityInfo.setProximityInfoNative("",
- SpellCheckerProximityInfo.ROW_SIZE, 480, 300, 11, 3, (480 / 10), proximity,
- 0, null, null, null, null, null, null, null, null);
+ SpellCheckerProximityInfo.ROW_SIZE,
+ SpellCheckerProximityInfo.PROXIMITY_GRID_WIDTH,
+ SpellCheckerProximityInfo.PROXIMITY_GRID_HEIGHT,
+ SpellCheckerProximityInfo.PROXIMITY_GRID_WIDTH,
+ SpellCheckerProximityInfo.PROXIMITY_GRID_HEIGHT,
+ 1, proximity, 0, null, null, null, null, null, null, null, null);
return spellCheckerProximityInfo;
}
diff --git a/java/src/com/android/inputmethod/keyboard/internal/KeyboardIconsSet.java b/java/src/com/android/inputmethod/keyboard/internal/KeyboardIconsSet.java
index 9b9c86179..ded89b1b8 100644
--- a/java/src/com/android/inputmethod/keyboard/internal/KeyboardIconsSet.java
+++ b/java/src/com/android/inputmethod/keyboard/internal/KeyboardIconsSet.java
@@ -30,7 +30,7 @@ public class KeyboardIconsSet {
// The value should be aligned with the enum value of Key.keyIcon.
public static final int ICON_UNDEFINED = 0;
- private static final int NUM_ICONS = 14;
+ private static final int NUM_ICONS = 16;
private final Drawable[] mIcons = new Drawable[NUM_ICONS + 1];
@@ -58,6 +58,8 @@ public class KeyboardIconsSet {
addIconIdMap(12, "disabledShortcurKey", R.styleable.Keyboard_iconDisabledShortcutKey);
addIconIdMap(13, "previewTabKey", R.styleable.Keyboard_iconPreviewTabKey);
addIconIdMap(14, "languageSwitchKey", R.styleable.Keyboard_iconLanguageSwitchKey);
+ addIconIdMap(15, "zwnjKey", R.styleable.Keyboard_iconZwnjKey);
+ addIconIdMap(16, "zwjKey", R.styleable.Keyboard_iconZwjKey);
}
private static void addIconIdMap(int iconId, String name, int attrId) {
diff --git a/java/src/com/android/inputmethod/latin/BinaryDictionary.java b/java/src/com/android/inputmethod/latin/BinaryDictionary.java
index dfc8c8e9d..a9df1ce12 100644
--- a/java/src/com/android/inputmethod/latin/BinaryDictionary.java
+++ b/java/src/com/android/inputmethod/latin/BinaryDictionary.java
@@ -55,6 +55,8 @@ public class BinaryDictionary extends Dictionary {
public static final Flag FLAG_REQUIRES_GERMAN_UMLAUT_PROCESSING =
new Flag(R.bool.config_require_umlaut_processing, 0x1);
+ public static final Flag FLAG_REQUIRES_FRENCH_LIGATURES_PROCESSING =
+ new Flag(R.bool.config_require_ligatures_processing, 0x4);
// FULL_EDIT_DISTANCE is a flag that forces the dictionary to use full words
// when computing edit distance, instead of the default behavior of stopping
@@ -77,6 +79,7 @@ public class BinaryDictionary extends Dictionary {
// actual value will be read from the configuration/extra value at run time for
// the configuration at dictionary creation time.
FLAG_REQUIRES_GERMAN_UMLAUT_PROCESSING,
+ FLAG_REQUIRES_FRENCH_LIGATURES_PROCESSING,
};
private int mFlags = 0;
@@ -202,9 +205,11 @@ public class BinaryDictionary extends Dictionary {
Arrays.fill(mInputCodes, WordComposer.NOT_A_CODE);
for (int i = 0; i < codesSize; i++) {
- int[] alternatives = codes.getCodesAt(i);
- System.arraycopy(alternatives, 0, mInputCodes, i * MAX_PROXIMITY_CHARS_SIZE,
- Math.min(alternatives.length, MAX_PROXIMITY_CHARS_SIZE));
+ final int[] alternatives = codes.getCodesAt(i);
+ if (alternatives == null || alternatives.length < 1) {
+ continue;
+ }
+ mInputCodes[i] = alternatives[0];
}
Arrays.fill(outputChars, (char) 0);
Arrays.fill(scores, 0);
diff --git a/java/src/com/android/inputmethod/latin/LatinIME.java b/java/src/com/android/inputmethod/latin/LatinIME.java
index e67f0ea05..7272006a2 100644
--- a/java/src/com/android/inputmethod/latin/LatinIME.java
+++ b/java/src/com/android/inputmethod/latin/LatinIME.java
@@ -68,6 +68,7 @@ import com.android.inputmethod.keyboard.KeyboardId;
import com.android.inputmethod.keyboard.KeyboardSwitcher;
import com.android.inputmethod.keyboard.KeyboardView;
import com.android.inputmethod.keyboard.LatinKeyboardView;
+import com.android.inputmethod.latin.Utils.UsabilityStudyLogUtils;
import com.android.inputmethod.latin.suggestions.SuggestionsView;
import java.io.FileDescriptor;
@@ -196,7 +197,7 @@ public class LatinIME extends InputMethodServiceCompatWrapper implements Keyboar
private InputMethodManagerCompatWrapper mImm;
private Resources mResources;
private SharedPreferences mPrefs;
- private final KeyboardSwitcher mKeyboardSwitcher;
+ /* package for tests */ final KeyboardSwitcher mKeyboardSwitcher;
private final SubtypeSwitcher mSubtypeSwitcher;
private VoiceProxy mVoiceProxy;
private boolean mShouldSwitchToLastSubtype = true;
@@ -438,6 +439,7 @@ public class LatinIME extends InputMethodServiceCompatWrapper implements Keyboar
final SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(this);
mPrefs = prefs;
LatinImeLogger.init(this, prefs);
+ ResearchLogger.init(this, prefs);
LanguageSwitcherProxy.init(this, prefs);
InputMethodManagerCompatWrapper.init(this);
SubtypeSwitcher.init(this);
@@ -502,7 +504,7 @@ public class LatinIME extends InputMethodServiceCompatWrapper implements Keyboar
private void initSuggest() {
final String localeStr = mSubtypeSwitcher.getInputLocaleStr();
- final Locale keyboardLocale = LocaleUtils.constructLocaleFromString(localeStr);
+ final Locale keyboardLocale = mSubtypeSwitcher.getInputLocale();
final Resources res = mResources;
final Locale savedLocale = LocaleUtils.setSystemLocale(res, keyboardLocale);
@@ -527,7 +529,7 @@ public class LatinIME extends InputMethodServiceCompatWrapper implements Keyboar
resetContactsDictionary(oldContactsDictionary);
mUserHistoryDictionary
- = new UserHistoryDictionary(this, this, localeStr, Suggest.DIC_USER_HISTORY);
+ = new UserHistoryDictionary(this, localeStr, Suggest.DIC_USER_HISTORY);
mSuggest.setUserHistoryDictionary(mUserHistoryDictionary);
LocaleUtils.setSystemLocale(res, savedLocale);
@@ -566,8 +568,7 @@ public class LatinIME extends InputMethodServiceCompatWrapper implements Keyboar
}
/* package private */ void resetSuggestMainDict() {
- final String localeStr = mSubtypeSwitcher.getInputLocaleStr();
- final Locale keyboardLocale = LocaleUtils.constructLocaleFromString(localeStr);
+ final Locale keyboardLocale = mSubtypeSwitcher.getInputLocale();
int mainDicResId = DictionaryFactory.getMainDictionaryResourceId(mResources);
mSuggest.resetMainDict(this, mainDicResId, keyboardLocale);
}
@@ -1006,10 +1007,6 @@ public class LatinIME extends InputMethodServiceCompatWrapper implements Keyboar
final int touchHeight = inputView.getHeight() + extraHeight
// Extend touchable region below the keyboard.
+ EXTENDED_TOUCHABLE_REGION_HEIGHT;
- if (DEBUG) {
- Log.d(TAG, "Touchable region: y=" + touchY + " width=" + touchWidth
- + " height=" + touchHeight);
- }
setTouchableRegionCompat(outInsets, 0, touchY, touchWidth, touchHeight);
}
outInsets.contentTopInsets = touchY;
@@ -1266,6 +1263,11 @@ public class LatinIME extends InputMethodServiceCompatWrapper implements Keyboar
mDeleteCount = 0;
}
mLastKeyTime = when;
+
+ if (ResearchLogger.sIsLogging) {
+ ResearchLogger.getInstance().logKeyEvent(primaryCode, x, y);
+ }
+
final KeyboardSwitcher switcher = mKeyboardSwitcher;
// The space state depends only on the last character pressed and its own previous
// state. Here, we revert the space state to neutral if the key is actually modifying
@@ -1989,7 +1991,7 @@ public class LatinIME extends InputMethodServiceCompatWrapper implements Keyboar
}
private void addToUserHistoryDictionary(final CharSequence suggestion) {
- if (suggestion == null || suggestion.length() < 1) return;
+ if (TextUtils.isEmpty(suggestion)) return;
// Only auto-add to dictionary if auto-correct is ON. Otherwise we'll be
// adding words in situations where the user or application really didn't
@@ -2007,8 +2009,14 @@ public class LatinIME extends InputMethodServiceCompatWrapper implements Keyboar
} else {
prevWord = null;
}
+ final String secondWord;
+ if (mWordComposer.isAutoCapitalized() && !mWordComposer.isMostlyCaps()) {
+ secondWord = suggestion.toString().toLowerCase(mSubtypeSwitcher.getInputLocale());
+ } else {
+ secondWord = suggestion.toString();
+ }
mUserHistoryDictionary.addToUserHistory(null == prevWord ? null : prevWord.toString(),
- suggestion.toString());
+ secondWord);
}
}
@@ -2249,10 +2257,6 @@ public class LatinIME extends InputMethodServiceCompatWrapper implements Keyboar
mFeedbackManager.vibrate(mKeyboardSwitcher.getKeyboardView());
}
- public boolean isAutoCapitalized() {
- return mWordComposer.isAutoCapitalized();
- }
-
private void updateCorrectionMode() {
// TODO: cleanup messy flags
final boolean shouldAutoCorrect = mSettingsValues.mAutoCorrectEnabled
diff --git a/java/src/com/android/inputmethod/latin/ResearchLogger.java b/java/src/com/android/inputmethod/latin/ResearchLogger.java
new file mode 100644
index 000000000..3b110bd78
--- /dev/null
+++ b/java/src/com/android/inputmethod/latin/ResearchLogger.java
@@ -0,0 +1,304 @@
+/*
+ * Copyright (C) 2012 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+
+package com.android.inputmethod.latin;
+
+import android.content.SharedPreferences;
+import android.inputmethodservice.InputMethodService;
+import android.os.Handler;
+import android.os.HandlerThread;
+import android.os.Process;
+import android.os.SystemClock;
+import android.text.TextUtils;
+import android.util.Log;
+import android.view.MotionEvent;
+
+import com.android.inputmethod.keyboard.Keyboard;
+
+import java.io.File;
+import java.io.FileNotFoundException;
+import java.io.FileOutputStream;
+import java.io.PrintWriter;
+import java.text.SimpleDateFormat;
+import java.util.Date;
+
+/**
+ * Logs the use of the LatinIME keyboard.
+ *
+ * This class logs operations on the IME keyboard, including what the user has typed.
+ * Data is stored locally in a file in app-specific storage.
+ *
+ * This functionality is off by default.
+ */
+public class ResearchLogger implements SharedPreferences.OnSharedPreferenceChangeListener {
+ private static final String TAG = ResearchLogger.class.getSimpleName();
+ private static final String PREF_USABILITY_STUDY_MODE = "usability_study_mode";
+
+ private static final ResearchLogger sInstance = new ResearchLogger(new LogFileManager());
+ public static boolean sIsLogging = false;
+ /* package */ final Handler mLoggingHandler;
+ private InputMethodService mIms;
+ private final Date mDate;
+ private final SimpleDateFormat mDateFormat;
+
+ /**
+ * Isolates management of files. This variable should never be null, but can be changed
+ * to support testing.
+ */
+ private LogFileManager mLogFileManager;
+
+ /**
+ * Manages the file(s) that stores the logs.
+ *
+ * Handles creation, deletion, and provides Readers, Writers, and InputStreams to access
+ * the logs.
+ */
+ public static class LogFileManager {
+ private static final String DEFAULT_FILENAME = "log.txt";
+ private static final String DEFAULT_LOG_DIRECTORY = "researchLogger";
+
+ private static final long LOGFILE_PURGE_INTERVAL = 1000 * 60 * 60 * 24;
+
+ private InputMethodService mIms;
+ private File mFile;
+ private PrintWriter mPrintWriter;
+
+ /* package */ LogFileManager() {
+ }
+
+ public void init(InputMethodService ims) {
+ mIms = ims;
+ }
+
+ public synchronized void createLogFile() {
+ try {
+ createLogFile(DEFAULT_LOG_DIRECTORY, DEFAULT_FILENAME);
+ } catch (FileNotFoundException e) {
+ Log.w(TAG, e);
+ }
+ }
+
+ public synchronized void createLogFile(String dir, String filename)
+ throws FileNotFoundException {
+ if (mIms == null) {
+ Log.w(TAG, "InputMethodService is not configured. Logging is off.");
+ return;
+ }
+ File filesDir = mIms.getFilesDir();
+ if (filesDir == null || !filesDir.exists()) {
+ Log.w(TAG, "Storage directory does not exist. Logging is off.");
+ return;
+ }
+ File directory = new File(filesDir, dir);
+ if (!directory.exists()) {
+ boolean wasCreated = directory.mkdirs();
+ if (!wasCreated) {
+ Log.w(TAG, "Log directory cannot be created. Logging is off.");
+ return;
+ }
+ }
+
+ close();
+ mFile = new File(directory, filename);
+ boolean append = true;
+ if (mFile.exists() && mFile.lastModified() + LOGFILE_PURGE_INTERVAL <
+ System.currentTimeMillis()) {
+ append = false;
+ }
+ mPrintWriter = new PrintWriter(new FileOutputStream(mFile, append), true);
+ }
+
+ public synchronized boolean append(String s) {
+ if (mPrintWriter == null) {
+ Log.w(TAG, "PrintWriter is null");
+ return false;
+ } else {
+ mPrintWriter.print(s);
+ return !mPrintWriter.checkError();
+ }
+ }
+
+ public synchronized void reset() {
+ if (mPrintWriter != null) {
+ mPrintWriter.close();
+ mPrintWriter = null;
+ }
+ if (mFile != null && mFile.exists()) {
+ mFile.delete();
+ mFile = null;
+ }
+ }
+
+ public synchronized void close() {
+ if (mPrintWriter != null) {
+ mPrintWriter.close();
+ mPrintWriter = null;
+ mFile = null;
+ }
+ }
+ }
+
+ private ResearchLogger(LogFileManager logFileManager) {
+ mDate = new Date();
+ mDateFormat = new SimpleDateFormat("yyyyMMdd-HHmmss.SSSZ");
+
+ HandlerThread handlerThread = new HandlerThread("ResearchLogger logging task",
+ Process.THREAD_PRIORITY_BACKGROUND);
+ handlerThread.start();
+ mLoggingHandler = new Handler(handlerThread.getLooper());
+ mLogFileManager = logFileManager;
+ }
+
+ public static ResearchLogger getInstance() {
+ return sInstance;
+ }
+
+ public static void init(InputMethodService ims, SharedPreferences prefs) {
+ sInstance.initInternal(ims, prefs);
+ }
+
+ public void initInternal(InputMethodService ims, SharedPreferences prefs) {
+ mIms = ims;
+ if (mLogFileManager != null) {
+ mLogFileManager.init(ims);
+ mLogFileManager.createLogFile();
+ }
+ if (prefs != null) {
+ sIsLogging = prefs.getBoolean(PREF_USABILITY_STUDY_MODE, false);
+ }
+ prefs.registerOnSharedPreferenceChangeListener(this);
+ }
+
+ /**
+ * Change to a different logFileManager.
+ *
+ * @throws IllegalArgumentException if logFileManager is null
+ */
+ void setLogFileManager(LogFileManager manager) {
+ if (manager == null) {
+ throw new IllegalArgumentException("warning: trying to set null logFileManager");
+ } else {
+ mLogFileManager = manager;
+ }
+ }
+
+ /**
+ * Represents a category of logging events that share the same subfield structure.
+ */
+ private static enum LogGroup {
+ MOTION_EVENT("m"),
+ KEY("k"),
+ CORRECTION("c"),
+ STATE_CHANGE("s");
+
+ private final String mLogString;
+
+ private LogGroup(String logString) {
+ mLogString = logString;
+ }
+ }
+
+ public void logMotionEvent(final int action, final long eventTime, final int id,
+ final int x, final int y, final float size, final float pressure) {
+ final String eventTag;
+ switch (action) {
+ case MotionEvent.ACTION_CANCEL: eventTag = "[Cancel]"; break;
+ case MotionEvent.ACTION_UP: eventTag = "[Up]"; break;
+ case MotionEvent.ACTION_DOWN: eventTag = "[Down]"; break;
+ case MotionEvent.ACTION_POINTER_UP: eventTag = "[PointerUp]"; break;
+ case MotionEvent.ACTION_POINTER_DOWN: eventTag = "[PointerDown]"; break;
+ case MotionEvent.ACTION_MOVE: eventTag = "[Move]"; break;
+ case MotionEvent.ACTION_OUTSIDE: eventTag = "[Outside]"; break;
+ default: eventTag = "[Action" + action + "]"; break;
+ }
+ if (!TextUtils.isEmpty(eventTag)) {
+ StringBuilder sb = new StringBuilder();
+ sb.append(eventTag);
+ sb.append('\t'); sb.append(eventTime);
+ sb.append('\t'); sb.append(id);
+ sb.append('\t'); sb.append(x);
+ sb.append('\t'); sb.append(y);
+ sb.append('\t'); sb.append(size);
+ sb.append('\t'); sb.append(pressure);
+ write(LogGroup.MOTION_EVENT, sb.toString());
+ }
+ }
+
+ public void logKeyEvent(int code, int x, int y) {
+ final StringBuilder sb = new StringBuilder();
+ sb.append(Keyboard.printableCode(code));
+ sb.append('\t'); sb.append(x);
+ sb.append('\t'); sb.append(y);
+ write(LogGroup.KEY, sb.toString());
+ }
+
+ public void logCorrection(String subgroup, String before, String after, int position) {
+ final StringBuilder sb = new StringBuilder();
+ sb.append(subgroup);
+ sb.append('\t'); sb.append(before);
+ sb.append('\t'); sb.append(after);
+ sb.append('\t'); sb.append(position);
+ write(LogGroup.CORRECTION, sb.toString());
+ }
+
+ public void logStateChange(String subgroup, String details) {
+ write(LogGroup.STATE_CHANGE, subgroup + "\t" + details);
+ }
+
+ private void write(final LogGroup logGroup, final String log) {
+ mLoggingHandler.post(new Runnable() {
+ @Override
+ public void run() {
+ final long currentTime = System.currentTimeMillis();
+ mDate.setTime(currentTime);
+ final long upTime = SystemClock.uptimeMillis();
+
+ final String printString = String.format("%s\t%d\t%s\t%s\n",
+ mDateFormat.format(mDate), upTime, logGroup.mLogString, log);
+ if (LatinImeLogger.sDBG) {
+ Log.d(TAG, "Write: " + '[' + logGroup.mLogString + ']' + log);
+ }
+ if (mLogFileManager.append(printString)) {
+ // success
+ } else {
+ if (LatinImeLogger.sDBG) {
+ Log.w(TAG, "Unable to write to log.");
+ }
+ }
+ }
+ });
+ }
+
+ public void clearAll() {
+ mLoggingHandler.post(new Runnable() {
+ @Override
+ public void run() {
+ if (LatinImeLogger.sDBG) {
+ Log.d(TAG, "Delete log file.");
+ }
+ mLogFileManager.reset();
+ }
+ });
+ }
+
+ @Override
+ public void onSharedPreferenceChanged(SharedPreferences prefs, String key) {
+ if (key == null || prefs == null) {
+ return;
+ }
+ sIsLogging = prefs.getBoolean(PREF_USABILITY_STUDY_MODE, false);
+ }
+}
diff --git a/java/src/com/android/inputmethod/latin/SubtypeSwitcher.java b/java/src/com/android/inputmethod/latin/SubtypeSwitcher.java
index ffbbf9bb8..3524c72f6 100644
--- a/java/src/com/android/inputmethod/latin/SubtypeSwitcher.java
+++ b/java/src/com/android/inputmethod/latin/SubtypeSwitcher.java
@@ -47,13 +47,13 @@ public class SubtypeSwitcher {
private static final String TAG = SubtypeSwitcher.class.getSimpleName();
public static final String KEYBOARD_MODE = "keyboard";
- private static final char LOCALE_SEPARATER = '_';
+ private static final char LOCALE_SEPARATOR = '_';
private static final String VOICE_MODE = "voice";
private static final String SUBTYPE_EXTRAVALUE_REQUIRE_NETWORK_CONNECTIVITY =
"requireNetworkConnectivity";
private final TextUtils.SimpleStringSplitter mLocaleSplitter =
- new TextUtils.SimpleStringSplitter(LOCALE_SEPARATER);
+ new TextUtils.SimpleStringSplitter(LOCALE_SEPARATOR);
private static final SubtypeSwitcher sInstance = new SubtypeSwitcher();
private /* final */ LatinIME mService;
diff --git a/java/src/com/android/inputmethod/latin/UserHistoryDictionary.java b/java/src/com/android/inputmethod/latin/UserHistoryDictionary.java
index 4e798460c..db2cdf967 100644
--- a/java/src/com/android/inputmethod/latin/UserHistoryDictionary.java
+++ b/java/src/com/android/inputmethod/latin/UserHistoryDictionary.java
@@ -75,8 +75,6 @@ public class UserHistoryDictionary extends ExpandableDictionary {
private static final String FREQ_COLUMN_PAIR_ID = "pair_id";
private static final String FREQ_COLUMN_FREQUENCY = "freq";
- private final LatinIME mIme;
-
/** Locale for which this auto dictionary is storing words */
private String mLocale;
@@ -139,9 +137,8 @@ public class UserHistoryDictionary extends ExpandableDictionary {
sDeleteHistoryBigrams = deleteHistoryBigram;
}
- public UserHistoryDictionary(Context context, LatinIME ime, String locale, int dicTypeId) {
+ public UserHistoryDictionary(final Context context, final String locale, final int dicTypeId) {
super(context, dicTypeId);
- mIme = ime;
mLocale = locale;
if (sOpenHelper == null) {
sOpenHelper = new DatabaseHelper(getContext());
@@ -179,10 +176,6 @@ public class UserHistoryDictionary extends ExpandableDictionary {
* The second word may not be null (a NullPointerException would be thrown).
*/
public int addToUserHistory(final String word1, String word2) {
- // remove caps if second word is autocapitalized
- if (mIme != null && mIme.isAutoCapitalized()) {
- word2 = Character.toLowerCase(word2.charAt(0)) + word2.substring(1);
- }
super.addWord(word2, FREQUENCY_FOR_TYPED);
// Do not insert a word as a bigram of itself
if (word2.equals(word1)) {
diff --git a/java/src/com/android/inputmethod/latin/Utils.java b/java/src/com/android/inputmethod/latin/Utils.java
index e2ce08323..be64c2fd8 100644
--- a/java/src/com/android/inputmethod/latin/Utils.java
+++ b/java/src/com/android/inputmethod/latin/Utils.java
@@ -31,7 +31,9 @@ import android.os.Process;
import android.text.TextUtils;
import android.text.format.DateUtils;
import android.util.Log;
+import android.view.MotionEvent;
+import com.android.inputmethod.keyboard.Keyboard;
import com.android.inputmethod.latin.SuggestedWords.SuggestedWordInfo;
import java.io.BufferedReader;
@@ -138,9 +140,6 @@ public class Utils {
// TODO: accept code points
public void push(char c, int x, int y) {
if (!mEnabled) return;
- if (mUsabilityStudy) {
- UsabilityStudyLogUtils.getInstance().writeChar(c, x, y);
- }
mCharBuf[mEnd] = c;
mXBuf[mEnd] = x;
mYBuf[mEnd] = y;
@@ -221,6 +220,7 @@ public class Utils {
}
public static class UsabilityStudyLogUtils {
+ // TODO: remove code duplication with ResearchLog class
private static final String USABILITY_TAG = UsabilityStudyLogUtils.class.getSimpleName();
private static final String FILENAME = "log.txt";
private static final UsabilityStudyLogUtils sInstance =
@@ -263,25 +263,8 @@ public class Utils {
}
}
- /**
- * Represents a category of logging events that share the same subfield structure.
- */
- public static enum LogGroup {
- MOTION_EVENT("m"),
- KEY("k"),
- CORRECTION("c"),
- STATE_CHANGE("s");
-
- private final String mLogString;
-
- private LogGroup(String logString) {
- mLogString = logString;
- }
- }
-
public static void writeBackSpace(int x, int y) {
- UsabilityStudyLogUtils.getInstance().write(
- LogGroup.KEY, "<backspace>\t" + x + "\t" + y);
+ UsabilityStudyLogUtils.getInstance().write("<backspace>\t" + x + "\t" + y);
}
public void writeChar(char c, int x, int y) {
@@ -297,12 +280,11 @@ public class Utils {
inputChar = "<space>";
break;
}
- UsabilityStudyLogUtils.getInstance().write(LogGroup.KEY,
- inputChar + "\t" + x + "\t" + y);
+ UsabilityStudyLogUtils.getInstance().write(inputChar + "\t" + x + "\t" + y);
LatinImeLogger.onPrintAllUsabilityStudyLogs();
}
- public void write(final LogGroup logGroup, final String log) {
+ public void write(final String log) {
mLoggingHandler.post(new Runnable() {
@Override
public void run() {
@@ -310,8 +292,8 @@ public class Utils {
final long currentTime = System.currentTimeMillis();
mDate.setTime(currentTime);
- final String printString = String.format("%s\t%d\t%s\t%s\n",
- mDateFormat.format(mDate), currentTime, logGroup.mLogString, log);
+ final String printString = String.format("%s\t%d\t%s\n",
+ mDateFormat.format(mDate), currentTime, log);
if (LatinImeLogger.sDBG) {
Log.d(USABILITY_TAG, "Write: " + log);
}
diff --git a/java/src/com/android/inputmethod/latin/WordComposer.java b/java/src/com/android/inputmethod/latin/WordComposer.java
index 9f23f174f..cabf68099 100644
--- a/java/src/com/android/inputmethod/latin/WordComposer.java
+++ b/java/src/com/android/inputmethod/latin/WordComposer.java
@@ -20,8 +20,6 @@ import com.android.inputmethod.keyboard.Key;
import com.android.inputmethod.keyboard.KeyDetector;
import com.android.inputmethod.keyboard.Keyboard;
import com.android.inputmethod.keyboard.KeyboardActionListener;
-import com.android.inputmethod.latin.spellcheck.AndroidSpellCheckerService;
-import com.android.inputmethod.latin.spellcheck.SpellCheckerProximityInfo;
import java.util.ArrayList;
import java.util.Arrays;
@@ -127,12 +125,8 @@ public class WordComposer {
final int[] codes;
final int keyX;
final int keyY;
- if (x == KeyboardActionListener.SPELL_CHECKER_COORDINATE
- || y == KeyboardActionListener.SPELL_CHECKER_COORDINATE) {
- // only used for tests in InputLogicTests
- addKeyForSpellChecker(primaryCode, AndroidSpellCheckerService.SCRIPT_LATIN);
- return;
- } else if (x == KeyboardActionListener.SUGGESTION_STRIP_COORDINATE
+ if (null == keyDetector
+ || x == KeyboardActionListener.SUGGESTION_STRIP_COORDINATE
|| y == KeyboardActionListener.SUGGESTION_STRIP_COORDINATE
|| x == KeyboardActionListener.NOT_A_TOUCH_COORDINATE
|| y == KeyboardActionListener.NOT_A_TOUCH_COORDINATE) {
@@ -149,27 +143,6 @@ public class WordComposer {
add(primaryCode, codes, keyX, keyY);
}
- // TODO: remove this function
- public void addKeyForSpellChecker(int primaryCode, int script) {
- final int[] proximities;
- final int proximityIndex =
- SpellCheckerProximityInfo.getIndexOfCodeForScript(primaryCode, script);
- if (-1 == proximityIndex) {
- proximities = new int[] { primaryCode };
- } else {
- // TODO: an initial examination seems to reveal this is actually used
- // read-only. It should be possible to compute the arrays statically once
- // and skip doing a copy each time here.
- proximities = Arrays.copyOfRange(
- SpellCheckerProximityInfo.getProximityForScript(script),
- proximityIndex,
- proximityIndex + SpellCheckerProximityInfo.ROW_SIZE);
- }
- add(primaryCode, proximities,
- KeyboardActionListener.NOT_A_TOUCH_COORDINATE,
- KeyboardActionListener.NOT_A_TOUCH_COORDINATE);
- }
-
/**
* Add a new keystroke, with codes[0] containing the pressed key's unicode and the rest of
* the array containing unicode for adjacent keys, sorted by reducing probability/proximity.
@@ -197,8 +170,7 @@ public class WordComposer {
/**
* Internal method to retrieve reasonable proximity info for a character.
*/
- private void addKeyInfo(final int codePoint, final Keyboard keyboard,
- final KeyDetector keyDetector) {
+ private void addKeyInfo(final int codePoint, final Keyboard keyboard) {
for (final Key key : keyboard.mKeys) {
if (key.mCode == codePoint) {
final int x = key.mX + key.mWidth / 2;
@@ -216,27 +188,16 @@ public class WordComposer {
* Set the currently composing word to the one passed as an argument.
* This will register NOT_A_COORDINATE for X and Ys, and use the passed keyboard for proximity.
*/
- private void setComposingWord(final CharSequence word, final Keyboard keyboard,
- final KeyDetector keyDetector) {
+ public void setComposingWord(final CharSequence word, final Keyboard keyboard) {
reset();
final int length = word.length();
for (int i = 0; i < length; i = Character.offsetByCodePoints(word, i, 1)) {
int codePoint = Character.codePointAt(word, i);
- addKeyInfo(codePoint, keyboard, keyDetector);
+ addKeyInfo(codePoint, keyboard);
}
}
/**
- * Shortcut for the above method, this will create a new KeyDetector for the passed keyboard.
- */
- public void setComposingWord(final CharSequence word, final Keyboard keyboard) {
- final KeyDetector keyDetector = new KeyDetector(0);
- keyDetector.setKeyboard(keyboard, 0, 0);
- keyDetector.setProximityCorrectionEnabled(true);
- setComposingWord(word, keyboard, keyDetector);
- }
-
- /**
* Delete the last keystroke as a result of hitting backspace.
*/
public void deleteLast() {
diff --git a/java/src/com/android/inputmethod/latin/define/ProductionFlag.java b/java/src/com/android/inputmethod/latin/define/ProductionFlag.java
new file mode 100644
index 000000000..cfb1d09cc
--- /dev/null
+++ b/java/src/com/android/inputmethod/latin/define/ProductionFlag.java
@@ -0,0 +1,21 @@
+/*
+ * Copyright (C) 2012 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+
+package com.android.inputmethod.latin.define;
+
+public class ProductionFlag {
+ public static final boolean IS_EXPERIMENTAL = false;
+}
diff --git a/java/src/com/android/inputmethod/latin/makedict/FusionDictionary.java b/java/src/com/android/inputmethod/latin/makedict/FusionDictionary.java
index d19672181..64fcd7f1a 100644
--- a/java/src/com/android/inputmethod/latin/makedict/FusionDictionary.java
+++ b/java/src/com/android/inputmethod/latin/makedict/FusionDictionary.java
@@ -21,7 +21,6 @@ import java.util.Arrays;
import java.util.Collections;
import java.util.Iterator;
import java.util.LinkedList;
-import java.util.List;
/**
* A dictionary that can fusion heads and tails of words for more compression.
@@ -60,11 +59,24 @@ public class FusionDictionary implements Iterable<Word> {
*/
public static class WeightedString {
final String mWord;
- final int mFrequency;
+ int mFrequency;
public WeightedString(String word, int frequency) {
mWord = word;
mFrequency = frequency;
}
+
+ @Override
+ public int hashCode() {
+ return Arrays.hashCode(new Object[] { mWord, mFrequency });
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (o == this) return true;
+ if (!(o instanceof WeightedString)) return false;
+ WeightedString w = (WeightedString)o;
+ return mWord.equals(w.mWord) && mFrequency == w.mFrequency;
+ }
}
/**
@@ -82,10 +94,10 @@ public class FusionDictionary implements Iterable<Word> {
public static class CharGroup {
public static final int NOT_A_TERMINAL = -1;
final int mChars[];
- final ArrayList<WeightedString> mShortcutTargets;
- final ArrayList<WeightedString> mBigrams;
- final int mFrequency; // NOT_A_TERMINAL == mFrequency indicates this is not a terminal.
- final boolean mIsShortcutOnly; // Only valid if this is a terminal.
+ ArrayList<WeightedString> mShortcutTargets;
+ ArrayList<WeightedString> mBigrams;
+ int mFrequency; // NOT_A_TERMINAL == mFrequency indicates this is not a terminal.
+ boolean mIsShortcutOnly; // Only valid if this is a terminal.
Node mChildren;
// The two following members to help with binary generation
int mCachedSize;
@@ -134,6 +146,102 @@ public class FusionDictionary implements Iterable<Word> {
assert(mChars.length > 0);
return 1 < mChars.length;
}
+
+ /**
+ * Adds a word to the bigram list. Updates the frequency if the word already
+ * exists.
+ */
+ public void addBigram(final String word, final int frequency) {
+ if (mBigrams == null) {
+ mBigrams = new ArrayList<WeightedString>();
+ }
+ WeightedString bigram = getBigram(word);
+ if (bigram != null) {
+ bigram.mFrequency = frequency;
+ } else {
+ bigram = new WeightedString(word, frequency);
+ mBigrams.add(bigram);
+ }
+ }
+
+ /**
+ * Gets the shortcut target for the given word. Returns null if the word is not in the
+ * shortcut list.
+ */
+ public WeightedString getShortcut(final String word) {
+ if (mShortcutTargets != null) {
+ final int size = mShortcutTargets.size();
+ for (int i = 0; i < size; ++i) {
+ WeightedString shortcut = mShortcutTargets.get(i);
+ if (shortcut.mWord.equals(word)) {
+ return shortcut;
+ }
+ }
+ }
+ return null;
+ }
+
+ /**
+ * Gets the bigram for the given word.
+ * Returns null if the word is not in the bigrams list.
+ */
+ public WeightedString getBigram(final String word) {
+ if (mBigrams != null) {
+ final int size = mBigrams.size();
+ for (int i = 0; i < size; ++i) {
+ WeightedString bigram = mBigrams.get(i);
+ if (bigram.mWord.equals(word)) {
+ return bigram;
+ }
+ }
+ }
+ return null;
+ }
+
+ /**
+ * Updates the CharGroup with the given properties. Adds the shortcut and bigram lists to
+ * the existing ones if any. Note: unigram, bigram, and shortcut frequencies are only
+ * updated if they are higher than the existing ones.
+ */
+ public void update(int frequency, ArrayList<WeightedString> shortcutTargets,
+ ArrayList<WeightedString> bigrams, boolean isShortcutOnly) {
+ if (frequency > mFrequency) {
+ mFrequency = frequency;
+ }
+ if (shortcutTargets != null) {
+ if (mShortcutTargets == null) {
+ mShortcutTargets = shortcutTargets;
+ } else {
+ final int size = shortcutTargets.size();
+ for (int i = 0; i < size; ++i) {
+ final WeightedString shortcut = shortcutTargets.get(i);
+ final WeightedString existingShortcut = getShortcut(shortcut.mWord);
+ if (existingShortcut == null) {
+ mShortcutTargets.add(shortcut);
+ } else if (existingShortcut.mFrequency < shortcut.mFrequency) {
+ existingShortcut.mFrequency = shortcut.mFrequency;
+ }
+ }
+ }
+ }
+ if (bigrams != null) {
+ if (mBigrams == null) {
+ mBigrams = bigrams;
+ } else {
+ final int size = bigrams.size();
+ for (int i = 0; i < size; ++i) {
+ final WeightedString bigram = bigrams.get(i);
+ final WeightedString existingBigram = getBigram(bigram.mWord);
+ if (existingBigram == null) {
+ mBigrams.add(bigram);
+ } else if (existingBigram.mFrequency < bigram.mFrequency) {
+ existingBigram.mFrequency = bigram.mFrequency;
+ }
+ }
+ }
+ }
+ mIsShortcutOnly = isShortcutOnly;
+ }
}
/**
@@ -247,6 +355,27 @@ public class FusionDictionary implements Iterable<Word> {
}
/**
+ * Helper method to add a new bigram to the dictionary.
+ *
+ * @param word1 the previous word of the context
+ * @param word2 the next word of the context
+ * @param frequency the bigram frequency
+ */
+ public void setBigram(final String word1, final String word2, final int frequency) {
+ CharGroup charGroup = findWordInTree(mRoot, word1);
+ if (charGroup != null) {
+ final CharGroup charGroup2 = findWordInTree(mRoot, word2);
+ if (charGroup2 == null) {
+ // TODO: refactor with the identical code in addNeutralWords
+ add(getCodePoints(word2), 0, null, null, false /* isShortcutOnly */);
+ }
+ charGroup.addBigram(word2, frequency);
+ } else {
+ throw new RuntimeException("First word of bigram not found");
+ }
+ }
+
+ /**
* Add a word to this dictionary.
*
* The shortcuts and bigrams, if any, have to be in the dictionary already. If they aren't,
@@ -294,17 +423,9 @@ public class FusionDictionary implements Iterable<Word> {
if (differentCharIndex == currentGroup.mChars.length) {
if (charIndex + differentCharIndex >= word.length) {
// The new word is a prefix of an existing word, but the node on which it
- // should end already exists as is.
- if (currentGroup.mFrequency > 0) {
- throw new RuntimeException("Such a word already exists in the dictionary : "
- + new String(word, 0, word.length));
- } else {
- final CharGroup newNode = new CharGroup(currentGroup.mChars,
- shortcutTargets, bigrams, frequency, currentGroup.mChildren,
- isShortcutOnly);
- currentNode.mData.set(nodeIndex, newNode);
- checkStack(currentNode);
- }
+ // should end already exists as is. Since the old CharNode was not a terminal,
+ // make it one by filling in its frequency and other attributes
+ currentGroup.update(frequency, shortcutTargets, bigrams, isShortcutOnly);
} else {
// The new word matches the full old word and extends past it.
// We only have to create a new node and add it to the end of this.
@@ -316,19 +437,9 @@ public class FusionDictionary implements Iterable<Word> {
}
} else {
if (0 == differentCharIndex) {
- // Exact same word. Check the frequency is 0 or NOT_A_TERMINAL, and update.
- if (0 != frequency) {
- if (0 < currentGroup.mFrequency) {
- throw new RuntimeException("This word already exists with frequency "
- + currentGroup.mFrequency + " : "
- + new String(word, 0, word.length));
- }
- final CharGroup newGroup = new CharGroup(word,
- currentGroup.mShortcutTargets, currentGroup.mBigrams,
- frequency, currentGroup.mChildren,
- currentGroup.mIsShortcutOnly && isShortcutOnly);
- currentNode.mData.set(nodeIndex, newGroup);
- }
+ // Exact same word. Update the frequency if higher. This will also add the
+ // new bigrams to the existing bigram list if it already exists.
+ currentGroup.update(frequency, shortcutTargets, bigrams, isShortcutOnly);
} else {
// Partial prefix match only. We have to replace the current node with a node
// containing the current prefix and create two new ones for the tails.
@@ -400,16 +511,11 @@ public class FusionDictionary implements Iterable<Word> {
* is ignored.
* This comparator imposes orderings that are inconsistent with equals.
*/
- static private class CharGroupComparator implements java.util.Comparator {
- public int compare(Object o1, Object o2) {
- final CharGroup c1 = (CharGroup)o1;
- final CharGroup c2 = (CharGroup)o2;
+ static private class CharGroupComparator implements java.util.Comparator<CharGroup> {
+ public int compare(CharGroup c1, CharGroup c2) {
if (c1.mChars[0] == c2.mChars[0]) return 0;
return c1.mChars[0] < c2.mChars[0] ? -1 : 1;
}
- public boolean equals(Object o) {
- return o instanceof CharGroupComparator;
- }
}
final static private CharGroupComparator CHARGROUP_COMPARATOR = new CharGroupComparator();
@@ -417,7 +523,7 @@ public class FusionDictionary implements Iterable<Word> {
* Finds the insertion index of a character within a node.
*/
private static int findInsertionIndex(final Node node, int character) {
- final List data = node.mData;
+ final ArrayList<CharGroup> data = node.mData;
final CharGroup reference = new CharGroup(new int[] { character }, null, null, 0,
false /* isShortcutOnly */);
int result = Collections.binarySearch(data, reference, CHARGROUP_COMPARATOR);
diff --git a/java/src/com/android/inputmethod/latin/makedict/Word.java b/java/src/com/android/inputmethod/latin/makedict/Word.java
index c2c01e1f8..4e0ab1049 100644
--- a/java/src/com/android/inputmethod/latin/makedict/Word.java
+++ b/java/src/com/android/inputmethod/latin/makedict/Word.java
@@ -19,6 +19,7 @@ package com.android.inputmethod.latin.makedict;
import com.android.inputmethod.latin.makedict.FusionDictionary.WeightedString;
import java.util.ArrayList;
+import java.util.Arrays;
/**
* Utility class for a word with a frequency.
@@ -32,6 +33,8 @@ public class Word implements Comparable<Word> {
final ArrayList<WeightedString> mShortcutTargets;
final ArrayList<WeightedString> mBigrams;
+ private int mHashCode = 0;
+
public Word(final String word, final int frequency,
final ArrayList<WeightedString> shortcutTargets,
final ArrayList<WeightedString> bigrams, final boolean isShortcutOnly) {
@@ -42,6 +45,16 @@ public class Word implements Comparable<Word> {
mIsShortcutOnly = isShortcutOnly;
}
+ private static int computeHashCode(Word word) {
+ return Arrays.hashCode(new Object[] {
+ word.mWord,
+ word.mFrequency,
+ word.mIsShortcutOnly,
+ word.mShortcutTargets.hashCode(),
+ word.mBigrams.hashCode()
+ });
+ }
+
/**
* Three-way comparison.
*
@@ -63,10 +76,20 @@ public class Word implements Comparable<Word> {
*/
@Override
public boolean equals(Object o) {
+ if (o == this) return true;
if (!(o instanceof Word)) return false;
Word w = (Word)o;
return mFrequency == w.mFrequency && mWord.equals(w.mWord)
+ && mIsShortcutOnly == w.mIsShortcutOnly
&& mShortcutTargets.equals(w.mShortcutTargets)
&& mBigrams.equals(w.mBigrams);
}
+
+ @Override
+ public int hashCode() {
+ if (mHashCode == 0) {
+ mHashCode = computeHashCode(this);
+ }
+ return mHashCode;
+ }
}
diff --git a/java/src/com/android/inputmethod/latin/spellcheck/AndroidSpellCheckerService.java b/java/src/com/android/inputmethod/latin/spellcheck/AndroidSpellCheckerService.java
index 35a5c0f52..5a173857e 100644
--- a/java/src/com/android/inputmethod/latin/spellcheck/AndroidSpellCheckerService.java
+++ b/java/src/com/android/inputmethod/latin/spellcheck/AndroidSpellCheckerService.java
@@ -353,6 +353,11 @@ public class AndroidSpellCheckerService extends SpellCheckerService
@Override
public boolean onUnbind(final Intent intent) {
+ closeAllDictionaries();
+ return false;
+ }
+
+ private void closeAllDictionaries() {
final Map<String, DictionaryPool> oldPools = mDictionaryPools;
mDictionaryPools = Collections.synchronizedMap(new TreeMap<String, DictionaryPool>());
final Map<String, Dictionary> oldUserDictionaries = mUserDictionaries;
@@ -378,7 +383,6 @@ public class AndroidSpellCheckerService extends SpellCheckerService
dictToClose.close();
}
}
- return false;
}
private DictionaryPool getDictionaryPool(final String locale) {
@@ -570,7 +574,16 @@ public class AndroidSpellCheckerService extends SpellCheckerService
final WordComposer composer = new WordComposer();
final int length = text.length();
for (int i = 0; i < length; i = text.offsetByCodePoints(i, 1)) {
- composer.addKeyForSpellChecker(text.codePointAt(i), mScript);
+ final int codePoint = text.codePointAt(i);
+ // The getXYForCodePointAndScript method returns (Y << 16) + X
+ final int xy = SpellCheckerProximityInfo.getXYForCodePointAndScript(
+ codePoint, mScript);
+ if (SpellCheckerProximityInfo.NOT_A_COORDINATE_PAIR == xy) {
+ composer.add(codePoint, WordComposer.NOT_A_COORDINATE,
+ WordComposer.NOT_A_COORDINATE, null);
+ } else {
+ composer.add(codePoint, xy & 0xFFFF, xy >> 16, null);
+ }
}
final int capitalizeType = getCapitalizationType(text);
diff --git a/java/src/com/android/inputmethod/latin/spellcheck/SpellCheckerProximityInfo.java b/java/src/com/android/inputmethod/latin/spellcheck/SpellCheckerProximityInfo.java
index db3544987..0103e8423 100644
--- a/java/src/com/android/inputmethod/latin/spellcheck/SpellCheckerProximityInfo.java
+++ b/java/src/com/android/inputmethod/latin/spellcheck/SpellCheckerProximityInfo.java
@@ -30,17 +30,25 @@ public class SpellCheckerProximityInfo {
// as the size of the passed array afterwards so they can't be different.
final public static int ROW_SIZE = ProximityInfo.MAX_PROXIMITY_CHARS_SIZE;
+ // The number of keys in a row of the grid used by the spell checker.
+ final public static int PROXIMITY_GRID_WIDTH = 11;
+ // The number of rows in the grid used by the spell checker.
+ final public static int PROXIMITY_GRID_HEIGHT = 3;
+
+ final private static int NOT_AN_INDEX = -1;
+ final public static int NOT_A_COORDINATE_PAIR = -1;
+
// Helper methods
final protected static void buildProximityIndices(final int[] proximity,
final TreeMap<Integer, Integer> indices) {
for (int i = 0; i < proximity.length; i += ROW_SIZE) {
- if (NUL != proximity[i]) indices.put(proximity[i], i);
+ if (NUL != proximity[i]) indices.put(proximity[i], i / ROW_SIZE);
}
}
final protected static int computeIndex(final int characterCode,
final TreeMap<Integer, Integer> indices) {
final Integer result = indices.get(characterCode);
- if (null == result) return -1;
+ if (null == result) return NOT_AN_INDEX;
return result;
}
@@ -64,6 +72,9 @@ public class SpellCheckerProximityInfo {
// 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.
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.
'q', 'w', 's', 'a', 'z', NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL,
'w', 'q', 'a', 's', 'd', 'e', 'x', NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL,
'e', 'w', 's', 'd', 'f', 'r', 'a', 'i', 'o', 'u', NUL, NUL, NUL, NUL, NUL, NUL,
@@ -76,9 +87,10 @@ public class SpellCheckerProximityInfo {
'p', 'o', 'l', NUL, NUL, NUL, NUL, NUL, NUL, NUL, 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.
'a', 'z', 'x', 's', 'w', 'q', 'e', 'i', 'o', 'u', NUL, NUL, NUL, NUL, NUL, NUL,
's', 'q', 'a', 'z', 'x', 'c', 'd', 'e', 'w', NUL, NUL, NUL, NUL, NUL, NUL, NUL,
- 'd', 'w', 's', 'x', 'c', 'v', 'f', 'r', 'e', 'w', NUL, NUL, NUL, NUL, NUL, NUL,
+ 'd', 'w', 's', 'x', 'c', 'v', 'f', 'r', 'e', NUL, NUL, NUL, NUL, NUL, NUL, NUL,
'f', 'e', 'd', 'c', 'v', 'b', 'g', 't', 'r', NUL, NUL, NUL, NUL, NUL, NUL, NUL,
'g', 'r', 'f', 'v', 'b', 'n', 'h', 'y', 't', NUL, NUL, NUL, NUL, NUL, NUL, NUL,
'h', 't', 'g', 'b', 'n', 'm', 'j', 'u', 'y', NUL, NUL, NUL, NUL, NUL, NUL, NUL,
@@ -88,6 +100,7 @@ public 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, NUL,
+ // Proximity for row 3. See comment above about size.
'z', 'a', 's', 'd', 'x', 't', 'g', 'h', 'j', 'u', 'q', 'e', NUL, NUL, NUL, NUL,
'x', 'z', 'a', 's', 'd', 'c', NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL,
'c', 'x', 's', 'd', 'f', 'v', NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL,
@@ -109,9 +122,12 @@ public class SpellCheckerProximityInfo {
private static class Cyrillic {
final private static TreeMap<Integer, Integer> INDICES = new TreeMap<Integer, Integer>();
+ // TODO: The following table is solely based on the keyboard layout. Consult with Russian
+ // speakers on commonly misspelled words/letters.
final static int[] PROXIMITY = {
- // TODO: This table is solely based on the keyboard layout. Consult with Russian
- // speakers on commonly misspelled words/letters.
+ // 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.
'й', 'ц', 'ф', 'ы', NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL,
'ц', 'й', 'ф', 'ы', 'в', 'у', NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL,
'у', 'ц', 'ы', 'в', 'а', 'к', NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL,
@@ -124,6 +140,7 @@ public 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,
@@ -136,6 +153,7 @@ public 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,14 +184,31 @@ public class SpellCheckerProximityInfo {
throw new RuntimeException("Wrong script supplied: " + script);
}
}
- public static int getIndexOfCodeForScript(final int characterCode, final int script) {
+
+ private static int getIndexOfCodeForScript(final int codePoint, final int script) {
switch (script) {
case AndroidSpellCheckerService.SCRIPT_LATIN:
- return Latin.getIndexOf(characterCode);
+ return Latin.getIndexOf(codePoint);
case AndroidSpellCheckerService.SCRIPT_CYRILLIC:
- return Cyrillic.getIndexOf(characterCode);
+ return Cyrillic.getIndexOf(codePoint);
default:
throw new RuntimeException("Wrong script supplied: " + script);
}
}
+
+ // Returns (Y << 16) + X to avoid creating a temporary object. This is okay because
+ // X and Y are limited to PROXIMITY_GRID_WIDTH resp. PROXIMITY_GRID_HEIGHT which is very
+ // inferior to 1 << 16
+ // As an exception, this returns NOT_A_COORDINATE_PAIR if the key is not on the grid
+ public static int getXYForCodePointAndScript(final int codePoint, final int script) {
+ final int index = getIndexOfCodeForScript(codePoint, script);
+ if (NOT_AN_INDEX == index) return NOT_A_COORDINATE_PAIR;
+ final int y = index / PROXIMITY_GRID_WIDTH;
+ final int x = index % PROXIMITY_GRID_WIDTH;
+ if (y > PROXIMITY_GRID_HEIGHT) {
+ // Safety check, should be entirely useless
+ throw new RuntimeException("Wrong y coordinate in spell checker proximity");
+ }
+ return (y << 16) + x;
+ }
}