aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--java/res/values/donottranslate.xml3
-rw-r--r--java/res/values/strings.xml2
-rw-r--r--java/res/xml/method.xml2
-rw-r--r--java/res/xml/prefs_for_debug.xml6
-rw-r--r--java/src/com/android/inputmethod/compat/EditorInfoCompatUtils.java25
-rw-r--r--java/src/com/android/inputmethod/keyboard/Keyboard.java20
-rw-r--r--java/src/com/android/inputmethod/keyboard/KeyboardActionListener.java10
-rw-r--r--java/src/com/android/inputmethod/keyboard/KeyboardSet.java69
-rw-r--r--java/src/com/android/inputmethod/keyboard/KeyboardSwitcher.java105
-rw-r--r--java/src/com/android/inputmethod/keyboard/LatinKeyboardView.java20
-rw-r--r--java/src/com/android/inputmethod/keyboard/MiniKeyboardView.java9
-rw-r--r--java/src/com/android/inputmethod/keyboard/PointerTracker.java14
-rw-r--r--java/src/com/android/inputmethod/keyboard/internal/KeyboardState.java149
-rw-r--r--java/src/com/android/inputmethod/latin/DebugSettings.java3
-rw-r--r--java/src/com/android/inputmethod/latin/EditingUtils.java17
-rw-r--r--java/src/com/android/inputmethod/latin/LatinIME.java78
-rw-r--r--java/src/com/android/inputmethod/latin/SettingsValues.java6
-rw-r--r--java/src/com/android/inputmethod/latin/SubtypeSwitcher.java2
-rw-r--r--java/src/com/android/inputmethod/latin/Suggest.java2
-rw-r--r--java/src/com/android/inputmethod/latin/Utils.java19
-rw-r--r--java/src/com/android/inputmethod/latin/suggestions/MoreSuggestionsView.java8
-rw-r--r--native/jni/com_android_inputmethod_latin_BinaryDictionary.cpp24
-rw-r--r--native/jni/jni_common.cpp10
-rw-r--r--native/src/bigram_dictionary.cpp10
-rw-r--r--native/src/binary_format.h4
-rw-r--r--native/src/correction.cpp191
-rw-r--r--native/src/correction.h5
-rw-r--r--native/src/debug.h6
-rw-r--r--native/src/defines.h57
-rw-r--r--native/src/dictionary.cpp4
-rw-r--r--native/src/dictionary.h75
-rw-r--r--native/src/proximity_info.cpp8
-rw-r--r--native/src/unigram_dictionary.cpp188
-rw-r--r--native/src/unigram_dictionary.h13
-rw-r--r--native/src/words_priority_queue.h21
-rw-r--r--native/src/words_priority_queue_pool.h56
-rw-r--r--tests/AndroidManifest.xml2
-rw-r--r--tests/data/bigramlist.xml2
-rw-r--r--tests/res/raw/test.dictbin2829 -> 1528 bytes
-rw-r--r--tests/src/com/android/inputmethod/keyboard/internal/KeyboardStateNonDistinctTests.java430
-rw-r--r--tests/src/com/android/inputmethod/keyboard/internal/KeyboardStateTests.java278
-rw-r--r--tests/src/com/android/inputmethod/keyboard/internal/MockKeyboardSwitcher.java157
-rw-r--r--tests/src/com/android/inputmethod/latin/SubtypeLocaleTests.java49
-rw-r--r--tests/src/com/android/inputmethod/latin/SuggestHelper.java14
-rw-r--r--tests/src/com/android/inputmethod/latin/SuggestTests.java7
-rw-r--r--tests/src/com/android/inputmethod/latin/SuggestTestsBase.java18
-rw-r--r--tests/src/com/android/inputmethod/latin/UserBigramSuggestHelper.java8
-rw-r--r--tests/src/com/android/inputmethod/latin/UserBigramSuggestTests.java4
-rw-r--r--tests/src/com/android/inputmethod/latin/UtilsTests.java2
-rw-r--r--tools/makedict/src/com/android/inputmethod/latin/BinaryDictInputOutput.java88
-rw-r--r--tools/makedict/src/com/android/inputmethod/latin/FusionDictionary.java85
-rw-r--r--tools/makedict/src/com/android/inputmethod/latin/Word.java4
-rw-r--r--tools/makedict/src/com/android/inputmethod/latin/XmlDictInputOutput.java16
53 files changed, 1577 insertions, 828 deletions
diff --git a/java/res/values/donottranslate.xml b/java/res/values/donottranslate.xml
index 1f7736d9c..8bd25c00d 100644
--- a/java/res/values/donottranslate.xml
+++ b/java/res/values/donottranslate.xml
@@ -120,6 +120,7 @@
<!-- Title for Latin keyboard debug settings activity / dialog -->
<string name="english_ime_debug_settings">Android keyboard Debug settings</string>
<string name="prefs_debug_mode">Debug Mode</string>
+ <string name="prefs_force_non_distinct_multitouch">Force non-distinct multitouch</string>
<!-- Keyboard theme names -->
<string name="layout_basic">Basic</string>
@@ -161,6 +162,8 @@
<!-- Generic subtype label -->
<string name="subtype_generic">%s</string>
+ <!-- Description for generic QWERTY keyboard subtype -->
+ <string name="subtype_generic_qwerty">%s (QWERTY)</string>
<!-- dictionary pack package name /settings activity (for shared prefs and settings) -->
<string name="dictionary_pack_package_name">com.google.android.inputmethod.latin.dictionarypack</string>
diff --git a/java/res/values/strings.xml b/java/res/values/strings.xml
index d860a1b23..f7d8b4533 100644
--- a/java/res/values/strings.xml
+++ b/java/res/values/strings.xml
@@ -335,8 +335,6 @@
<!-- Title of the item to change the keyboard theme [CHAR LIMIT=20]-->
<string name="keyboard_layout">Keyboard theme</string>
- <!-- Description for German QWERTY keyboard subtype [CHAR LIMIT=22] -->
- <string name="subtype_de_qwerty">German QWERTY</string>
<!-- Description for English (United Kingdom) keyboard subtype [CHAR LIMIT=22] -->
<string name="subtype_en_GB">English (UK)</string>
<!-- Description for English (United States) keyboard subtype [CHAR LIMIT=22] -->
diff --git a/java/res/xml/method.xml b/java/res/xml/method.xml
index a3b1d5859..6c827363c 100644
--- a/java/res/xml/method.xml
+++ b/java/res/xml/method.xml
@@ -71,7 +71,7 @@
android:imeSubtypeExtraValue="AsciiCapable,SupportTouchPositionCorrection"
/>
<subtype android:icon="@drawable/ic_subtype_keyboard"
- android:label="@string/subtype_de_qwerty"
+ android:label="@string/subtype_generic_qwerty"
android:imeSubtypeLocale="de"
android:imeSubtypeMode="keyboard"
android:imeSubtypeExtraValue="AsciiCapable,KeyboardLocale=de_ZZ,SupportTouchPositionCorrection"
diff --git a/java/res/xml/prefs_for_debug.xml b/java/res/xml/prefs_for_debug.xml
index 80613a56f..f38b85f0b 100644
--- a/java/res/xml/prefs_for_debug.xml
+++ b/java/res/xml/prefs_for_debug.xml
@@ -42,4 +42,10 @@
android:defaultValue="false"
/>
+ <CheckBoxPreference
+ android:key="force_non_distinct_multitouch"
+ android:title="@string/prefs_force_non_distinct_multitouch"
+ android:persistent="true"
+ android:defaultValue="false"
+ />
</PreferenceScreen>
diff --git a/java/src/com/android/inputmethod/compat/EditorInfoCompatUtils.java b/java/src/com/android/inputmethod/compat/EditorInfoCompatUtils.java
index bcdcef7dc..e1db36088 100644
--- a/java/src/com/android/inputmethod/compat/EditorInfoCompatUtils.java
+++ b/java/src/com/android/inputmethod/compat/EditorInfoCompatUtils.java
@@ -26,12 +26,16 @@ public class EditorInfoCompatUtils {
EditorInfo.class, "IME_FLAG_NAVIGATE_NEXT");
private static final Field FIELD_IME_FLAG_NAVIGATE_PREVIOUS = CompatUtils.getField(
EditorInfo.class, "IME_FLAG_NAVIGATE_PREVIOUS");
+ private static final Field FIELD_IME_FLAG_FORCE_ASCII = CompatUtils.getField(
+ EditorInfo.class, "IME_FLAG_FORCE_ASCII");
private static final Field FIELD_IME_ACTION_PREVIOUS = CompatUtils.getField(
EditorInfo.class, "IME_ACTION_PREVIOUS");
private static final Integer OBJ_IME_FLAG_NAVIGATE_NEXT = (Integer) CompatUtils
.getFieldValue(null, null, FIELD_IME_FLAG_NAVIGATE_NEXT);
private static final Integer OBJ_IME_FLAG_NAVIGATE_PREVIOUS = (Integer) CompatUtils
.getFieldValue(null, null, FIELD_IME_FLAG_NAVIGATE_PREVIOUS);
+ private static final Integer OBJ_IME_FLAG_FORCE_ASCII = (Integer) CompatUtils
+ .getFieldValue(null, null, FIELD_IME_FLAG_FORCE_ASCII);
private static final Integer OBJ_IME_ACTION_PREVIOUS = (Integer) CompatUtils
.getFieldValue(null, null, FIELD_IME_ACTION_PREVIOUS);
@@ -47,6 +51,12 @@ public class EditorInfoCompatUtils {
return (imeOptions & OBJ_IME_FLAG_NAVIGATE_PREVIOUS) != 0;
}
+ public static boolean hasFlagForceAscii(int imeOptions) {
+ if (OBJ_IME_FLAG_FORCE_ASCII == null)
+ return false;
+ return (imeOptions & OBJ_IME_FLAG_FORCE_ASCII) != 0;
+ }
+
public static void performEditorActionNext(InputConnection ic) {
ic.performEditorAction(EditorInfo.IME_ACTION_NEXT);
}
@@ -93,10 +103,19 @@ public class EditorInfoCompatUtils {
break;
}
}
+ final StringBuilder flags = new StringBuilder();
if ((imeOptions & EditorInfo.IME_FLAG_NO_ENTER_ACTION) != 0) {
- return "flagNoEnterAction|" + action;
- } else {
- return action;
+ flags.append("flagNoEnterAction|");
+ }
+ if (hasFlagNavigateNext(imeOptions)) {
+ flags.append("flagNavigateNext|");
+ }
+ if (hasFlagNavigatePrevious(imeOptions)) {
+ flags.append("flagNavigatePrevious|");
+ }
+ if (hasFlagForceAscii(imeOptions)) {
+ flags.append("flagForceAscii|");
}
+ return flags + action;
}
}
diff --git a/java/src/com/android/inputmethod/keyboard/Keyboard.java b/java/src/com/android/inputmethod/keyboard/Keyboard.java
index c1d024cef..5816e5680 100644
--- a/java/src/com/android/inputmethod/keyboard/Keyboard.java
+++ b/java/src/com/android/inputmethod/keyboard/Keyboard.java
@@ -434,9 +434,23 @@ public class Keyboard {
case CODE_SHORTCUT: return "shortcut";
case CODE_UNSPECIFIED: return "unspec";
default:
- if (code < 0) Log.w(TAG, "Unknow negative key code=" + code);
- if (code < 0x100) return String.format("\\u%02x", code);
- return String.format("\\u04x", code);
+ if (code <= 0) Log.w(TAG, "Unknown non-positive key code=" + code);
+ if (code < CODE_SPACE) return String.format("'\\u%02x'", code);
+ if (code < 0x100) return String.format("'%c'", code);
+ return String.format("'\\u%04x'", code);
+ }
+ }
+
+ public static String toThemeName(int themeId) {
+ // This should be aligned with theme-*.xml resource files' themeId attribute.
+ switch (themeId) {
+ case 0: return "Basic";
+ case 1: return "BasicHighContrast";
+ case 5: return "IceCreamSandwich";
+ case 6: return "Stone";
+ case 7: return "StoneBold";
+ case 8: return "GingerBread";
+ default: return null;
}
}
diff --git a/java/src/com/android/inputmethod/keyboard/KeyboardActionListener.java b/java/src/com/android/inputmethod/keyboard/KeyboardActionListener.java
index 6f5420882..dce2c37f2 100644
--- a/java/src/com/android/inputmethod/keyboard/KeyboardActionListener.java
+++ b/java/src/com/android/inputmethod/keyboard/KeyboardActionListener.java
@@ -24,10 +24,8 @@ public interface KeyboardActionListener {
*
* @param primaryCode the unicode of the key being pressed. If the touch is not on a valid key,
* the value will be zero.
- * @param withSliding true if pressing has occurred because the user slid finger from other key
- * to this key without releasing the finger.
*/
- public void onPress(int primaryCode, boolean withSliding);
+ public void onPressKey(int primaryCode);
/**
* Called when the user releases a key. This is sent after the {@link #onCodeInput} is called.
@@ -37,7 +35,7 @@ public interface KeyboardActionListener {
* @param withSliding true if releasing has occurred because the user slid finger from the key
* to other key without releasing the finger.
*/
- public void onRelease(int primaryCode, boolean withSliding);
+ public void onReleaseKey(int primaryCode, boolean withSliding);
/**
* Send a key code to the listener.
@@ -79,9 +77,9 @@ public interface KeyboardActionListener {
public static class Adapter implements KeyboardActionListener {
@Override
- public void onPress(int primaryCode, boolean withSliding) {}
+ public void onPressKey(int primaryCode) {}
@Override
- public void onRelease(int primaryCode, boolean withSliding) {}
+ public void onReleaseKey(int primaryCode, boolean withSliding) {}
@Override
public void onCodeInput(int primaryCode, int[] keyCodes, int x, int y) {}
@Override
diff --git a/java/src/com/android/inputmethod/keyboard/KeyboardSet.java b/java/src/com/android/inputmethod/keyboard/KeyboardSet.java
index a2e784c99..285252044 100644
--- a/java/src/com/android/inputmethod/keyboard/KeyboardSet.java
+++ b/java/src/com/android/inputmethod/keyboard/KeyboardSet.java
@@ -17,6 +17,7 @@
package com.android.inputmethod.keyboard;
import android.content.Context;
+import android.content.res.Configuration;
import android.content.res.Resources;
import android.content.res.TypedArray;
import android.content.res.XmlResourceParser;
@@ -24,12 +25,11 @@ import android.util.Log;
import android.util.Xml;
import android.view.inputmethod.EditorInfo;
+import com.android.inputmethod.compat.EditorInfoCompatUtils;
import com.android.inputmethod.latin.LatinIME;
import com.android.inputmethod.latin.LatinImeLogger;
import com.android.inputmethod.latin.LocaleUtils;
import com.android.inputmethod.latin.R;
-import com.android.inputmethod.latin.SettingsValues;
-import com.android.inputmethod.latin.SubtypeSwitcher;
import com.android.inputmethod.latin.Utils;
import com.android.inputmethod.latin.XmlParseUtils;
@@ -61,6 +61,7 @@ public class KeyboardSet {
int mMode;
int mInputType;
int mImeOptions;
+ boolean mTouchPositionCorrectionEnabled;
boolean mSettingsKeyEnabled;
boolean mVoiceKeyEnabled;
boolean mVoiceKeyOnMain;
@@ -115,9 +116,8 @@ public class KeyboardSet {
return Builder.getKeyboardId(elementState, false, mParams);
}
- private static Keyboard getKeyboard(Context context, int xmlId, KeyboardId id) {
+ private Keyboard getKeyboard(Context context, int xmlId, KeyboardId id) {
final Resources res = context.getResources();
- final SubtypeSwitcher subtypeSwitcher = SubtypeSwitcher.getInstance();
final SoftReference<Keyboard> ref = sKeyboardCache.get(id);
Keyboard keyboard = (ref == null) ? null : ref.get();
if (keyboard == null) {
@@ -126,9 +126,7 @@ public class KeyboardSet {
final Keyboard.Builder<Keyboard.Params> builder =
new Keyboard.Builder<Keyboard.Params>(context, new Keyboard.Params());
builder.load(xmlId, id);
- builder.setTouchPositionCorrectionEnabled(
- subtypeSwitcher.currentSubtypeContainsExtraValueKey(
- LatinIME.SUBTYPE_EXTRA_VALUE_SUPPORT_TOUCH_POSITION_CORRECTION));
+ builder.setTouchPositionCorrectionEnabled(mParams.mTouchPositionCorrectionEnabled);
keyboard = builder.build();
} finally {
LocaleUtils.setSystemLocale(res, savedLocale);
@@ -151,15 +149,17 @@ public class KeyboardSet {
public static class Builder {
private final Context mContext;
+ private final String mPackageName;
private final Resources mResources;
+ private final EditorInfo mEditorInfo;
private final Params mParams = new Params();
- public Builder(Context context, EditorInfo editorInfo, SettingsValues settingsValues) {
+ public Builder(Context context, EditorInfo editorInfo) {
mContext = context;
+ mPackageName = context.getPackageName();
mResources = context.getResources();
- final SubtypeSwitcher subtypeSwitcher = SubtypeSwitcher.getInstance();
- final String packageName = context.getPackageName();
+ mEditorInfo = editorInfo;
final Params params = mParams;
params.mMode = Utils.getKeyboardMode(editorInfo);
@@ -167,27 +167,46 @@ public class KeyboardSet {
params.mInputType = editorInfo.inputType;
params.mImeOptions = editorInfo.imeOptions;
}
- params.mSettingsKeyEnabled = settingsValues.isSettingsKeyEnabled();
+ params.mNoSettingsKey = Utils.inPrivateImeOptions(
+ mPackageName, LatinIME.IME_OPTION_NO_SETTINGS_KEY, mEditorInfo);
+ }
+
+ public Builder setScreenGeometry(int orientation, int widthPixels) {
+ mParams.mOrientation = orientation;
+ mParams.mWidth = widthPixels;
+ return this;
+ }
+
+ // TODO: Use InputMethodSubtype object as argument.
+ public Builder setSubtype(Locale inputLocale, boolean asciiCapable,
+ boolean touchPositionCorrectionEnabled) {
+ final boolean forceAscii = EditorInfoCompatUtils.hasFlagForceAscii(mParams.mImeOptions)
+ || Utils.inPrivateImeOptions(
+ mPackageName, LatinIME.IME_OPTION_FORCE_ASCII, mEditorInfo);
+ mParams.mLocale = (forceAscii && !asciiCapable) ? Locale.US : inputLocale;
+ mParams.mTouchPositionCorrectionEnabled = touchPositionCorrectionEnabled;
+ return this;
+ }
+
+ public Builder setOptions(boolean settingsKeyEnabled, boolean voiceKeyEnabled,
+ boolean voiceKeyOnMain) {
+ mParams.mSettingsKeyEnabled = settingsKeyEnabled;
@SuppressWarnings("deprecation")
final boolean noMicrophone = Utils.inPrivateImeOptions(
- packageName, LatinIME.IME_OPTION_NO_MICROPHONE, editorInfo)
+ mPackageName, LatinIME.IME_OPTION_NO_MICROPHONE, mEditorInfo)
|| Utils.inPrivateImeOptions(
- null, LatinIME.IME_OPTION_NO_MICROPHONE_COMPAT, editorInfo);
- params.mVoiceKeyEnabled = settingsValues.isVoiceKeyEnabled(editorInfo) && !noMicrophone;
- params.mVoiceKeyOnMain = settingsValues.isVoiceKeyOnMain();
- params.mNoSettingsKey = Utils.inPrivateImeOptions(
- packageName, LatinIME.IME_OPTION_NO_SETTINGS_KEY, editorInfo);
- final boolean forceAscii = Utils.inPrivateImeOptions(
- packageName, LatinIME.IME_OPTION_FORCE_ASCII, editorInfo);
- final boolean asciiCapable = subtypeSwitcher.currentSubtypeContainsExtraValueKey(
- LatinIME.SUBTYPE_EXTRA_VALUE_ASCII_CAPABLE);
- params.mLocale = (forceAscii && !asciiCapable)
- ? Locale.US : subtypeSwitcher.getInputLocale();
- params.mOrientation = mResources.getConfiguration().orientation;
- params.mWidth = mResources.getDisplayMetrics().widthPixels;
+ null, LatinIME.IME_OPTION_NO_MICROPHONE_COMPAT, mEditorInfo);
+ mParams.mVoiceKeyEnabled = voiceKeyEnabled && !noMicrophone;
+ mParams.mVoiceKeyOnMain = voiceKeyOnMain;
+ return this;
}
public KeyboardSet build() {
+ if (mParams.mOrientation == Configuration.ORIENTATION_UNDEFINED)
+ throw new RuntimeException("Screen geometry is not specified");
+ if (mParams.mLocale == null)
+ throw new RuntimeException("KeyboardSet subtype is not specified");
+
final Locale savedLocale = LocaleUtils.setSystemLocale(mResources, mParams.mLocale);
try {
parseKeyboardSet(mResources, R.xml.keyboard_set);
diff --git a/java/src/com/android/inputmethod/keyboard/KeyboardSwitcher.java b/java/src/com/android/inputmethod/keyboard/KeyboardSwitcher.java
index e5097152b..22ab3e4e5 100644
--- a/java/src/com/android/inputmethod/keyboard/KeyboardSwitcher.java
+++ b/java/src/com/android/inputmethod/keyboard/KeyboardSwitcher.java
@@ -28,6 +28,7 @@ import android.view.inputmethod.EditorInfo;
import com.android.inputmethod.accessibility.AccessibleKeyboardViewProxy;
import com.android.inputmethod.keyboard.internal.KeyboardState;
+import com.android.inputmethod.latin.DebugSettings;
import com.android.inputmethod.latin.InputView;
import com.android.inputmethod.latin.LatinIME;
import com.android.inputmethod.latin.LatinImeLogger;
@@ -53,6 +54,7 @@ public class KeyboardSwitcher implements KeyboardState.SwitchActions,
private SubtypeSwitcher mSubtypeSwitcher;
private SharedPreferences mPrefs;
+ private boolean mForceNonDistinctMultitouch;
private InputView mCurrentInputView;
private LatinKeyboardView mKeyboardView;
@@ -92,6 +94,8 @@ public class KeyboardSwitcher implements KeyboardState.SwitchActions,
mState = new KeyboardState(this);
setContextThemeWrapper(ims, getKeyboardThemeIndex(ims, prefs));
prefs.registerOnSharedPreferenceChangeListener(this);
+ mForceNonDistinctMultitouch = prefs.getBoolean(
+ DebugSettings.FORCE_NON_DISTINCT_MULTITOUCH_KEY, false);
}
private static int getKeyboardThemeIndex(Context context, SharedPreferences prefs) {
@@ -117,8 +121,20 @@ public class KeyboardSwitcher implements KeyboardState.SwitchActions,
}
public void loadKeyboard(EditorInfo editorInfo, SettingsValues settingsValues) {
- mKeyboardSet = new KeyboardSet.Builder(mThemeContext, editorInfo, settingsValues)
- .build();
+ final KeyboardSet.Builder builder = new KeyboardSet.Builder(mThemeContext, editorInfo);
+ builder.setScreenGeometry(mThemeContext.getResources().getConfiguration().orientation,
+ mThemeContext.getResources().getDisplayMetrics().widthPixels);
+ builder.setSubtype(
+ mSubtypeSwitcher.getInputLocale(),
+ mSubtypeSwitcher.currentSubtypeContainsExtraValueKey(
+ LatinIME.SUBTYPE_EXTRA_VALUE_ASCII_CAPABLE),
+ mSubtypeSwitcher.currentSubtypeContainsExtraValueKey(
+ LatinIME.SUBTYPE_EXTRA_VALUE_SUPPORT_TOUCH_POSITION_CORRECTION));
+ builder.setOptions(
+ settingsValues.isSettingsKeyEnabled(),
+ settingsValues.isVoiceKeyEnabled(editorInfo),
+ settingsValues.isVoiceKeyOnMain());
+ mKeyboardSet = builder.build();
final KeyboardId mainKeyboardId = mKeyboardSet.getMainKeyboardId();
try {
mState.onLoadKeyboard(mResources.getString(R.string.layout_switch_back_symbols),
@@ -132,7 +148,7 @@ public class KeyboardSwitcher implements KeyboardState.SwitchActions,
// have separate layouts with unique KeyboardIds for alphabet and alphabet-shifted
// respectively.
if (mainKeyboardId.isPhoneKeyboard()) {
- mState.onToggleAlphabetAndSymbols();
+ mState.setSymbolsKeyboard();
}
updateShiftState();
}
@@ -209,18 +225,16 @@ public class KeyboardSwitcher implements KeyboardState.SwitchActions,
Keyboard keyboard = getKeyboard();
if (keyboard == null)
return;
- if (shiftMode == AUTOMATIC_SHIFT) {
+ switch (shiftMode) {
+ case AUTOMATIC_SHIFT:
keyboard.setAutomaticTemporaryUpperCase();
- } else {
- final boolean shifted = (shiftMode == MANUAL_SHIFT);
- // On non-distinct multi touch panel device, we should also turn off the shift locked
- // state when shift key is pressed to go to normal mode.
- // On the other hand, on distinct multi touch panel device, turning off the shift
- // locked state with shift key pressing is handled by onReleaseShift().
- if (!hasDistinctMultitouch() && !shifted && mState.isShiftLocked()) {
- keyboard.setShiftLocked(false);
- }
- keyboard.setShifted(shifted);
+ break;
+ case MANUAL_SHIFT:
+ keyboard.setShifted(true);
+ break;
+ case UNSHIFT:
+ keyboard.setShifted(false);
+ break;
}
mKeyboardView.invalidateAllKeys();
}
@@ -243,51 +257,18 @@ public class KeyboardSwitcher implements KeyboardState.SwitchActions,
}
/**
- * Toggle keyboard shift state triggered by user touch event.
- */
- public void toggleShift() {
- mState.onToggleShift();
- }
-
- /**
- * Toggle caps lock state triggered by user touch event.
- */
- public void toggleCapsLock() {
- mState.onToggleCapsLock();
- }
-
- /**
- * Toggle between alphabet and symbols modes triggered by user touch event.
- */
- public void toggleAlphabetAndSymbols() {
- mState.onToggleAlphabetAndSymbols();
- }
-
- /**
* Update keyboard shift state triggered by connected EditText status change.
*/
public void updateShiftState() {
mState.onUpdateShiftState(mInputMethodService.getCurrentAutoCapsState());
}
- public void onPressShift(boolean withSliding) {
- mState.onPressShift(withSliding);
- }
-
- public void onReleaseShift(boolean withSliding) {
- mState.onReleaseShift(withSliding);
- }
-
- public void onPressSymbol() {
- mState.onPressSymbol();
+ public void onPressKey(int code) {
+ mState.onPressKey(code);
}
- public void onReleaseSymbol() {
- mState.onReleaseSymbol();
- }
-
- public void onOtherKeyPressed() {
- mState.onOtherKeyPressed();
+ public void onReleaseKey(int code, boolean withSliding) {
+ mState.onReleaseKey(code, withSliding);
}
public void onCancelInput() {
@@ -312,6 +293,12 @@ public class KeyboardSwitcher implements KeyboardState.SwitchActions,
setKeyboard(mKeyboardSet.getSymbolsShiftedKeyboard());
}
+ // Implements {@link KeyboardState.SwitchActions}.
+ @Override
+ public void requestUpdatingShiftState() {
+ mState.onUpdateShiftState(mInputMethodService.getCurrentAutoCapsState());
+ }
+
public boolean isInMomentarySwitchState() {
return mState.isInMomentarySwitchState();
}
@@ -373,6 +360,9 @@ public class KeyboardSwitcher implements KeyboardState.SwitchActions,
mKeyboardView = (LatinKeyboardView) mCurrentInputView.findViewById(R.id.keyboard_view);
mKeyboardView.setKeyboardActionListener(mInputMethodService);
+ if (mForceNonDistinctMultitouch) {
+ mKeyboardView.setDistinctMultitouch(false);
+ }
// This always needs to be set since the accessibility state can
// potentially change without the input view being re-created.
@@ -418,17 +408,4 @@ public class KeyboardSwitcher implements KeyboardState.SwitchActions,
}
}
}
-
- private static String themeName(int themeId) {
- // This should be aligned with theme-*.xml resource files' themeId attribute.
- switch (themeId) {
- case 0: return "Basic";
- case 1: return "BasicHighContrast";
- case 5: return "IceCreamSandwich";
- case 6: return "Stone";
- case 7: return "StoneBold";
- case 8: return "GingerBread";
- default: return null;
- }
- }
}
diff --git a/java/src/com/android/inputmethod/keyboard/LatinKeyboardView.java b/java/src/com/android/inputmethod/keyboard/LatinKeyboardView.java
index 3433cd455..aa0f9751d 100644
--- a/java/src/com/android/inputmethod/keyboard/LatinKeyboardView.java
+++ b/java/src/com/android/inputmethod/keyboard/LatinKeyboardView.java
@@ -115,7 +115,7 @@ public class LatinKeyboardView extends KeyboardView implements PointerTracker.Ke
/** Listener for {@link KeyboardActionListener}. */
private KeyboardActionListener mKeyboardActionListener;
- private final boolean mHasDistinctMultitouch;
+ private boolean mHasDistinctMultitouch;
private int mOldPointerCount = 1;
private Key mOldKey;
@@ -371,6 +371,10 @@ public class LatinKeyboardView extends KeyboardView implements PointerTracker.Ke
return mHasDistinctMultitouch;
}
+ public void setDistinctMultitouch(boolean hasDistinctMultitouch) {
+ mHasDistinctMultitouch = hasDistinctMultitouch;
+ }
+
/**
* When enabled, calls to {@link KeyboardActionListener#onCodeInput} will include key
* codes for adjacent keys. When disabled, only the primary key code will be
@@ -413,9 +417,9 @@ public class LatinKeyboardView extends KeyboardView implements PointerTracker.Ke
// the second tap is treated as this double tap event, so that we need not mark tracker
// calling setAlreadyProcessed() nor remove the tracker from mPointerQueue.
if (ignore) {
- mKeyboardActionListener.onCustomRequest(LatinIME.CODE_HAPTIC_AND_AUDIO_FEEDBACK);
+ invokeCustomRequest(LatinIME.CODE_HAPTIC_AND_AUDIO_FEEDBACK);
} else {
- mKeyboardActionListener.onCodeInput(Keyboard.CODE_CAPSLOCK, null, 0, 0);
+ invokeCodeInput(Keyboard.CODE_CAPSLOCK);
}
}
@@ -475,17 +479,17 @@ public class LatinKeyboardView extends KeyboardView implements PointerTracker.Ke
}
private boolean invokeCustomRequest(int code) {
- return getKeyboardActionListener().onCustomRequest(code);
+ return mKeyboardActionListener.onCustomRequest(code);
}
private void invokeCodeInput(int primaryCode) {
- getKeyboardActionListener().onCodeInput(primaryCode, null,
+ mKeyboardActionListener.onCodeInput(primaryCode, null,
KeyboardActionListener.NOT_A_TOUCH_COORDINATE,
KeyboardActionListener.NOT_A_TOUCH_COORDINATE);
}
private void invokeReleaseKey(int primaryCode) {
- getKeyboardActionListener().onRelease(primaryCode, false);
+ mKeyboardActionListener.onReleaseKey(primaryCode, false);
}
private boolean openMoreKeysPanel(Key parentKey, PointerTracker tracker) {
@@ -510,7 +514,7 @@ public class LatinKeyboardView extends KeyboardView implements PointerTracker.Ke
: parentKey.mX + parentKey.mWidth / 2;
final int pointY = parentKey.mY - keyboard.mVerticalGap;
moreKeysPanel.showMoreKeysPanel(
- this, this, pointX, pointY, mMoreKeysWindow, getKeyboardActionListener());
+ this, this, pointX, pointY, mMoreKeysWindow, mKeyboardActionListener);
final int translatedX = moreKeysPanel.translateX(tracker.getLastX());
final int translatedY = moreKeysPanel.translateY(tracker.getLastY());
tracker.onShowMoreKeysPanel(translatedX, translatedY, moreKeysPanel);
@@ -806,7 +810,7 @@ public class LatinKeyboardView extends KeyboardView implements PointerTracker.Ke
return bounds.width();
}
- // Layout local language name and left and right arrow on spacebar.
+ // Layout locale language name on spacebar.
private static String layoutSpacebar(Paint paint, Locale locale, int width,
float origTextSize) {
final Rect bounds = new Rect();
diff --git a/java/src/com/android/inputmethod/keyboard/MiniKeyboardView.java b/java/src/com/android/inputmethod/keyboard/MiniKeyboardView.java
index 8e9929681..1f9ed5e91 100644
--- a/java/src/com/android/inputmethod/keyboard/MiniKeyboardView.java
+++ b/java/src/com/android/inputmethod/keyboard/MiniKeyboardView.java
@@ -61,12 +61,13 @@ public class MiniKeyboardView extends KeyboardView implements MoreKeysPanel {
}
@Override
- public void onPress(int primaryCode, boolean withSliding) {
- mListener.onPress(primaryCode, withSliding);
+ public void onPressKey(int primaryCode) {
+ mListener.onPressKey(primaryCode);
}
+
@Override
- public void onRelease(int primaryCode, boolean withSliding) {
- mListener.onRelease(primaryCode, withSliding);
+ public void onReleaseKey(int primaryCode, boolean withSliding) {
+ mListener.onReleaseKey(primaryCode, withSliding);
}
};
diff --git a/java/src/com/android/inputmethod/keyboard/PointerTracker.java b/java/src/com/android/inputmethod/keyboard/PointerTracker.java
index 2183e8ed6..274bd0b31 100644
--- a/java/src/com/android/inputmethod/keyboard/PointerTracker.java
+++ b/java/src/com/android/inputmethod/keyboard/PointerTracker.java
@@ -237,18 +237,18 @@ public class PointerTracker {
}
// Returns true if keyboard has been changed by this callback.
- private boolean callListenerOnPressAndCheckKeyboardLayoutChange(Key key, boolean withSliding) {
+ private boolean callListenerOnPressAndCheckKeyboardLayoutChange(Key key) {
final boolean ignoreModifierKey = mIgnoreModifierKey && key.isModifier();
if (DEBUG_LISTENER) {
Log.d(TAG, "onPress : " + KeyDetector.printableCode(key)
- + " sliding=" + withSliding + " ignoreModifier=" + ignoreModifierKey
+ + " ignoreModifier=" + ignoreModifierKey
+ " enabled=" + key.isEnabled());
}
if (ignoreModifierKey) {
return false;
}
if (key.isEnabled()) {
- mListener.onPress(key.mCode, withSliding);
+ mListener.onPressKey(key.mCode);
final boolean keyboardLayoutHasBeenChanged = mKeyboardLayoutHasBeenChanged;
mKeyboardLayoutHasBeenChanged = false;
return keyboardLayoutHasBeenChanged;
@@ -296,7 +296,7 @@ public class PointerTracker {
return;
}
if (key.isEnabled()) {
- mListener.onRelease(primaryCode, withSliding);
+ mListener.onReleaseKey(primaryCode, withSliding);
}
}
@@ -495,7 +495,7 @@ public class PointerTracker {
// This onPress call may have changed keyboard layout. Those cases are detected at
// {@link #setKeyboard}. In those cases, we should update key according to the new
// keyboard layout.
- if (callListenerOnPressAndCheckKeyboardLayoutChange(key, false)) {
+ if (callListenerOnPressAndCheckKeyboardLayoutChange(key)) {
key = onDownKey(x, y, eventTime);
}
@@ -529,7 +529,7 @@ public class PointerTracker {
// This onPress call may have changed keyboard layout. Those cases are detected at
// {@link #setKeyboard}. In those cases, we should update key according to the
// new keyboard layout.
- if (callListenerOnPressAndCheckKeyboardLayoutChange(key, true)) {
+ if (callListenerOnPressAndCheckKeyboardLayoutChange(key)) {
key = onMoveKey(x, y);
}
onMoveToNewKey(key, x, y);
@@ -548,7 +548,7 @@ public class PointerTracker {
// This onPress call may have changed keyboard layout. Those cases are detected
// at {@link #setKeyboard}. In those cases, we should update key according
// to the new keyboard layout.
- if (callListenerOnPressAndCheckKeyboardLayoutChange(key, true)) {
+ if (callListenerOnPressAndCheckKeyboardLayoutChange(key)) {
key = onMoveKey(x, y);
}
onMoveToNewKey(key, x, y);
diff --git a/java/src/com/android/inputmethod/keyboard/internal/KeyboardState.java b/java/src/com/android/inputmethod/keyboard/internal/KeyboardState.java
index f54bdbb05..c43b9852b 100644
--- a/java/src/com/android/inputmethod/keyboard/internal/KeyboardState.java
+++ b/java/src/com/android/inputmethod/keyboard/internal/KeyboardState.java
@@ -21,24 +21,22 @@ import android.util.Log;
import com.android.inputmethod.keyboard.Keyboard;
-// TODO: Add unit tests
/**
* Keyboard state machine.
*
* This class contains all keyboard state transition logic.
*
* The input events are {@link #onLoadKeyboard(String, boolean)}, {@link #onSaveKeyboardState()},
- * {@link #onPressShift(boolean)}, {@link #onReleaseShift(boolean)}, {@link #onPressSymbol()},
- * {@link #onReleaseSymbol()}, {@link #onOtherKeyPressed()},
+ * {@link #onPressKey(int)}, {@link #onReleaseKey(int, boolean)},
* {@link #onCodeInput(int, boolean, boolean)}, {@link #onCancelInput(boolean)},
- * {@link #onUpdateShiftState(boolean)}, {@link #onToggleShift()}, {@link #onToggleCapsLock()},
- * and {@link #onToggleAlphabetAndSymbols()}.
+ * {@link #onUpdateShiftState(boolean)}.
*
* The actions are {@link SwitchActions}'s methods.
*/
public class KeyboardState {
private static final String TAG = KeyboardState.class.getSimpleName();
- private static final boolean DEBUG_STATE = false;
+ private static final boolean DEBUG_EVENT = false;
+ private static final boolean DEBUG_ACTION = false;
public interface SwitchActions {
public void setAlphabetKeyboard();
@@ -53,6 +51,11 @@ public class KeyboardState {
public void setSymbolsKeyboard();
public void setSymbolsShiftedKeyboard();
+
+ /**
+ * Request to call back {@link KeyboardState#onUpdateShiftState(boolean)}.
+ */
+ public void requestUpdatingShiftState();
}
private KeyboardShiftState mKeyboardShiftState = new KeyboardShiftState();
@@ -93,7 +96,7 @@ public class KeyboardState {
}
public void onLoadKeyboard(String layoutSwitchBackSymbols, boolean hasDistinctMultitouch) {
- if (DEBUG_STATE) {
+ if (DEBUG_EVENT) {
Log.d(TAG, "onLoadKeyboard");
}
mLayoutSwitchBackSymbols = layoutSwitchBackSymbols;
@@ -118,7 +121,7 @@ public class KeyboardState {
state.mIsShifted = mIsSymbolShifted;
}
state.mIsValid = true;
- if (DEBUG_STATE) {
+ if (DEBUG_EVENT) {
Log.d(TAG, "onSaveKeyboardState: alphabet=" + state.mIsAlphabetMode
+ " shiftLocked=" + state.mIsShiftLocked + " shift=" + state.mIsShifted);
}
@@ -126,7 +129,7 @@ public class KeyboardState {
private void onRestoreKeyboardState() {
final SavedKeyboardState state = mSavedKeyboardState;
- if (DEBUG_STATE) {
+ if (DEBUG_EVENT) {
Log.d(TAG, "onRestoreKeyboardState: valid=" + state.mIsValid
+ " alphabet=" + state.mIsAlphabetMode
+ " shiftLocked=" + state.mIsShiftLocked + " shift=" + state.mIsShifted);
@@ -158,20 +161,19 @@ public class KeyboardState {
}
private void setShifted(int shiftMode) {
- if (DEBUG_STATE) {
+ if (DEBUG_ACTION) {
Log.d(TAG, "setShifted: shiftMode=" + shiftModeToString(shiftMode));
}
if (shiftMode == SwitchActions.AUTOMATIC_SHIFT) {
mKeyboardShiftState.setAutomaticTemporaryUpperCase();
} else {
- // TODO: Duplicated logic in KeyboardSwitcher#setShifted()
final boolean shifted = (shiftMode == SwitchActions.MANUAL_SHIFT);
// On non-distinct multi touch panel device, we should also turn off the shift locked
// state when shift key is pressed to go to normal mode.
// On the other hand, on distinct multi touch panel device, turning off the shift
// locked state with shift key pressing is handled by onReleaseShift().
if (!mHasDistinctMultitouch && !shifted && mKeyboardShiftState.isShiftLocked()) {
- mKeyboardShiftState.setShiftLocked(false);
+ mSwitchActions.setShiftLocked(false);
}
mKeyboardShiftState.setShifted(shifted);
}
@@ -179,7 +181,7 @@ public class KeyboardState {
}
private void setShiftLocked(boolean shiftLocked) {
- if (DEBUG_STATE) {
+ if (DEBUG_ACTION) {
Log.d(TAG, "setShiftLocked: shiftLocked=" + shiftLocked);
}
mKeyboardShiftState.setShiftLocked(shiftLocked);
@@ -203,7 +205,7 @@ public class KeyboardState {
}
private void setAlphabetKeyboard() {
- if (DEBUG_STATE) {
+ if (DEBUG_ACTION) {
Log.d(TAG, "setAlphabetKeyboard");
}
mSwitchActions.setAlphabetKeyboard();
@@ -212,10 +214,12 @@ public class KeyboardState {
mSwitchState = SWITCH_STATE_ALPHA;
setShiftLocked(mPrevMainKeyboardWasShiftLocked);
mPrevMainKeyboardWasShiftLocked = false;
+ mSwitchActions.requestUpdatingShiftState();
}
- private void setSymbolsKeyboard() {
- if (DEBUG_STATE) {
+ // TODO: Make this method private
+ public void setSymbolsKeyboard() {
+ if (DEBUG_ACTION) {
Log.d(TAG, "setSymbolsKeyboard");
}
mPrevMainKeyboardWasShiftLocked = mKeyboardShiftState.isShiftLocked();
@@ -226,7 +230,7 @@ public class KeyboardState {
}
private void setSymbolsShiftedKeyboard() {
- if (DEBUG_STATE) {
+ if (DEBUG_ACTION) {
Log.d(TAG, "setSymbolsShiftedKeyboard");
}
mSwitchActions.setSymbolsShiftedKeyboard();
@@ -235,19 +239,40 @@ public class KeyboardState {
mSwitchState = SWITCH_STATE_SYMBOL_BEGIN;
}
- public void onPressSymbol() {
- if (DEBUG_STATE) {
- Log.d(TAG, "onPressSymbol: " + this);
+ public void onPressKey(int code) {
+ if (DEBUG_EVENT) {
+ Log.d(TAG, "onPressKey: code=" + Keyboard.printableCode(code) + " " + this);
+ }
+ if (code == Keyboard.CODE_SHIFT) {
+ onPressShift();
+ } else if (code == Keyboard.CODE_SWITCH_ALPHA_SYMBOL) {
+ onPressSymbol();
+ } else {
+ mShiftKeyState.onOtherKeyPressed();
+ mSymbolKeyState.onOtherKeyPressed();
+ }
+ }
+
+ public void onReleaseKey(int code, boolean withSliding) {
+ if (DEBUG_EVENT) {
+ Log.d(TAG, "onReleaseKey: code=" + Keyboard.printableCode(code)
+ + " sliding=" + withSliding + " " + this);
}
+ if (code == Keyboard.CODE_SHIFT) {
+ onReleaseShift(withSliding);
+ } else if (code == Keyboard.CODE_SWITCH_ALPHA_SYMBOL) {
+ // TODO: Make use of withSliding instead of relying on mSwitchState.
+ onReleaseSymbol();
+ }
+ }
+
+ private void onPressSymbol() {
toggleAlphabetAndSymbols();
mSymbolKeyState.onPress();
mSwitchState = SWITCH_STATE_MOMENTARY_ALPHA_AND_SYMBOL;
}
- public void onReleaseSymbol() {
- if (DEBUG_STATE) {
- Log.d(TAG, "onReleaseSymbol: " + this);
- }
+ private void onReleaseSymbol() {
// Snap back to the previous keyboard mode if the user chords the mode change key and
// another key, then releases the mode change key.
if (mSwitchState == SWITCH_STATE_CHORDING_ALPHA) {
@@ -256,16 +281,8 @@ public class KeyboardState {
mSymbolKeyState.onRelease();
}
- public void onOtherKeyPressed() {
- if (DEBUG_STATE) {
- Log.d(TAG, "onOtherKeyPressed: " + this);
- }
- mShiftKeyState.onOtherKeyPressed();
- mSymbolKeyState.onOtherKeyPressed();
- }
-
public void onUpdateShiftState(boolean autoCaps) {
- if (DEBUG_STATE) {
+ if (DEBUG_EVENT) {
Log.d(TAG, "onUpdateShiftState: autoCaps=" + autoCaps + " " + this);
}
onUpdateShiftStateInternal(autoCaps);
@@ -289,10 +306,7 @@ public class KeyboardState {
}
}
- public void onPressShift(boolean withSliding) {
- if (DEBUG_STATE) {
- Log.d(TAG, "onPressShift: sliding=" + withSliding + " " + this);
- }
+ private void onPressShift() {
if (mIsAlphabetMode) {
if (mKeyboardShiftState.isShiftLocked()) {
// Shift key is pressed while caps lock state, we will treat this state as shifted
@@ -321,10 +335,7 @@ public class KeyboardState {
}
}
- public void onReleaseShift(boolean withSliding) {
- if (DEBUG_STATE) {
- Log.d(TAG, "onReleaseShift: sliding=" + withSliding + " " + this);
- }
+ private void onReleaseShift(boolean withSliding) {
if (mIsAlphabetMode) {
final boolean isShiftLocked = mKeyboardShiftState.isShiftLocked();
if (mShiftKeyState.isMomentary()) {
@@ -358,8 +369,8 @@ public class KeyboardState {
}
public void onCancelInput(boolean isSinglePointer) {
- if (DEBUG_STATE) {
- Log.d(TAG, "onCancelInput: isSinglePointer=" + isSinglePointer + " " + this);
+ if (DEBUG_EVENT) {
+ Log.d(TAG, "onCancelInput: single=" + isSinglePointer + " " + this);
}
// Snap back to the previous keyboard mode if the user cancels sliding input.
if (isSinglePointer) {
@@ -387,10 +398,23 @@ public class KeyboardState {
}
public void onCodeInput(int code, boolean isSinglePointer, boolean autoCaps) {
- if (DEBUG_STATE) {
- Log.d(TAG, "onCodeInput: code=" + code + " isSinglePointer=" + isSinglePointer
+ if (DEBUG_EVENT) {
+ Log.d(TAG, "onCodeInput: code=" + Keyboard.printableCode(code)
+ + " single=" + isSinglePointer
+ " autoCaps=" + autoCaps + " " + this);
}
+
+ if (mIsAlphabetMode && code == Keyboard.CODE_CAPSLOCK) {
+ if (mKeyboardShiftState.isShiftLocked()) {
+ setShiftLocked(false);
+ // Shift key is long pressed or double tapped while caps lock state, we will
+ // toggle back to normal state. And mark as if shift key is released.
+ mShiftKeyState.onRelease();
+ } else {
+ setShiftLocked(true);
+ }
+ }
+
switch (mSwitchState) {
case SWITCH_STATE_MOMENTARY_ALPHA_AND_SYMBOL:
// Only distinct multi touch devices can be in this state.
@@ -459,41 +483,6 @@ public class KeyboardState {
}
}
- public void onToggleShift() {
- if (DEBUG_STATE) {
- Log.d(TAG, "onToggleShift: " + this);
- }
- if (mIsAlphabetMode) {
- setShifted(mKeyboardShiftState.isShiftedOrShiftLocked()
- ? SwitchActions.UNSHIFT : SwitchActions.MANUAL_SHIFT);
- } else {
- toggleShiftInSymbols();
- }
- }
-
- public void onToggleCapsLock() {
- if (DEBUG_STATE) {
- Log.d(TAG, "onToggleCapsLock: " + this);
- }
- if (mIsAlphabetMode) {
- if (mKeyboardShiftState.isShiftLocked()) {
- setShiftLocked(false);
- // Shift key is long pressed while caps lock state, we will toggle back to normal
- // state. And mark as if shift key is released.
- mShiftKeyState.onRelease();
- } else {
- setShiftLocked(true);
- }
- }
- }
-
- public void onToggleAlphabetAndSymbols() {
- if (DEBUG_STATE) {
- Log.d(TAG, "onToggleAlphabetAndSymbols: " + this);
- }
- toggleAlphabetAndSymbols();
- }
-
private static String shiftModeToString(int shiftMode) {
switch (shiftMode) {
case SwitchActions.UNSHIFT: return "UNSHIFT";
diff --git a/java/src/com/android/inputmethod/latin/DebugSettings.java b/java/src/com/android/inputmethod/latin/DebugSettings.java
index 2f1e7c2b8..3805da154 100644
--- a/java/src/com/android/inputmethod/latin/DebugSettings.java
+++ b/java/src/com/android/inputmethod/latin/DebugSettings.java
@@ -30,6 +30,7 @@ public class DebugSettings extends PreferenceActivity
private static final String TAG = "DebugSettings";
private static final String DEBUG_MODE_KEY = "debug_mode";
+ public static final String FORCE_NON_DISTINCT_MULTITOUCH_KEY = "force_non_distinct_multitouch";
private boolean mServiceNeedsRestart = false;
private CheckBoxPreference mDebugMode;
@@ -60,6 +61,8 @@ public class DebugSettings extends PreferenceActivity
updateDebugMode();
mServiceNeedsRestart = true;
}
+ } else if (key.equals(FORCE_NON_DISTINCT_MULTITOUCH_KEY)) {
+ mServiceNeedsRestart = true;
}
}
diff --git a/java/src/com/android/inputmethod/latin/EditingUtils.java b/java/src/com/android/inputmethod/latin/EditingUtils.java
index 634dbbdfc..1e8ad1840 100644
--- a/java/src/com/android/inputmethod/latin/EditingUtils.java
+++ b/java/src/com/android/inputmethod/latin/EditingUtils.java
@@ -87,23 +87,6 @@ public class EditingUtils {
}
/**
- * Removes the word surrounding the cursor. Parameters are identical to
- * getWordAtCursor.
- */
- public static void deleteWordAtCursor(InputConnection connection, String separators) {
- // getWordRangeAtCursor returns null if the connection is null
- Range range = getWordRangeAtCursor(connection, separators);
- if (range == null) return;
-
- connection.finishComposingText();
- // Move cursor to beginning of word, to avoid crash when cursor is outside
- // of valid range after deleting text.
- int newCursor = getCursorPosition(connection) - range.mCharsBefore;
- connection.setSelection(newCursor, newCursor);
- connection.deleteSurroundingText(0, range.mCharsBefore + range.mCharsAfter);
- }
-
- /**
* Represents a range of text, relative to the current cursor position.
*/
public static class Range {
diff --git a/java/src/com/android/inputmethod/latin/LatinIME.java b/java/src/com/android/inputmethod/latin/LatinIME.java
index 59de798d8..d11aaeb96 100644
--- a/java/src/com/android/inputmethod/latin/LatinIME.java
+++ b/java/src/com/android/inputmethod/latin/LatinIME.java
@@ -104,10 +104,14 @@ public class LatinIME extends InputMethodServiceCompatWrapper implements Keyboar
*/
public static final String IME_OPTION_NO_SETTINGS_KEY = "noSettingsKey";
+ // TODO: Remove this private option.
/**
* The private IME option used to indicate that the given text field needs
* ASCII code points input.
+ *
+ * @deprecated Use {@link EditorInfo#IME_FLAG_FORCE_ASCII}.
*/
+ @SuppressWarnings("dep-ann")
public static final String IME_OPTION_FORCE_ASCII = "forceAscii";
/**
@@ -1019,7 +1023,10 @@ public class LatinIME extends InputMethodServiceCompatWrapper implements Keyboar
@Override
public boolean onEvaluateFullscreenMode() {
- return super.onEvaluateFullscreenMode() && mSettingsValues.mUseFullScreenMode;
+ // Reread resource value here, because this method is called by framework anytime as needed.
+ final boolean isFullscreenModeAllowed =
+ mSettingsValues.isFullscreenModeAllowed(getResources());
+ return super.onEvaluateFullscreenMode() && isFullscreenModeAllowed;
}
@Override
@@ -1232,7 +1239,6 @@ public class LatinIME extends InputMethodServiceCompatWrapper implements Keyboar
}
mLastKeyTime = when;
final KeyboardSwitcher switcher = mKeyboardSwitcher;
- final boolean distinctMultiTouch = switcher.hasDistinctMultitouch();
// 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
// the input contents (any non-shift key), which is what we should do for
@@ -1254,22 +1260,14 @@ public class LatinIME extends InputMethodServiceCompatWrapper implements Keyboar
LatinImeLogger.logOnDelete();
break;
case Keyboard.CODE_SHIFT:
- // Shift key is handled in onPress() when device has distinct multi-touch panel.
- if (!distinctMultiTouch) {
- switcher.toggleShift();
- }
- break;
case Keyboard.CODE_SWITCH_ALPHA_SYMBOL:
- // Symbol key is handled in onPress() when device has distinct multi-touch panel.
- if (!distinctMultiTouch) {
- switcher.toggleAlphabetAndSymbols();
- }
+ // Shift and symbol key is handled in onPressKey() and onReleaseKey().
break;
case Keyboard.CODE_SETTINGS:
onSettingsKeyPressed();
break;
case Keyboard.CODE_CAPSLOCK:
- switcher.toggleCapsLock();
+ // Caps lock code is handled in KeyboardSwitcher.onCodeInput() below.
hapticAndAudioFeedback(primaryCode);
break;
case Keyboard.CODE_SHORTCUT:
@@ -1369,6 +1367,10 @@ public class LatinIME extends InputMethodServiceCompatWrapper implements Keyboar
ic.deleteSurroundingText(1, 0);
}
} else {
+ // We should be very careful about auto-correction cancellation and suggestion
+ // resuming here. The behavior needs to be different according to text field types,
+ // and it would be much clearer to test for them explicitly here rather than
+ // relying on implicit values like "whether the suggestion strip is displayed".
if (mWordComposer.didAutoCorrectToAnotherWord()) {
Utils.Stats.onAutoCorrectionCancellation();
cancelAutoCorrect(ic);
@@ -1388,10 +1390,11 @@ public class LatinIME extends InputMethodServiceCompatWrapper implements Keyboar
}
}
+ // See the comment above: must be careful about resuming auto-suggestion.
if (mSuggestionsView != null && mSuggestionsView.dismissAddToDictionaryHint()) {
// Go back to the suggestion mode if the user canceled the
// "Touch again to save".
- // NOTE: In gerenal, we don't revert the word when backspacing
+ // NOTE: In general, we don't revert the word when backspacing
// from a manual suggestion pick. We deliberately chose a
// different behavior only in the case of picking the first
// suggestion (typed word). It's intentional to have made this
@@ -1402,7 +1405,9 @@ public class LatinIME extends InputMethodServiceCompatWrapper implements Keyboar
if (mDeleteCount > DELETE_ACCELERATE_AT) {
ic.deleteSurroundingText(1, 0);
}
- restartSuggestionsOnWordBeforeCursorIfAtEndOfWord(ic);
+ if (isSuggestionsRequested()) {
+ restartSuggestionsOnWordBeforeCursorIfAtEndOfWord(ic);
+ }
}
}
}
@@ -1694,6 +1699,10 @@ public class LatinIME extends InputMethodServiceCompatWrapper implements Keyboar
// Check if we have a suggestion engine attached.
if ((mSuggest == null || !isSuggestionsRequested())
&& !mVoiceProxy.isVoiceInputHighlighted()) {
+ if (mWordComposer.isComposingWord()) {
+ Log.w(TAG, "Called updateSuggestions but suggestions were not requested!");
+ mWordComposer.setAutoCorrection(mWordComposer.getTypedWord());
+ }
return;
}
@@ -2081,10 +2090,23 @@ public class LatinIME extends InputMethodServiceCompatWrapper implements Keyboar
&& !mSettingsValues.isWordSeparator(textAfterCursor.charAt(0))) return;
// Bail out if word before cursor is 0-length or a single non letter (like an apostrophe)
- // Example: " '|" gets rejected here but "I'|" and "I|" are okay
- final CharSequence word = EditingUtils.getWordAtCursor(ic, mSettingsValues.mWordSeparators);
+ // Example: " -|" gets rejected here but "e-|" and "e|" are okay
+ CharSequence word = EditingUtils.getWordAtCursor(ic, mSettingsValues.mWordSeparators);
+ // We don't suggest on leading single quotes, so we have to remove them from the word if
+ // it starts with single quotes.
+ while (!TextUtils.isEmpty(word) && Keyboard.CODE_SINGLE_QUOTE == word.charAt(0)) {
+ word = word.subSequence(1, word.length());
+ }
if (TextUtils.isEmpty(word)) return;
- if (word.length() == 1 && !Character.isLetter(word.charAt(0))) return;
+ final char firstChar = word.charAt(0); // we just tested that word is not empty
+ if (word.length() == 1 && !Character.isLetter(firstChar)) return;
+
+ // We only suggest on words that start with a letter or a symbol that is excluded from
+ // word separators (see #handleCharacterWhileInBatchEdit).
+ if (!(isAlphabet(firstChar)
+ || mSettingsValues.isSymbolExcludedFromWordSeparators(firstChar))) {
+ return;
+ }
// Okay, we are at the end of a word. Restart suggestions.
restartSuggestionsOnWordBeforeCursor(ic, word);
@@ -2235,31 +2257,17 @@ public class LatinIME extends InputMethodServiceCompatWrapper implements Keyboar
}
@Override
- public void onPress(int primaryCode, boolean withSliding) {
+ public void onPressKey(int primaryCode) {
final KeyboardSwitcher switcher = mKeyboardSwitcher;
if (switcher.isVibrateAndSoundFeedbackRequired()) {
hapticAndAudioFeedback(primaryCode);
}
- final boolean distinctMultiTouch = switcher.hasDistinctMultitouch();
- if (distinctMultiTouch && primaryCode == Keyboard.CODE_SHIFT) {
- switcher.onPressShift(withSliding);
- } else if (distinctMultiTouch && primaryCode == Keyboard.CODE_SWITCH_ALPHA_SYMBOL) {
- switcher.onPressSymbol();
- } else {
- switcher.onOtherKeyPressed();
- }
+ switcher.onPressKey(primaryCode);
}
@Override
- public void onRelease(int primaryCode, boolean withSliding) {
- KeyboardSwitcher switcher = mKeyboardSwitcher;
- // Reset any drag flags in the keyboard
- final boolean distinctMultiTouch = switcher.hasDistinctMultitouch();
- if (distinctMultiTouch && primaryCode == Keyboard.CODE_SHIFT) {
- switcher.onReleaseShift(withSliding);
- } else if (distinctMultiTouch && primaryCode == Keyboard.CODE_SWITCH_ALPHA_SYMBOL) {
- switcher.onReleaseSymbol();
- }
+ public void onReleaseKey(int primaryCode, boolean withSliding) {
+ mKeyboardSwitcher.onReleaseKey(primaryCode, withSliding);
}
diff --git a/java/src/com/android/inputmethod/latin/SettingsValues.java b/java/src/com/android/inputmethod/latin/SettingsValues.java
index 83b27f7fe..0ae28d3fc 100644
--- a/java/src/com/android/inputmethod/latin/SettingsValues.java
+++ b/java/src/com/android/inputmethod/latin/SettingsValues.java
@@ -41,7 +41,6 @@ public class SettingsValues {
private final String mSymbolsExcludedFromWordSeparators;
public final String mWordSeparators;
public final CharSequence mHintToSaveText;
- public final boolean mUseFullScreenMode;
// From preferences, in the same order as xml/prefs.xml:
public final boolean mAutoCap;
@@ -107,7 +106,6 @@ public class SettingsValues {
mWordSeparators = createWordSeparators(mMagicSpaceStrippers, mMagicSpaceSwappers,
mSymbolsExcludedFromWordSeparators, res);
mHintToSaveText = context.getText(R.string.hint_add_to_dictionary);
- mUseFullScreenMode = res.getBoolean(R.bool.config_use_fullscreen_mode);
// Get the settings preferences
mAutoCap = prefs.getBoolean(Settings.PREF_AUTO_CAP, true);
@@ -294,6 +292,10 @@ public class SettingsValues {
return mVoiceKeyOnMain;
}
+ public boolean isFullscreenModeAllowed(Resources res) {
+ return res.getBoolean(R.bool.config_use_fullscreen_mode);
+ }
+
// Accessed from the settings interface, hence public
public static float getCurrentKeypressSoundVolume(final SharedPreferences sp,
final Resources res) {
diff --git a/java/src/com/android/inputmethod/latin/SubtypeSwitcher.java b/java/src/com/android/inputmethod/latin/SubtypeSwitcher.java
index 42111822d..f5778167a 100644
--- a/java/src/com/android/inputmethod/latin/SubtypeSwitcher.java
+++ b/java/src/com/android/inputmethod/latin/SubtypeSwitcher.java
@@ -46,8 +46,8 @@ public class SubtypeSwitcher {
private static boolean DBG = LatinImeLogger.sDBG;
private static final String TAG = SubtypeSwitcher.class.getSimpleName();
+ public static final String KEYBOARD_MODE = "keyboard";
private static final char LOCALE_SEPARATER = '_';
- private static final String KEYBOARD_MODE = "keyboard";
private static final String VOICE_MODE = "voice";
private static final String SUBTYPE_EXTRAVALUE_REQUIRE_NETWORK_CONNECTIVITY =
"requireNetworkConnectivity";
diff --git a/java/src/com/android/inputmethod/latin/Suggest.java b/java/src/com/android/inputmethod/latin/Suggest.java
index e9ca390d3..8e0d031f4 100644
--- a/java/src/com/android/inputmethod/latin/Suggest.java
+++ b/java/src/com/android/inputmethod/latin/Suggest.java
@@ -115,7 +115,7 @@ public class Suggest implements Dictionary.WordCallback {
/* package for test */ Suggest(final Context context, final File dictionary,
final long startOffset, final long length, final Flag[] flagArray,
final Locale locale) {
- initSynchronously(null, DictionaryFactory.createDictionaryForTest(context, dictionary,
+ initSynchronously(context, DictionaryFactory.createDictionaryForTest(context, dictionary,
startOffset, length, flagArray), locale);
}
diff --git a/java/src/com/android/inputmethod/latin/Utils.java b/java/src/com/android/inputmethod/latin/Utils.java
index 38148438b..8e0cfa122 100644
--- a/java/src/com/android/inputmethod/latin/Utils.java
+++ b/java/src/com/android/inputmethod/latin/Utils.java
@@ -154,10 +154,21 @@ public class Utils {
}
}
- return filteredImisCount > 1
- // imm.getEnabledInputMethodSubtypeList(null, false) will return the current IME's enabled
- // input method subtype (The current IME should be LatinIME.)
- || imm.getEnabledInputMethodSubtypeList(null, false).size() > 1;
+ if (filteredImisCount > 1) {
+ return true;
+ }
+ final List<InputMethodSubtypeCompatWrapper> subtypes =
+ imm.getEnabledInputMethodSubtypeList(null, true);
+ int keyboardCount = 0;
+ // imm.getEnabledInputMethodSubtypeList(null, true) will return the current IME's
+ // both explicitly and implicitly enabled input method subtype.
+ // (The current IME should be LatinIME.)
+ for (InputMethodSubtypeCompatWrapper subtype : subtypes) {
+ if (SubtypeSwitcher.KEYBOARD_MODE.equals(subtype.getMode())) {
+ ++keyboardCount;
+ }
+ }
+ return keyboardCount > 1;
}
public static String getInputMethodId(InputMethodManagerCompatWrapper imm, String packageName) {
diff --git a/java/src/com/android/inputmethod/latin/suggestions/MoreSuggestionsView.java b/java/src/com/android/inputmethod/latin/suggestions/MoreSuggestionsView.java
index b5f67ace0..600f14e04 100644
--- a/java/src/com/android/inputmethod/latin/suggestions/MoreSuggestionsView.java
+++ b/java/src/com/android/inputmethod/latin/suggestions/MoreSuggestionsView.java
@@ -56,13 +56,13 @@ public class MoreSuggestionsView extends KeyboardView implements MoreKeysPanel {
private final KeyboardActionListener mSuggestionsPaneListener =
new KeyboardActionListener.Adapter() {
@Override
- public void onPress(int primaryCode, boolean withSliding) {
- mListener.onPress(primaryCode, withSliding);
+ public void onPressKey(int primaryCode) {
+ mListener.onPressKey(primaryCode);
}
@Override
- public void onRelease(int primaryCode, boolean withSliding) {
- mListener.onRelease(primaryCode, withSliding);
+ public void onReleaseKey(int primaryCode, boolean withSliding) {
+ mListener.onReleaseKey(primaryCode, withSliding);
}
@Override
diff --git a/native/jni/com_android_inputmethod_latin_BinaryDictionary.cpp b/native/jni/com_android_inputmethod_latin_BinaryDictionary.cpp
index 71a893ca7..f2878eea5 100644
--- a/native/jni/com_android_inputmethod_latin_BinaryDictionary.cpp
+++ b/native/jni/com_android_inputmethod_latin_BinaryDictionary.cpp
@@ -51,7 +51,7 @@ static jlong latinime_BinaryDictionary_open(JNIEnv *env, jobject object,
PROF_START(66);
const char *sourceDirChars = env->GetStringUTFChars(sourceDir, 0);
if (sourceDirChars == 0) {
- LOGE("DICT: Can't get sourceDir string");
+ AKLOGE("DICT: Can't get sourceDir string");
return 0;
}
int fd = 0;
@@ -61,7 +61,7 @@ static jlong latinime_BinaryDictionary_open(JNIEnv *env, jobject object,
/* mmap version */
fd = open(sourceDirChars, O_RDONLY);
if (fd < 0) {
- LOGE("DICT: Can't open sourceDir. sourceDirChars=%s errno=%d", sourceDirChars, errno);
+ AKLOGE("DICT: Can't open sourceDir. sourceDirChars=%s errno=%d", sourceDirChars, errno);
return 0;
}
int pagesize = getpagesize();
@@ -70,7 +70,7 @@ static jlong latinime_BinaryDictionary_open(JNIEnv *env, jobject object,
int adjDictSize = dictSize + adjust;
dictBuf = mmap(0, sizeof(char) * adjDictSize, PROT_READ, MAP_PRIVATE, fd, adjDictOffset);
if (dictBuf == MAP_FAILED) {
- LOGE("DICT: Can't mmap dictionary. errno=%d", errno);
+ AKLOGE("DICT: Can't mmap dictionary. errno=%d", errno);
return 0;
}
dictBuf = (void *)((char *)dictBuf + adjust);
@@ -79,39 +79,39 @@ static jlong latinime_BinaryDictionary_open(JNIEnv *env, jobject object,
FILE *file = 0;
file = fopen(sourceDirChars, "rb");
if (file == 0) {
- LOGE("DICT: Can't fopen sourceDir. sourceDirChars=%s errno=%d", sourceDirChars, errno);
+ AKLOGE("DICT: Can't fopen sourceDir. sourceDirChars=%s errno=%d", sourceDirChars, errno);
return 0;
}
dictBuf = malloc(sizeof(char) * dictSize);
if (!dictBuf) {
- LOGE("DICT: Can't allocate memory region for dictionary. errno=%d", errno);
+ AKLOGE("DICT: Can't allocate memory region for dictionary. errno=%d", errno);
return 0;
}
int ret = fseek(file, (long)dictOffset, SEEK_SET);
if (ret != 0) {
- LOGE("DICT: Failure in fseek. ret=%d errno=%d", ret, errno);
+ AKLOGE("DICT: Failure in fseek. ret=%d errno=%d", ret, errno);
return 0;
}
ret = fread(dictBuf, sizeof(char) * dictSize, 1, file);
if (ret != 1) {
- LOGE("DICT: Failure in fread. ret=%d errno=%d", ret, errno);
+ AKLOGE("DICT: Failure in fread. ret=%d errno=%d", ret, errno);
return 0;
}
ret = fclose(file);
if (ret != 0) {
- LOGE("DICT: Failure in fclose. ret=%d errno=%d", ret, errno);
+ AKLOGE("DICT: Failure in fclose. ret=%d errno=%d", ret, errno);
return 0;
}
#endif // USE_MMAP_FOR_DICTIONARY
env->ReleaseStringUTFChars(sourceDir, sourceDirChars);
if (!dictBuf) {
- LOGE("DICT: dictBuf is null");
+ AKLOGE("DICT: dictBuf is null");
return 0;
}
Dictionary *dictionary = 0;
if (BinaryFormat::UNKNOWN_FORMAT == BinaryFormat::detectFormat((uint8_t*)dictBuf)) {
- LOGE("DICT: dictionary format is unknown, bad magic number");
+ AKLOGE("DICT: dictionary format is unknown, bad magic number");
#ifdef USE_MMAP_FOR_DICTIONARY
releaseDictBuf(((char*)dictBuf) - adjust, adjDictSize, fd);
#else // USE_MMAP_FOR_DICTIONARY
@@ -230,11 +230,11 @@ void releaseDictBuf(void* dictBuf, const size_t length, int fd) {
#ifdef USE_MMAP_FOR_DICTIONARY
int ret = munmap(dictBuf, length);
if (ret != 0) {
- LOGE("DICT: Failure in munmap. ret=%d errno=%d", ret, errno);
+ AKLOGE("DICT: Failure in munmap. ret=%d errno=%d", ret, errno);
}
ret = close(fd);
if (ret != 0) {
- LOGE("DICT: Failure in close. ret=%d errno=%d", ret, errno);
+ AKLOGE("DICT: Failure in close. ret=%d errno=%d", ret, errno);
}
#else // USE_MMAP_FOR_DICTIONARY
free(dictBuf);
diff --git a/native/jni/jni_common.cpp b/native/jni/jni_common.cpp
index 958abfd67..85d26836a 100644
--- a/native/jni/jni_common.cpp
+++ b/native/jni/jni_common.cpp
@@ -36,18 +36,18 @@ jint JNI_OnLoad(JavaVM* vm, void* reserved) {
jint result = -1;
if (vm->GetEnv((void**) &env, JNI_VERSION_1_4) != JNI_OK) {
- LOGE("ERROR: GetEnv failed");
+ AKLOGE("ERROR: GetEnv failed");
goto bail;
}
assert(env != 0);
if (!register_BinaryDictionary(env)) {
- LOGE("ERROR: BinaryDictionary native registration failed");
+ AKLOGE("ERROR: BinaryDictionary native registration failed");
goto bail;
}
if (!register_ProximityInfo(env)) {
- LOGE("ERROR: ProximityInfo native registration failed");
+ AKLOGE("ERROR: ProximityInfo native registration failed");
goto bail;
}
@@ -64,11 +64,11 @@ int registerNativeMethods(JNIEnv* env, const char* className, JNINativeMethod* m
int numMethods) {
jclass clazz = env->FindClass(className);
if (clazz == 0) {
- LOGE("Native registration unable to find class '%s'", className);
+ AKLOGE("Native registration unable to find class '%s'", className);
return JNI_FALSE;
}
if (env->RegisterNatives(clazz, methods, numMethods) < 0) {
- LOGE("RegisterNatives failed for '%s'", className);
+ AKLOGE("RegisterNatives failed for '%s'", className);
env->DeleteLocalRef(clazz);
return JNI_FALSE;
}
diff --git a/native/src/bigram_dictionary.cpp b/native/src/bigram_dictionary.cpp
index c340c6c1a..db7734bc7 100644
--- a/native/src/bigram_dictionary.cpp
+++ b/native/src/bigram_dictionary.cpp
@@ -32,8 +32,8 @@ BigramDictionary::BigramDictionary(const unsigned char *dict, int maxWordLength,
MAX_ALTERNATIVES(maxAlternatives), IS_LATEST_DICT_VERSION(isLatestDictVersion),
HAS_BIGRAM(hasBigram), mParentDictionary(parentDictionary) {
if (DEBUG_DICT) {
- LOGI("BigramDictionary - constructor");
- LOGI("Has Bigram : %d", hasBigram);
+ AKLOGI("BigramDictionary - constructor");
+ AKLOGI("Has Bigram : %d", hasBigram);
}
}
@@ -46,7 +46,7 @@ bool BigramDictionary::addWordBigram(unsigned short *word, int length, int frequ
#ifdef FLAG_DBG
char s[length + 1];
for (int i = 0; i <= length; i++) s[i] = word[i];
- LOGI("Bigram: Found word = %s, freq = %d :", s, frequency);
+ AKLOGI("Bigram: Found word = %s, freq = %d :", s, frequency);
#endif
}
@@ -60,7 +60,7 @@ bool BigramDictionary::addWordBigram(unsigned short *word, int length, int frequ
insertAt++;
}
if (DEBUG_DICT) {
- LOGI("Bigram: InsertAt -> %d maxBigrams: %d", insertAt, mMaxBigrams);
+ AKLOGI("Bigram: InsertAt -> %d maxBigrams: %d", insertAt, mMaxBigrams);
}
if (insertAt < mMaxBigrams) {
memmove((char*) mBigramFreq + (insertAt + 1) * sizeof(mBigramFreq[0]),
@@ -76,7 +76,7 @@ bool BigramDictionary::addWordBigram(unsigned short *word, int length, int frequ
}
*dest = 0; // NULL terminate
if (DEBUG_DICT) {
- LOGI("Bigram: Added word at %d", insertAt);
+ AKLOGI("Bigram: Added word at %d", insertAt);
}
return true;
}
diff --git a/native/src/binary_format.h b/native/src/binary_format.h
index 9944fa2bd..1d74998f6 100644
--- a/native/src/binary_format.h
+++ b/native/src/binary_format.h
@@ -61,7 +61,9 @@ inline int BinaryFormat::detectFormat(const uint8_t* const dict) {
}
inline int BinaryFormat::getGroupCountAndForwardPointer(const uint8_t* const dict, int* pos) {
- return dict[(*pos)++];
+ const int msb = dict[(*pos)++];
+ if (msb < 0x80) return msb;
+ return ((msb & 0x7F) << 8) | dict[(*pos)++];
}
inline uint8_t BinaryFormat::getFlagsAndForwardPointer(const uint8_t* const dict, int* pos) {
diff --git a/native/src/correction.cpp b/native/src/correction.cpp
index dc31bfae7..6a129d4e3 100644
--- a/native/src/correction.cpp
+++ b/native/src/correction.cpp
@@ -42,7 +42,7 @@ inline static void initEditDistance(int *editDistanceTable) {
inline static void dumpEditDistance10ForDebug(int *editDistanceTable, const int inputLength,
const int outputLength) {
if (DEBUG_DICT) {
- LOGI("EditDistanceTable");
+ AKLOGI("EditDistanceTable");
for (int i = 0; i <= 10; ++i) {
int c[11];
for (int j = 0; j <= 10; ++j) {
@@ -52,7 +52,7 @@ inline static void dumpEditDistance10ForDebug(int *editDistanceTable, const int
c[j] = -1;
}
}
- LOGI("[ %d, %d, %d, %d, %d, %d, %d, %d, %d, %d, %d ]",
+ AKLOGI("[ %d, %d, %d, %d, %d, %d, %d, %d, %d, %d, %d ]",
c[0], c[1], c[2], c[3], c[4], c[5], c[6], c[7], c[8], c[9], c[10]);
}
}
@@ -83,8 +83,8 @@ inline static void calcEditDistanceOneStep(int *editDistanceTable, const unsigne
inline static int getCurrentEditDistance(
int *editDistanceTable, const int inputLength, const int outputLength) {
- if (DEBUG_DICT) {
- LOGI("getCurrentEditDistance %d, %d", inputLength, outputLength);
+ if (DEBUG_EDIT_DISTANCE) {
+ AKLOGI("getCurrentEditDistance %d, %d", inputLength, outputLength);
}
return editDistanceTable[(inputLength + 1) * (outputLength + 1) - 1];
}
@@ -168,8 +168,8 @@ int Correction::getFinalFreq(const int freq, unsigned short **word, int *wordLen
const int outputIndex = mTerminalOutputIndex;
const int inputIndex = mTerminalInputIndex;
*wordLength = outputIndex + 1;
- if (mProximityInfo->sameAsTyped(mWord, outputIndex + 1) || outputIndex < MIN_SUGGEST_DEPTH) {
- return -1;
+ if (outputIndex < MIN_SUGGEST_DEPTH) {
+ return NOT_A_FREQUENCY;
}
*word = mWord;
@@ -215,20 +215,10 @@ int Correction::goDownTree(
}
// TODO: remove
-int Correction::getOutputIndex() {
- return mOutputIndex;
-}
-
-// TODO: remove
int Correction::getInputIndex() {
return mInputIndex;
}
-// TODO: remove
-bool Correction::needsToTraverseAllNodes() {
- return mNeedsToTraverseAllNodes;
-}
-
void Correction::incrementInputIndex() {
++mInputIndex;
}
@@ -278,13 +268,12 @@ void Correction::addCharToCurrentWord(const int32_t c) {
mWord, mOutputIndex + 1);
}
-// TODO: inline?
Correction::CorrectionType Correction::processSkipChar(
const int32_t c, const bool isTerminal, const bool inputIndexIncremented) {
addCharToCurrentWord(c);
- if (needsToTraverseAllNodes() && isTerminal) {
- mTerminalInputIndex = mInputIndex - (inputIndexIncremented ? 1 : 0);
- mTerminalOutputIndex = mOutputIndex;
+ mTerminalInputIndex = mInputIndex - (inputIndexIncremented ? 1 : 0);
+ mTerminalOutputIndex = mOutputIndex;
+ if (mNeedsToTraverseAllNodes && isTerminal) {
incrementOutputIndex();
return TRAVERSE_ALL_ON_TERMINAL;
} else {
@@ -293,6 +282,13 @@ Correction::CorrectionType Correction::processSkipChar(
}
}
+Correction::CorrectionType Correction::processUnrelatedCorrectionType() {
+ // Needs to set mTerminalInputIndex and mTerminalOutputIndex before returning any CorrectionType
+ mTerminalInputIndex = mInputIndex;
+ mTerminalOutputIndex = mOutputIndex;
+ return UNRELATED;
+}
+
inline bool isEquivalentChar(ProximityInfo::ProximityType type) {
return type == ProximityInfo::EQUIVALENT_CHAR;
}
@@ -301,7 +297,7 @@ Correction::CorrectionType Correction::processCharAndCalcState(
const int32_t c, const bool isTerminal) {
const int correctionCount = (mSkippedCount + mExcessiveCount + mTransposedCount);
if (correctionCount > mMaxErrors) {
- return UNRELATED;
+ return processUnrelatedCorrectionType();
}
// TODO: Change the limit if we'll allow two or more corrections
@@ -378,10 +374,10 @@ Correction::CorrectionType Correction::processCharAndCalcState(
--mTransposedCount;
if (DEBUG_CORRECTION) {
DUMP_WORD(mWord, mOutputIndex);
- LOGI("UNRELATED(0): %d, %d, %d, %d, %c", mProximityCount, mSkippedCount,
+ AKLOGI("UNRELATED(0): %d, %d, %d, %d, %c", mProximityCount, mSkippedCount,
mTransposedCount, mExcessiveCount, c);
}
- return UNRELATED;
+ return processUnrelatedCorrectionType();
}
}
@@ -404,7 +400,7 @@ Correction::CorrectionType Correction::processCharAndCalcState(
&& isEquivalentChar(mProximityInfo->getMatchedProximityId(
mInputIndex, mWord[mOutputIndex - 1], false))) {
if (DEBUG_CORRECTION) {
- LOGI("CONVERSION p->e %c", mWord[mOutputIndex - 1]);
+ AKLOGI("CONVERSION p->e %c", mWord[mOutputIndex - 1]);
}
// Conversion p->e
// Example:
@@ -481,10 +477,10 @@ Correction::CorrectionType Correction::processCharAndCalcState(
} else {
if (DEBUG_CORRECTION) {
DUMP_WORD(mWord, mOutputIndex);
- LOGI("UNRELATED(1): %d, %d, %d, %d, %c", mProximityCount, mSkippedCount,
+ AKLOGI("UNRELATED(1): %d, %d, %d, %d, %c", mProximityCount, mSkippedCount,
mTransposedCount, mExcessiveCount, c);
}
- return UNRELATED;
+ return processUnrelatedCorrectionType();
}
} else if (secondTransposing) {
// If inputIndex is greater than mInputLength, that means there is no
@@ -534,11 +530,13 @@ Correction::CorrectionType Correction::processCharAndCalcState(
mTerminalOutputIndex = mOutputIndex - 1;
if (DEBUG_CORRECTION) {
DUMP_WORD(mWord, mOutputIndex);
- LOGI("ONTERMINAL(1): %d, %d, %d, %d, %c", mProximityCount, mSkippedCount,
+ AKLOGI("ONTERMINAL(1): %d, %d, %d, %d, %c", mProximityCount, mSkippedCount,
mTransposedCount, mExcessiveCount, c);
}
return ON_TERMINAL;
} else {
+ mTerminalInputIndex = mInputIndex - 1;
+ mTerminalOutputIndex = mOutputIndex - 1;
return NOT_ON_TERMINAL;
}
}
@@ -655,9 +653,10 @@ int Correction::RankingAlgorithm::calculateFinalFreq(const int inputIndex, const
int finalFreq = freq;
// TODO: Optimize this.
- // TODO: Ignoring edit distance for transposed char, for now
- if (transposedCount == 0 && (proximityMatchedCount > 0 || skipped || excessiveCount > 0)) {
- ed = getCurrentEditDistance(editDistanceTable, inputLength, outputIndex + 1);
+ if (transposedCount > 0 || proximityMatchedCount > 0 || skipped || excessiveCount > 0) {
+ ed = getCurrentEditDistance(editDistanceTable, inputLength, outputIndex + 1)
+ - transposedCount;
+
const int matchWeight = powerIntCapped(typedLetterMultiplier,
max(inputLength, outputIndex + 1) - ed);
multiplyIntCapped(matchWeight, &finalFreq);
@@ -669,21 +668,22 @@ int Correction::RankingAlgorithm::calculateFinalFreq(const int inputIndex, const
ed = max(0, ed - quoteDiffCount);
- if (ed == 1 && (inputLength == outputIndex || inputLength == outputIndex + 2)) {
- // Promote a word with just one skipped or excessive char
- if (sameLength) {
- multiplyRate(WORDS_WITH_JUST_ONE_CORRECTION_PROMOTION_RATE, &finalFreq);
- } else {
+ if (transposedCount < 1) {
+ if (ed == 1 && (inputLength == outputIndex || inputLength == outputIndex + 2)) {
+ // Promote a word with just one skipped or excessive char
+ if (sameLength) {
+ multiplyRate(WORDS_WITH_JUST_ONE_CORRECTION_PROMOTION_RATE, &finalFreq);
+ } else {
+ multiplyIntCapped(typedLetterMultiplier, &finalFreq);
+ }
+ } else if (ed == 0) {
multiplyIntCapped(typedLetterMultiplier, &finalFreq);
+ sameLength = true;
}
- } else if (ed == 0) {
- multiplyIntCapped(typedLetterMultiplier, &finalFreq);
- sameLength = true;
}
adjustedProximityMatchedCount = min(max(0, ed - (outputIndex + 1 - inputLength)),
proximityMatchedCount);
} else {
- // TODO: Calculate the edit distance for transposed char
const int matchWeight = powerIntCapped(typedLetterMultiplier, matchCount);
multiplyIntCapped(matchWeight, &finalFreq);
}
@@ -703,7 +703,7 @@ int Correction::RankingAlgorithm::calculateFinalFreq(const int inputIndex, const
/ (10 * inputLength
- WORDS_WITH_MISSING_CHARACTER_DEMOTION_START_POS_10X + 10);
if (DEBUG_DICT_FULL) {
- LOGI("Demotion rate for missing character is %d.", demotionRate);
+ AKLOGI("Demotion rate for missing character is %d.", demotionRate);
}
multiplyRate(demotionRate, &finalFreq);
}
@@ -717,7 +717,7 @@ int Correction::RankingAlgorithm::calculateFinalFreq(const int inputIndex, const
multiplyRate(WORDS_WITH_EXCESSIVE_CHARACTER_DEMOTION_RATE, &finalFreq);
if (!lastCharExceeded && !proximityInfo->existsAdjacentProximityChars(excessivePos)) {
if (DEBUG_CORRECTION_FREQ) {
- LOGI("Double excessive demotion");
+ AKLOGI("Double excessive demotion");
}
// If an excessive character is not adjacent to the left char or the right char,
// we will demote this word.
@@ -767,7 +767,7 @@ int Correction::RankingAlgorithm::calculateFinalFreq(const int inputIndex, const
for (int i = 0; i < adjustedProximityMatchedCount; ++i) {
// A word with proximity corrections
if (DEBUG_DICT_FULL) {
- LOGI("Found a proximity correction.");
+ AKLOGI("Found a proximity correction.");
}
multiplyIntCapped(typedLetterMultiplier, &finalFreq);
multiplyRate(WORDS_WITH_PROXIMITY_CHARACTER_DEMOTION_RATE, &finalFreq);
@@ -828,12 +828,12 @@ int Correction::RankingAlgorithm::calculateFinalFreq(const int inputIndex, const
}
if (DEBUG_DICT_FULL) {
- LOGI("calc: %d, %d", outputIndex, sameLength);
+ AKLOGI("calc: %d, %d", outputIndex, sameLength);
}
if (DEBUG_CORRECTION_FREQ) {
DUMP_WORD(correction->mWord, outputIndex + 1);
- LOGI("FinalFreq: [P%d, S%d, T%d, E%d] %d, %d, %d, %d, %d", proximityMatchedCount,
+ AKLOGI("FinalFreq: [P%d, S%d, T%d, E%d] %d, %d, %d, %d, %d", proximityMatchedCount,
skippedCount, transposedCount, excessiveCount, lastCharExceeded, sameLength,
quoteDiffCount, ed, finalFreq);
}
@@ -874,7 +874,102 @@ int Correction::RankingAlgorithm::calcFreqForSplitTwoWords(
firstCapitalizedWordDemotion ^ secondCapitalizedWordDemotion;
if (DEBUG_DICT_FULL) {
- LOGI("Two words: %c, %c, %d", word[0], word[firstWordLength + 1], capitalizedWordDemotion);
+ AKLOGI("Two words: %c, %c, %d",
+ word[0], word[firstWordLength + 1], capitalizedWordDemotion);
+ }
+
+ if (firstWordLength == 0 || secondWordLength == 0) {
+ return 0;
+ }
+ const int firstDemotionRate = 100 - 100 / (firstWordLength + 1);
+ int tempFirstFreq = firstFreq;
+ multiplyRate(firstDemotionRate, &tempFirstFreq);
+
+ const int secondDemotionRate = 100 - 100 / (secondWordLength + 1);
+ int tempSecondFreq = secondFreq;
+ multiplyRate(secondDemotionRate, &tempSecondFreq);
+
+ const int totalLength = firstWordLength + secondWordLength;
+
+ // Promote pairFreq with multiplying by 2, because the word length is the same as the typed
+ // length.
+ int totalFreq = tempFirstFreq + tempSecondFreq;
+
+ // This is a workaround to try offsetting the not-enough-demotion which will be done in
+ // calcNormalizedScore in Utils.java.
+ // In calcNormalizedScore the score will be demoted by (1 - 1 / length)
+ // but we demoted only (1 - 1 / (length + 1)) so we will additionally adjust freq by
+ // (1 - 1 / length) / (1 - 1 / (length + 1)) = (1 - 1 / (length * length))
+ const int normalizedScoreNotEnoughDemotionAdjustment = 100 - 100 / (totalLength * totalLength);
+ multiplyRate(normalizedScoreNotEnoughDemotionAdjustment, &totalFreq);
+
+ // At this moment, totalFreq is calculated by the following formula:
+ // (firstFreq * (1 - 1 / (firstWordLength + 1)) + secondFreq * (1 - 1 / (secondWordLength + 1)))
+ // * (1 - 1 / totalLength) / (1 - 1 / (totalLength + 1))
+
+ multiplyIntCapped(powerIntCapped(typedLetterMultiplier, totalLength), &totalFreq);
+
+ // This is another workaround to offset the demotion which will be done in
+ // calcNormalizedScore in Utils.java.
+ // In calcNormalizedScore the score will be demoted by (1 - 1 / length) so we have to promote
+ // the same amount because we already have adjusted the synthetic freq of this "missing or
+ // mistyped space" suggestion candidate above in this method.
+ const int normalizedScoreDemotionRateOffset = (100 + 100 / totalLength);
+ multiplyRate(normalizedScoreDemotionRateOffset, &totalFreq);
+
+ if (isSpaceProximity) {
+ // A word pair with one space proximity correction
+ if (DEBUG_DICT) {
+ AKLOGI("Found a word pair with space proximity correction.");
+ }
+ multiplyIntCapped(typedLetterMultiplier, &totalFreq);
+ multiplyRate(WORDS_WITH_PROXIMITY_CHARACTER_DEMOTION_RATE, &totalFreq);
+ }
+
+ multiplyRate(WORDS_WITH_MISSING_SPACE_CHARACTER_DEMOTION_RATE, &totalFreq);
+
+ if (capitalizedWordDemotion) {
+ multiplyRate(TWO_WORDS_CAPITALIZED_DEMOTION_RATE, &totalFreq);
+ }
+
+ return totalFreq;
+}
+
+/* static */
+int Correction::RankingAlgorithm::calcFreqForSplitTwoWordsOld(
+ const int firstFreq, const int secondFreq, const Correction* correction,
+ const unsigned short *word) {
+ const int spaceProximityPos = correction->mSpaceProximityPos;
+ const int missingSpacePos = correction->mMissingSpacePos;
+ if (DEBUG_DICT) {
+ int inputCount = 0;
+ if (spaceProximityPos >= 0) ++inputCount;
+ if (missingSpacePos >= 0) ++inputCount;
+ assert(inputCount <= 1);
+ }
+ const bool isSpaceProximity = spaceProximityPos >= 0;
+ const int inputLength = correction->mInputLength;
+ const int firstWordLength = isSpaceProximity ? spaceProximityPos : missingSpacePos;
+ const int secondWordLength = isSpaceProximity ? (inputLength - spaceProximityPos - 1)
+ : (inputLength - missingSpacePos);
+ const int typedLetterMultiplier = correction->TYPED_LETTER_MULTIPLIER;
+
+ bool firstCapitalizedWordDemotion = false;
+ if (firstWordLength >= 2) {
+ firstCapitalizedWordDemotion = isUpperCase(word[0]);
+ }
+
+ bool secondCapitalizedWordDemotion = false;
+ if (secondWordLength >= 2) {
+ secondCapitalizedWordDemotion = isUpperCase(word[firstWordLength + 1]);
+ }
+
+ const bool capitalizedWordDemotion =
+ firstCapitalizedWordDemotion ^ secondCapitalizedWordDemotion;
+
+ if (DEBUG_DICT_FULL) {
+ AKLOGI("Two words: %c, %c, %d",
+ word[0], word[firstWordLength + 1], capitalizedWordDemotion);
}
if (firstWordLength == 0 || secondWordLength == 0) {
@@ -919,7 +1014,7 @@ int Correction::RankingAlgorithm::calcFreqForSplitTwoWords(
if (isSpaceProximity) {
// A word pair with one space proximity correction
if (DEBUG_DICT) {
- LOGI("Found a word pair with space proximity correction.");
+ AKLOGI("Found a word pair with space proximity correction.");
}
multiplyIntCapped(typedLetterMultiplier, &totalFreq);
multiplyRate(WORDS_WITH_PROXIMITY_CHARACTER_DEMOTION_RATE, &totalFreq);
@@ -965,10 +1060,10 @@ inline static int editDistanceInternal(
}
if (DEBUG_EDIT_DISTANCE) {
- LOGI("IN = %d, OUT = %d", beforeLength, afterLength);
+ AKLOGI("IN = %d, OUT = %d", beforeLength, afterLength);
for (int i = 0; i < li; ++i) {
for (int j = 0; j < lo; ++j) {
- LOGI("EDIT[%d][%d], %d", i, j, dp[i * lo + j]);
+ AKLOGI("EDIT[%d][%d], %d", i, j, dp[i * lo + j]);
}
}
}
diff --git a/native/src/correction.h b/native/src/correction.h
index 4012e7e82..22a424f5c 100644
--- a/native/src/correction.h
+++ b/native/src/correction.h
@@ -48,7 +48,6 @@ class Correction {
void checkState();
bool initProcessState(const int index);
- int getOutputIndex();
int getInputIndex();
virtual ~Correction();
@@ -101,6 +100,8 @@ class Correction {
const int freq, int *editDistanceTable, const Correction* correction);
static int calcFreqForSplitTwoWords(const int firstFreq, const int secondFreq,
const Correction* correction, const unsigned short *word);
+ static int calcFreqForSplitTwoWordsOld(const int firstFreq, const int secondFreq,
+ const Correction* correction, const unsigned short *word);
static double calcNormalizedScore(const unsigned short* before, const int beforeLength,
const unsigned short* after, const int afterLength, const int score);
static int editDistance(const unsigned short* before,
@@ -115,11 +116,11 @@ class Correction {
private:
inline void incrementInputIndex();
inline void incrementOutputIndex();
- inline bool needsToTraverseAllNodes();
inline void startToTraverseAllNodes();
inline bool isQuote(const unsigned short c);
inline CorrectionType processSkipChar(
const int32_t c, const bool isTerminal, const bool inputIndexIncremented);
+ inline CorrectionType processUnrelatedCorrectionType();
inline void addCharToCurrentWord(const int32_t c);
const int TYPED_LETTER_MULTIPLIER;
diff --git a/native/src/debug.h b/native/src/debug.h
index 38b2f107a..b13052c95 100644
--- a/native/src/debug.h
+++ b/native/src/debug.h
@@ -42,7 +42,7 @@ static inline unsigned char* convertToUnibyteStringAndReplaceLastChar(unsigned s
static inline void LOGI_S16(unsigned short* string, const unsigned int length) {
unsigned char tmp_buffer[length];
convertToUnibyteString(string, tmp_buffer, length);
- LOGI(">> %s", tmp_buffer);
+ AKLOGI(">> %s", tmp_buffer);
// The log facility is throwing out log that comes too fast. The following
// is a dirty way of slowing down processing so that we can see all log.
// TODO : refactor this in a blocking log or something.
@@ -53,7 +53,7 @@ static inline void LOGI_S16_PLUS(unsigned short* string, const unsigned int leng
unsigned char c) {
unsigned char tmp_buffer[length+1];
convertToUnibyteStringAndReplaceLastChar(string, tmp_buffer, length, c);
- LOGI(">> %s", tmp_buffer);
+ AKLOGI(">> %s", tmp_buffer);
// Likewise
// usleep(10);
}
@@ -64,7 +64,7 @@ static inline void printDebug(const char* tag, int* codes, int codesSize, int MA
buf[codesSize] = 0;
while (--codesSize >= 0)
buf[codesSize] = (unsigned char)codes[codesSize * MAX_PROXIMITY_CHARS];
- LOGI("%s, WORD = %s", tag, buf);
+ AKLOGI("%s, WORD = %s", tag, buf);
free(buf);
}
diff --git a/native/src/defines.h b/native/src/defines.h
index d636e5197..d739043a4 100644
--- a/native/src/defines.h
+++ b/native/src/defines.h
@@ -20,15 +20,32 @@
#if defined(FLAG_DO_PROFILE) || defined(FLAG_DBG)
#include <cutils/log.h>
+#define AKLOGE ALOGE
+#define AKLOGI ALOGI
+
+#define DUMP_WORD(word, length) do { dumpWord(word, length); } while(0)
+
+static char charBuf[50];
+
+static void dumpWord(const unsigned short* word, const int length) {
+ for (int i = 0; i < length; ++i) {
+ charBuf[i] = word[i];
+ }
+ charBuf[length] = 0;
+ AKLOGI("[ %s ]", charBuf);
+}
+
#else
-#define LOGE(fmt, ...)
-#define LOGI(fmt, ...)
+#define AKLOGE(fmt, ...)
+#define AKLOGI(fmt, ...)
+#define DUMP_WORD(word, length)
#endif
#ifdef FLAG_DO_PROFILE
// Profiler
#include <cutils/log.h>
#include <time.h>
+
#define PROF_BUF_SIZE 100
static double profile_buf[PROF_BUF_SIZE];
static double profile_old[PROF_BUF_SIZE];
@@ -42,8 +59,8 @@ static unsigned int profile_counter[PROF_BUF_SIZE];
#define PROF_CLOSE do { PROF_END(PROF_BUF_SIZE - 1); PROF_OUTALL; } while(0)
#define PROF_END(prof_buf_id) profile_buf[prof_buf_id] += ((clock()) - profile_old[prof_buf_id])
#define PROF_CLOCKOUT(prof_buf_id) \
- LOGI("%s : clock is %f", __FUNCTION__, (clock() - profile_old[prof_buf_id]))
-#define PROF_OUTALL do { LOGI("--- %s ---", __FUNCTION__); prof_out(); } while(0)
+ AKLOGI("%s : clock is %f", __FUNCTION__, (clock() - profile_old[prof_buf_id]))
+#define PROF_OUTALL do { AKLOGI("--- %s ---", __FUNCTION__); prof_out(); } while(0)
static void prof_reset(void) {
for (int i = 0; i < PROF_BUF_SIZE; ++i) {
@@ -55,9 +72,9 @@ static void prof_reset(void) {
static void prof_out(void) {
if (profile_counter[PROF_BUF_SIZE - 1] != 1) {
- LOGI("Error: You must call PROF_OPEN before PROF_CLOSE.");
+ AKLOGI("Error: You must call PROF_OPEN before PROF_CLOSE.");
}
- LOGI("Total time is %6.3f ms.",
+ AKLOGI("Total time is %6.3f ms.",
profile_buf[PROF_BUF_SIZE - 1] * 1000 / (double)CLOCKS_PER_SEC);
double all = 0;
for (int i = 0; i < PROF_BUF_SIZE - 1; ++i) {
@@ -66,7 +83,7 @@ static void prof_out(void) {
if (all == 0) all = 1;
for (int i = 0; i < PROF_BUF_SIZE - 1; ++i) {
if (profile_buf[i] != 0) {
- LOGI("(%d): Used %4.2f%%, %8.4f ms. Called %d times.",
+ AKLOGI("(%d): Used %4.2f%%, %8.4f ms. Called %d times.",
i, (profile_buf[i] * 100 / all),
profile_buf[i] * 1000 / (double)CLOCKS_PER_SEC, profile_counter[i]);
}
@@ -100,20 +117,8 @@ static void prof_out(void) {
#define DEBUG_TRACE DEBUG_DICT_FULL
#define DEBUG_PROXIMITY_INFO false
#define DEBUG_CORRECTION false
-#define DEBUG_CORRECTION_FREQ true
-#define DEBUG_WORDS_PRIORITY_QUEUE true
-
-#define DUMP_WORD(word, length) do { dumpWord(word, length); } while(0)
-
-static char charBuf[50];
-
-static void dumpWord(const unsigned short* word, const int length) {
- for (int i = 0; i < length; ++i) {
- charBuf[i] = word[i];
- }
- charBuf[length] = 0;
- LOGI("[ %s ]", charBuf);
-}
+#define DEBUG_CORRECTION_FREQ false
+#define DEBUG_WORDS_PRIORITY_QUEUE false
#else // FLAG_DBG
@@ -128,7 +133,6 @@ static void dumpWord(const unsigned short* word, const int length) {
#define DEBUG_CORRECTION_FREQ false
#define DEBUG_WORDS_PRIORITY_QUEUE false
-#define DUMP_WORD(word, length)
#endif // FLAG_DBG
@@ -168,6 +172,7 @@ static void dumpWord(const unsigned short* word, const int length) {
#define EQUIVALENT_CHAR_WITHOUT_DISTANCE_INFO -2
#define PROXIMITY_CHAR_WITHOUT_DISTANCE_INFO -3
#define NOT_A_INDEX -1
+#define NOT_A_FREQUENCY -1
#define KEYCODE_SPACE ' '
@@ -185,7 +190,7 @@ static void dumpWord(const unsigned short* word, const int length) {
#define WORDS_WITH_MISSING_SPACE_CHARACTER_DEMOTION_RATE 67
#define WORDS_WITH_EXCESSIVE_CHARACTER_DEMOTION_RATE 75
#define WORDS_WITH_EXCESSIVE_CHARACTER_OUT_OF_PROXIMITY_DEMOTION_RATE 75
-#define WORDS_WITH_TRANSPOSED_CHARACTERS_DEMOTION_RATE 60
+#define WORDS_WITH_TRANSPOSED_CHARACTERS_DEMOTION_RATE 70
#define FULL_MATCHED_WORDS_PROMOTION_RATE 120
#define WORDS_WITH_PROXIMITY_CHARACTER_DEMOTION_RATE 90
#define WORDS_WITH_MATCH_SKIP_PROMOTION_RATE 105
@@ -204,7 +209,11 @@ static void dumpWord(const unsigned short* word, const int length) {
// Word limit for sub queues used in WordsPriorityQueuePool. Sub queues are temporary queues used
// for better performance.
-#define SUB_QUEUE_MAX_WORDS 5
+// Holds up to 1 candidate for each word
+#define SUB_QUEUE_MAX_WORDS 1
+#define SUB_QUEUE_MAX_COUNT 10
+
+#define TWO_WORDS_CORRECTION_THRESHOLD 0.22f
#define MAX_DEPTH_MULTIPLIER 3
diff --git a/native/src/dictionary.cpp b/native/src/dictionary.cpp
index e3673d425..822c2151d 100644
--- a/native/src/dictionary.cpp
+++ b/native/src/dictionary.cpp
@@ -33,9 +33,9 @@ Dictionary::Dictionary(void *dict, int dictSize, int mmapFd, int dictBufAdjust,
IS_LATEST_DICT_VERSION((((unsigned char*) dict)[0] & 0xFF) >= DICTIONARY_VERSION_MIN) {
if (DEBUG_DICT) {
if (MAX_WORD_LENGTH_INTERNAL < maxWordLength) {
- LOGI("Max word length (%d) is greater than %d",
+ AKLOGI("Max word length (%d) is greater than %d",
maxWordLength, MAX_WORD_LENGTH_INTERNAL);
- LOGI("IN NATIVE SUGGEST Version: %d", (mDict[0] & 0xFF));
+ AKLOGI("IN NATIVE SUGGEST Version: %d", (mDict[0] & 0xFF));
}
}
mCorrection = new Correction(typedLetterMultiplier, fullWordMultiplier);
diff --git a/native/src/dictionary.h b/native/src/dictionary.h
index 79d377a4f..90d7148d5 100644
--- a/native/src/dictionary.h
+++ b/native/src/dictionary.h
@@ -56,16 +56,7 @@ class Dictionary {
// public static utility methods
// static inline methods should be defined in the header file
- static unsigned short getChar(const unsigned char *dict, int *pos);
- static int getCount(const unsigned char *dict, int *pos);
- static bool getTerminal(const unsigned char *dict, int *pos);
- static int getAddress(const unsigned char *dict, int *pos);
- static int getFreq(const unsigned char *dict, const bool isLatestDictVersion, int *pos);
static int wideStrLen(unsigned short *str);
- // returns next sibling's position
- static int setDictionaryValues(const unsigned char *dict, const bool isLatestDictVersion,
- const int pos, unsigned short *c, int *childrenPosition,
- bool *terminal, int *freq);
private:
bool hasBigram();
@@ -87,56 +78,6 @@ class Dictionary {
// public static utility methods
// static inline methods should be defined in the header file
-inline unsigned short Dictionary::getChar(const unsigned char *dict, int *pos) {
- unsigned short ch = (unsigned short) (dict[(*pos)++] & 0xFF);
- // If the code is 255, then actual 16 bit code follows (in big endian)
- if (ch == 0xFF) {
- ch = ((dict[*pos] & 0xFF) << 8) | (dict[*pos + 1] & 0xFF);
- (*pos) += 2;
- }
- return ch;
-}
-
-inline int Dictionary::getCount(const unsigned char *dict, int *pos) {
- return dict[(*pos)++] & 0xFF;
-}
-
-inline bool Dictionary::getTerminal(const unsigned char *dict, int *pos) {
- return (dict[*pos] & FLAG_TERMINAL_MASK) > 0;
-}
-
-inline int Dictionary::getAddress(const unsigned char *dict, int *pos) {
- int address = 0;
- if ((dict[*pos] & FLAG_ADDRESS_MASK) == 0) {
- *pos += 1;
- } else {
- address += (dict[*pos] & (ADDRESS_MASK >> 16)) << 16;
- address += (dict[*pos + 1] & 0xFF) << 8;
- address += (dict[*pos + 2] & 0xFF);
- *pos += 3;
- }
- return address;
-}
-
-inline int Dictionary::getFreq(const unsigned char *dict,
- const bool isLatestDictVersion, int *pos) {
- int freq = dict[(*pos)++] & 0xFF;
- if (isLatestDictVersion) {
- // skipping bigram
- int bigramExist = (dict[*pos] & FLAG_BIGRAM_READ);
- if (bigramExist > 0) {
- int nextBigramExist = 1;
- while (nextBigramExist > 0) {
- (*pos) += 3;
- nextBigramExist = (dict[(*pos)++] & FLAG_BIGRAM_CONTINUED);
- }
- } else {
- (*pos)++;
- }
- }
- return freq;
-}
-
inline int Dictionary::wideStrLen(unsigned short *str) {
if (!str) return 0;
unsigned short *end = str;
@@ -144,22 +85,6 @@ inline int Dictionary::wideStrLen(unsigned short *str) {
end++;
return end - str;
}
-
-inline int Dictionary::setDictionaryValues(const unsigned char *dict,
- const bool isLatestDictVersion, const int pos, unsigned short *c,int *childrenPosition,
- bool *terminal, int *freq) {
- int position = pos;
- // -- at char
- *c = Dictionary::getChar(dict, &position);
- // -- at flag/add
- *terminal = Dictionary::getTerminal(dict, &position);
- *childrenPosition = Dictionary::getAddress(dict, &position);
- // -- after address or flag
- *freq = (*terminal) ? Dictionary::getFreq(dict, isLatestDictVersion, &position) : 1;
- // returns next sibling's position
- return position;
-}
-
} // namespace latinime
#endif // LATINIME_DICTIONARY_H
diff --git a/native/src/proximity_info.cpp b/native/src/proximity_info.cpp
index 95e35263b..b91957c77 100644
--- a/native/src/proximity_info.cpp
+++ b/native/src/proximity_info.cpp
@@ -52,7 +52,7 @@ ProximityInfo::ProximityInfo(const int maxProximityCharsSize, const int keyboard
const int proximityGridLength = GRID_WIDTH * GRID_HEIGHT * MAX_PROXIMITY_CHARS_SIZE;
mProximityCharsArray = new uint32_t[proximityGridLength];
if (DEBUG_PROXIMITY_INFO) {
- LOGI("Create proximity info array %d", proximityGridLength);
+ AKLOGI("Create proximity info array %d", proximityGridLength);
}
memcpy(mProximityCharsArray, proximityCharsArray,
proximityGridLength * sizeof(mProximityCharsArray[0]));
@@ -102,7 +102,7 @@ inline int ProximityInfo::getStartIndexFromCoordinates(const int x, const int y)
bool ProximityInfo::hasSpaceProximity(const int x, const int y) const {
if (x < 0 || y < 0) {
if (DEBUG_DICT) {
- LOGI("HasSpaceProximity: Illegal coordinates (%d, %d)", x, y);
+ AKLOGI("HasSpaceProximity: Illegal coordinates (%d, %d)", x, y);
assert(false);
}
return false;
@@ -110,11 +110,11 @@ bool ProximityInfo::hasSpaceProximity(const int x, const int y) const {
const int startIndex = getStartIndexFromCoordinates(x, y);
if (DEBUG_PROXIMITY_INFO) {
- LOGI("hasSpaceProximity: index %d, %d, %d", startIndex, x, y);
+ AKLOGI("hasSpaceProximity: index %d, %d, %d", startIndex, x, y);
}
for (int i = 0; i < MAX_PROXIMITY_CHARS_SIZE; ++i) {
if (DEBUG_PROXIMITY_INFO) {
- LOGI("Index: %d", mProximityCharsArray[startIndex + i]);
+ AKLOGI("Index: %d", mProximityCharsArray[startIndex + i]);
}
if (mProximityCharsArray[startIndex + i] == KEYCODE_SPACE) {
return true;
diff --git a/native/src/unigram_dictionary.cpp b/native/src/unigram_dictionary.cpp
index e95e03ce5..8be95bc40 100644
--- a/native/src/unigram_dictionary.cpp
+++ b/native/src/unigram_dictionary.cpp
@@ -47,7 +47,7 @@ UnigramDictionary::UnigramDictionary(const uint8_t* const streamStart, int typed
BYTES_IN_ONE_CHAR(MAX_PROXIMITY_CHARS * sizeof(int)),
MAX_UMLAUT_SEARCH_DEPTH(DEFAULT_MAX_UMLAUT_SEARCH_DEPTH) {
if (DEBUG_DICT) {
- LOGI("UnigramDictionary - constructor");
+ AKLOGI("UnigramDictionary - constructor");
}
}
@@ -163,14 +163,14 @@ int UnigramDictionary::getSuggestions(ProximityInfo *proximityInfo,
queuePool->getMasterQueue()->outputSuggestions(frequencies, outWords);
if (DEBUG_DICT) {
- LOGI("Returning %d words", suggestedWordsCount);
+ AKLOGI("Returning %d words", suggestedWordsCount);
/// Print the returned words
for (int j = 0; j < suggestedWordsCount; ++j) {
#ifdef FLAG_DBG
short unsigned int* w = outWords + j * MAX_WORD_LENGTH;
char s[MAX_WORD_LENGTH];
for (int i = 0; i <= MAX_WORD_LENGTH; i++) s[i] = w[i];
- LOGI("%s %i", s, frequencies[j]);
+ AKLOGI("%s %i", s, frequencies[j]);
#endif
}
}
@@ -186,7 +186,7 @@ void UnigramDictionary::getWordSuggestions(ProximityInfo *proximityInfo,
PROF_OPEN;
PROF_START(0);
- // Note: This line is intentionally left blank
+ queuePool->clearAll();
PROF_END(0);
PROF_START(1);
@@ -213,7 +213,7 @@ void UnigramDictionary::getWordSuggestions(ProximityInfo *proximityInfo,
&& inputLength >= MIN_USER_TYPED_LENGTH_FOR_MISSING_SPACE_SUGGESTION) {
for (int i = 1; i < inputLength; ++i) {
if (DEBUG_DICT) {
- LOGI("--- Suggest missing space characters %d", i);
+ AKLOGI("--- Suggest missing space characters %d", i);
}
getMissingSpaceWords(proximityInfo, xcoordinates, ycoordinates, codes,
useFullEditDistance, inputLength, i, correction, queuePool);
@@ -226,12 +226,12 @@ void UnigramDictionary::getWordSuggestions(ProximityInfo *proximityInfo,
// The first and last "mistyped spaces" are taken care of by excessive character handling
for (int i = 1; i < inputLength - 1; ++i) {
if (DEBUG_DICT) {
- LOGI("--- Suggest words with proximity space %d", i);
+ AKLOGI("--- Suggest words with proximity space %d", i);
}
const int x = xcoordinates[i];
const int y = ycoordinates[i];
if (DEBUG_PROXIMITY_INFO) {
- LOGI("Input[%d] x = %d, y = %d, has space proximity = %d",
+ AKLOGI("Input[%d] x = %d, y = %d, has space proximity = %d",
i, x, y, proximityInfo->hasSpaceProximity(x, y));
}
if (proximityInfo->hasSpaceProximity(x, y)) {
@@ -241,18 +241,33 @@ void UnigramDictionary::getWordSuggestions(ProximityInfo *proximityInfo,
}
}
PROF_END(6);
+ if (DEBUG_DICT) {
+ queuePool->dumpSubQueue1TopSuggestions();
+ for (int i = 0; i < SUB_QUEUE_MAX_COUNT; ++i) {
+ WordsPriorityQueue* queue = queuePool->getSubQueue1(i);
+ if (queue->size() > 0) {
+ WordsPriorityQueue::SuggestedWord* sw = queue->top();
+ const int score = sw->mScore;
+ const unsigned short* word = sw->mWord;
+ const int wordLength = sw->mWordLength;
+ double ns = Correction::RankingAlgorithm::calcNormalizedScore(
+ proximityInfo->getPrimaryInputWord(), i, word, wordLength, score);
+ ns += 0;
+ AKLOGI("--- TOP SUB WORDS for %d --- %d %f [%d]", i, score, ns,
+ (ns > TWO_WORDS_CORRECTION_THRESHOLD));
+ DUMP_WORD(proximityInfo->getPrimaryInputWord(), i);
+ DUMP_WORD(word, wordLength);
+ }
+ }
+ }
}
void UnigramDictionary::initSuggestions(ProximityInfo *proximityInfo, const int *xCoordinates,
- const int *yCoordinates, const int *codes, const int inputLength,
- WordsPriorityQueue *queue, Correction *correction) {
+ const int *yCoordinates, const int *codes, const int inputLength, Correction *correction) {
if (DEBUG_DICT) {
- LOGI("initSuggest");
+ AKLOGI("initSuggest");
}
proximityInfo->setInputParams(codes, inputLength, xCoordinates, yCoordinates);
- if (queue) {
- queue->clear();
- }
const int maxDepth = min(inputLength * MAX_DEPTH_MULTIPLIER, MAX_WORD_LENGTH);
correction->initCorrection(proximityInfo, inputLength, maxDepth);
}
@@ -264,15 +279,13 @@ void UnigramDictionary::getOneWordSuggestions(ProximityInfo *proximityInfo,
const int *xcoordinates, const int *ycoordinates, const int *codes,
const bool useFullEditDistance, const int inputLength, Correction *correction,
WordsPriorityQueuePool *queuePool) {
- WordsPriorityQueue *masterQueue = queuePool->getMasterQueue();
- initSuggestions(
- proximityInfo, xcoordinates, ycoordinates, codes, inputLength, masterQueue, correction);
- getSuggestionCandidates(useFullEditDistance, inputLength, correction, masterQueue,
+ initSuggestions(proximityInfo, xcoordinates, ycoordinates, codes, inputLength, correction);
+ getSuggestionCandidates(useFullEditDistance, inputLength, correction, queuePool,
true /* doAutoCompletion */, DEFAULT_MAX_ERRORS);
}
void UnigramDictionary::getSuggestionCandidates(const bool useFullEditDistance,
- const int inputLength, Correction *correction, WordsPriorityQueue *queue,
+ const int inputLength, Correction *correction, WordsPriorityQueuePool *queuePool,
const bool doAutoCompletion, const int maxErrors) {
// TODO: Remove setCorrectionParams
correction->setCorrectionParams(0, 0, 0,
@@ -280,7 +293,7 @@ void UnigramDictionary::getSuggestionCandidates(const bool useFullEditDistance,
doAutoCompletion, maxErrors);
int rootPosition = ROOT_POS;
// Get the number of children of root, then increment the position
- int childCount = Dictionary::getCount(DICT_ROOT, &rootPosition);
+ int childCount = BinaryFormat::getGroupCountAndForwardPointer(DICT_ROOT, &rootPosition);
int outputIndex = 0;
correction->initCorrectionState(rootPosition, childCount, (inputLength <= 0));
@@ -292,7 +305,7 @@ void UnigramDictionary::getSuggestionCandidates(const bool useFullEditDistance,
int firstChildPos;
const bool needsToTraverseChildrenNodes = processCurrentNode(siblingPos,
- correction, &childCount, &firstChildPos, &siblingPos, queue);
+ correction, &childCount, &firstChildPos, &siblingPos, queuePool);
// Update next sibling pos
correction->setTreeSiblingPos(outputIndex, siblingPos);
@@ -327,14 +340,34 @@ void UnigramDictionary::getMistypedSpaceWords(ProximityInfo *proximityInfo, cons
inline void UnigramDictionary::onTerminal(const int freq,
const TerminalAttributes& terminalAttributes, Correction *correction,
- WordsPriorityQueue *queue) {
+ WordsPriorityQueuePool *queuePool, const bool addToMasterQueue) {
+ const int inputIndex = correction->getInputIndex();
+ const bool addToSubQueue = inputIndex < SUB_QUEUE_MAX_COUNT;
+ if (!addToMasterQueue && !addToSubQueue) {
+ return;
+ }
+ WordsPriorityQueue *masterQueue = queuePool->getMasterQueue();
+ WordsPriorityQueue *subQueue = queuePool->getSubQueue1(inputIndex);
int wordLength;
unsigned short* wordPointer;
const int finalFreq = correction->getFinalFreq(freq, &wordPointer, &wordLength);
- if (finalFreq >= 0) {
+ if (finalFreq != NOT_A_FREQUENCY) {
if (!terminalAttributes.isShortcutOnly()) {
- addWord(wordPointer, wordLength, finalFreq, queue);
+ if (addToMasterQueue) {
+ addWord(wordPointer, wordLength, finalFreq, masterQueue);
+ }
+ // TODO: Check the validity of "inputIndex == wordLength"
+ //if (addToSubQueue && inputIndex == wordLength) {
+ if (addToSubQueue) {
+ addWord(wordPointer, wordLength, finalFreq, subQueue);
+ }
+ }
+ // Please note that the shortcut candidates will be added to the master queue only.
+ if (!addToMasterQueue) {
+ return;
}
+
+ // From here, below is the code to add shortcut candidates.
TerminalAttributes::ShortcutIterator iterator = terminalAttributes.getShortcutIterator();
while (iterator.hasNextShortcutTarget()) {
// TODO: addWord only supports weak ordering, meaning we have no means to control the
@@ -345,7 +378,7 @@ inline void UnigramDictionary::onTerminal(const int freq,
uint16_t shortcutTarget[MAX_WORD_LENGTH_INTERNAL];
const int shortcutTargetStringLength = iterator.getNextShortcutTarget(
MAX_WORD_LENGTH_INTERNAL, shortcutTarget);
- addWord(shortcutTarget, shortcutTargetStringLength, finalFreq, queue);
+ addWord(shortcutTarget, shortcutTargetStringLength, finalFreq, masterQueue);
}
}
}
@@ -390,7 +423,81 @@ void UnigramDictionary::getSplitTwoWordsSuggestions(ProximityInfo *proximityInfo
const int firstFreq = getMostFrequentWordLike(
firstWordStartPos, firstWordLength, proximityInfo, mWord);
if (DEBUG_DICT) {
- LOGI("First freq: %d", firstFreq);
+ AKLOGI("First freq: %d", firstFreq);
+ }
+ if (firstFreq <= 0) return;
+
+ for (int i = 0; i < firstWordLength; ++i) {
+ word[i] = mWord[i];
+ }
+
+ const int secondFreq = getMostFrequentWordLike(
+ secondWordStartPos, secondWordLength, proximityInfo, mWord);
+ if (DEBUG_DICT) {
+ AKLOGI("Second freq: %d", secondFreq);
+ }
+ if (secondFreq <= 0) return;
+
+ word[firstWordLength] = SPACE;
+ for (int i = (firstWordLength + 1); i < newWordLength; ++i) {
+ word[i] = mWord[i - firstWordLength - 1];
+ }
+
+ // TODO: Remove initSuggestions and correction->setCorrectionParams
+ initSuggestions(proximityInfo, xcoordinates, ycoordinates, codes, inputLength, correction);
+
+ correction->setCorrectionParams(-1 /* skipPos */, -1 /* excessivePos */,
+ -1 /* transposedPos */, spaceProximityPos, missingSpacePos,
+ useFullEditDistance, false /* doAutoCompletion */, MAX_ERRORS_FOR_TWO_WORDS);
+ const int pairFreq = correction->getFreqForSplitTwoWords(firstFreq, secondFreq, word);
+ if (DEBUG_DICT) {
+ AKLOGI("Split two words: %d, %d, %d, %d", firstFreq, secondFreq, pairFreq, inputLength);
+ }
+ addWord(word, newWordLength, pairFreq, masterQueue);
+ return;
+}
+
+void UnigramDictionary::getSplitTwoWordsSuggestionsOld(ProximityInfo *proximityInfo,
+ const int *xcoordinates, const int *ycoordinates, const int *codes,
+ const bool useFullEditDistance, const int inputLength, const int missingSpacePos,
+ const int spaceProximityPos, Correction *correction, WordsPriorityQueuePool* queuePool) {
+ WordsPriorityQueue *masterQueue = queuePool->getMasterQueue();
+
+ if (DEBUG_DICT) {
+ int inputCount = 0;
+ if (spaceProximityPos >= 0) ++inputCount;
+ if (missingSpacePos >= 0) ++inputCount;
+ assert(inputCount <= 1);
+ }
+ const bool isSpaceProximity = spaceProximityPos >= 0;
+ const int firstWordStartPos = 0;
+ const int secondWordStartPos = isSpaceProximity ? (spaceProximityPos + 1) : missingSpacePos;
+ const int firstWordLength = isSpaceProximity ? spaceProximityPos : missingSpacePos;
+ const int secondWordLength = isSpaceProximity
+ ? (inputLength - spaceProximityPos - 1)
+ : (inputLength - missingSpacePos);
+
+ if (inputLength >= MAX_WORD_LENGTH) return;
+ if (0 >= firstWordLength || 0 >= secondWordLength || firstWordStartPos >= secondWordStartPos
+ || firstWordStartPos < 0 || secondWordStartPos + secondWordLength > inputLength)
+ return;
+
+ const int newWordLength = firstWordLength + secondWordLength + 1;
+
+
+ // Space proximity preparation
+ //WordsPriorityQueue *subQueue = queuePool->getSubQueue1();
+ //initSuggestions(proximityInfo, xcoordinates, ycoordinates, codes, firstWordLength, subQueue,
+ //correction);
+ //getSuggestionCandidates(useFullEditDistance, firstWordLength, correction, subQueue, false,
+ //MAX_ERRORS_FOR_TWO_WORDS);
+
+ // Allocating variable length array on stack
+ unsigned short word[newWordLength];
+ const int firstFreq = getMostFrequentWordLike(
+ firstWordStartPos, firstWordLength, proximityInfo, mWord);
+ if (DEBUG_DICT) {
+ AKLOGI("First freq: %d", firstFreq);
}
if (firstFreq <= 0) return;
@@ -401,7 +508,7 @@ void UnigramDictionary::getSplitTwoWordsSuggestions(ProximityInfo *proximityInfo
const int secondFreq = getMostFrequentWordLike(
secondWordStartPos, secondWordLength, proximityInfo, mWord);
if (DEBUG_DICT) {
- LOGI("Second freq: %d", secondFreq);
+ AKLOGI("Second freq: %d", secondFreq);
}
if (secondFreq <= 0) return;
@@ -411,15 +518,14 @@ void UnigramDictionary::getSplitTwoWordsSuggestions(ProximityInfo *proximityInfo
}
// TODO: Remove initSuggestions and correction->setCorrectionParams
- initSuggestions(proximityInfo, xcoordinates, ycoordinates, codes, inputLength,
- 0 /* do not clear queue */, correction);
+ initSuggestions(proximityInfo, xcoordinates, ycoordinates, codes, inputLength, correction);
correction->setCorrectionParams(-1 /* skipPos */, -1 /* excessivePos */,
-1 /* transposedPos */, spaceProximityPos, missingSpacePos,
useFullEditDistance, false /* doAutoCompletion */, MAX_ERRORS_FOR_TWO_WORDS);
const int pairFreq = correction->getFreqForSplitTwoWords(firstFreq, secondFreq, word);
if (DEBUG_DICT) {
- LOGI("Split two words: %d, %d, %d, %d", firstFreq, secondFreq, pairFreq, inputLength);
+ AKLOGI("Split two words: %d, %d, %d, %d", firstFreq, secondFreq, pairFreq, inputLength);
}
addWord(word, newWordLength, pairFreq, masterQueue);
return;
@@ -507,9 +613,10 @@ int UnigramDictionary::getMostFrequentWordLikeInner(const uint16_t * const inWor
int maxFreq = -1;
const uint8_t* const root = DICT_ROOT;
- mStackChildCount[0] = root[0];
+ int startPos = 0;
+ mStackChildCount[0] = BinaryFormat::getGroupCountAndForwardPointer(root, &startPos);
mStackInputIndex[0] = 0;
- mStackSiblingPos[0] = 1;
+ mStackSiblingPos[0] = startPos;
while (depth >= 0) {
const int charGroupCount = mStackChildCount[depth];
int pos = mStackSiblingPos[depth];
@@ -583,7 +690,7 @@ int UnigramDictionary::getBigramPosition(int pos, unsigned short *word, int offs
// given level, as output into newCount when traversing this level's parent.
inline bool UnigramDictionary::processCurrentNode(const int initialPos,
Correction *correction, int *newCount,
- int *newChildrenPosition, int *nextSiblingPosition, WordsPriorityQueue *queue) {
+ int *newChildrenPosition, int *nextSiblingPosition, WordsPriorityQueuePool *queuePool) {
if (DEBUG_DICT) {
correction->checkState();
}
@@ -658,14 +765,13 @@ inline bool UnigramDictionary::processCurrentNode(const int initialPos,
} while (NOT_A_CHARACTER != c);
if (isTerminalNode) {
- if (needsToInvokeOnTerminal) {
- // The frequency should be here, because we come here only if this is actually
- // a terminal node, and we are on its last char.
- const int freq = BinaryFormat::readFrequencyWithoutMovingPointer(DICT_ROOT, pos);
- TerminalAttributes terminalAttributes(DICT_ROOT, flags,
- BinaryFormat::skipFrequency(flags, pos));
- onTerminal(freq, terminalAttributes, correction, queue);
- }
+ // The frequency should be here, because we come here only if this is actually
+ // a terminal node, and we are on its last char.
+ const int freq = BinaryFormat::readFrequencyWithoutMovingPointer(DICT_ROOT, pos);
+ const int childrenAddressPos = BinaryFormat::skipFrequency(flags, pos);
+ const int attributesPos = BinaryFormat::skipChildrenPosition(flags, childrenAddressPos);
+ TerminalAttributes terminalAttributes(DICT_ROOT, flags, attributesPos);
+ onTerminal(freq, terminalAttributes, correction, queuePool, needsToInvokeOnTerminal);
// If there are more chars in this node, then this virtual node has children.
// If we are on the last char, this virtual node has children if this node has.
@@ -690,7 +796,7 @@ inline bool UnigramDictionary::processCurrentNode(const int initialPos,
*nextSiblingPosition =
BinaryFormat::skipChildrenPosAndAttributes(DICT_ROOT, flags, pos);
if (DEBUG_DICT_FULL) {
- LOGI("Traversing was pruned.");
+ AKLOGI("Traversing was pruned.");
}
return false;
}
diff --git a/native/src/unigram_dictionary.h b/native/src/unigram_dictionary.h
index 23581425a..b950971bb 100644
--- a/native/src/unigram_dictionary.h
+++ b/native/src/unigram_dictionary.h
@@ -93,18 +93,21 @@ class UnigramDictionary {
const int codesRemain, const int currentDepth, int* codesDest, Correction *correction,
WordsPriorityQueuePool* queuePool);
void initSuggestions(ProximityInfo *proximityInfo, const int *xcoordinates,
- const int *ycoordinates, const int *codes, const int codesSize,
- WordsPriorityQueue *queue, Correction *correction);
+ const int *ycoordinates, const int *codes, const int codesSize, Correction *correction);
void getOneWordSuggestions(ProximityInfo *proximityInfo, const int *xcoordinates,
const int *ycoordinates, const int *codes, const bool useFullEditDistance,
const int inputLength, Correction *correction, WordsPriorityQueuePool* queuePool);
void getSuggestionCandidates(
const bool useFullEditDistance, const int inputLength, Correction *correction,
- WordsPriorityQueue* queue, const bool doAutoCompletion, const int maxErrors);
+ WordsPriorityQueuePool* queuePool, const bool doAutoCompletion, const int maxErrors);
void getSplitTwoWordsSuggestions(ProximityInfo *proximityInfo,
const int *xcoordinates, const int *ycoordinates, const int *codes,
const bool useFullEditDistance, const int inputLength, const int spaceProximityPos,
const int missingSpacePos, Correction *correction, WordsPriorityQueuePool* queuePool);
+ void getSplitTwoWordsSuggestionsOld(ProximityInfo *proximityInfo,
+ const int *xcoordinates, const int *ycoordinates, const int *codes,
+ const bool useFullEditDistance, const int inputLength, const int spaceProximityPos,
+ const int missingSpacePos, Correction *correction, WordsPriorityQueuePool* queuePool);
void getMissingSpaceWords(ProximityInfo *proximityInfo, const int *xcoordinates,
const int *ycoordinates, const int *codes, const bool useFullEditDistance,
const int inputLength, const int missingSpacePos, Correction *correction,
@@ -114,12 +117,12 @@ class UnigramDictionary {
const int inputLength, const int spaceProximityPos, Correction *correction,
WordsPriorityQueuePool* queuePool);
void onTerminal(const int freq, const TerminalAttributes& terminalAttributes,
- Correction *correction, WordsPriorityQueue *queue);
+ Correction *correction, WordsPriorityQueuePool *queuePool, const bool addToMasterQueue);
bool needsToSkipCurrentNode(const unsigned short c,
const int inputIndex, const int skipPos, const int depth);
// Process a node by considering proximity, missing and excessive character
bool processCurrentNode(const int initialPos, Correction *correction, int *newCount,
- int *newChildPosition, int *nextSiblingPosition, WordsPriorityQueue *queue);
+ int *newChildPosition, int *nextSiblingPosition, WordsPriorityQueuePool *queuePool);
int getMostFrequentWordLike(const int startInputIndex, const int inputLength,
ProximityInfo *proximityInfo, unsigned short *word);
int getMostFrequentWordLikeInner(const uint16_t* const inWord, const int length,
diff --git a/native/src/words_priority_queue.h b/native/src/words_priority_queue.h
index 84f2523c2..6262439b5 100644
--- a/native/src/words_priority_queue.h
+++ b/native/src/words_priority_queue.h
@@ -48,6 +48,7 @@ class WordsPriorityQueue {
mSuggestedWords[i].mUsed = false;
}
}
+
~WordsPriorityQueue() {
delete[] mSuggestedWords;
}
@@ -70,20 +71,19 @@ class WordsPriorityQueue {
sw->setParams(score, word, wordLength);
}
if (sw == 0) {
- LOGE("SuggestedWord is accidentally null.");
+ AKLOGE("SuggestedWord is accidentally null.");
return;
}
if (DEBUG_WORDS_PRIORITY_QUEUE) {
- LOGI("Push word. %d, %d", score, wordLength);
+ AKLOGI("Push word. %d, %d", score, wordLength);
DUMP_WORD(word, wordLength);
}
mSuggestions.push(sw);
}
- SuggestedWord* topAndPop() {
+ SuggestedWord* top() {
if (mSuggestions.empty()) return 0;
SuggestedWord* sw = mSuggestions.top();
- mSuggestions.pop();
return sw;
}
@@ -93,7 +93,7 @@ class WordsPriorityQueue {
while (!mSuggestions.empty() && index >= 0) {
SuggestedWord* sw = mSuggestions.top();
if (DEBUG_WORDS_PRIORITY_QUEUE) {
- LOGI("dump word. %d", sw->mScore);
+ AKLOGI("dump word. %d", sw->mScore);
DUMP_WORD(sw->mWord, sw->mWordLength);
}
const unsigned int wordLength = sw->mWordLength;
@@ -111,7 +111,7 @@ class WordsPriorityQueue {
return size;
}
- int size() {
+ int size() const {
return mSuggestions.size();
}
@@ -119,7 +119,7 @@ class WordsPriorityQueue {
while (!mSuggestions.empty()) {
SuggestedWord* sw = mSuggestions.top();
if (DEBUG_WORDS_PRIORITY_QUEUE) {
- LOGI("Clear word. %d", sw->mScore);
+ AKLOGI("Clear word. %d", sw->mScore);
DUMP_WORD(sw->mWord, sw->mWordLength);
}
sw->mUsed = false;
@@ -127,6 +127,13 @@ class WordsPriorityQueue {
}
}
+ void dumpTopWord() {
+ if (size() <= 0) {
+ return;
+ }
+ DUMP_WORD(mSuggestions.top()->mWord, mSuggestions.top()->mWordLength);
+ }
+
private:
struct wordComparator {
bool operator ()(SuggestedWord * left, SuggestedWord * right) {
diff --git a/native/src/words_priority_queue_pool.h b/native/src/words_priority_queue_pool.h
index 386297650..5fa254852 100644
--- a/native/src/words_priority_queue_pool.h
+++ b/native/src/words_priority_queue_pool.h
@@ -17,6 +17,8 @@
#ifndef LATINIME_WORDS_PRIORITY_QUEUE_POOL_H
#define LATINIME_WORDS_PRIORITY_QUEUE_POOL_H
+#include <assert.h>
+#include <new>
#include "words_priority_queue.h"
namespace latinime {
@@ -24,30 +26,60 @@ namespace latinime {
class WordsPriorityQueuePool {
public:
WordsPriorityQueuePool(int mainQueueMaxWords, int subQueueMaxWords, int maxWordLength) {
- mMasterQueue = new WordsPriorityQueue(mainQueueMaxWords, maxWordLength);
- mSubQueue1 = new WordsPriorityQueue(subQueueMaxWords, maxWordLength);
- mSubQueue2 = new WordsPriorityQueue(subQueueMaxWords, maxWordLength);
+ mMasterQueue = new(mMasterQueueBuf) WordsPriorityQueue(mainQueueMaxWords, maxWordLength);
+ for (int i = 0, subQueueBufOffset = 0; i < SUB_QUEUE_MAX_COUNT;
+ ++i, subQueueBufOffset += sizeof(WordsPriorityQueue)) {
+ mSubQueues1[i] = new(mSubQueueBuf1 + subQueueBufOffset)
+ WordsPriorityQueue(subQueueMaxWords, maxWordLength);
+ mSubQueues2[i] = new(mSubQueueBuf2 + subQueueBufOffset)
+ WordsPriorityQueue(subQueueMaxWords, maxWordLength);
+ }
}
- ~WordsPriorityQueuePool() {
- delete mMasterQueue;
+ virtual ~WordsPriorityQueuePool() {
}
WordsPriorityQueue* getMasterQueue() {
return mMasterQueue;
}
+
// TODO: Come up with more generic pool
- WordsPriorityQueue* getSubQueue1() {
- return mSubQueue1;
+ WordsPriorityQueue* getSubQueue1(const int id) {
+ if (DEBUG_WORDS_PRIORITY_QUEUE) {
+ assert(id >= 0 && id < SUB_QUEUE_MAX_COUNT);
+ }
+ return mSubQueues1[id];
+ }
+
+ WordsPriorityQueue* getSubQueue2(const int id) {
+ if (DEBUG_WORDS_PRIORITY_QUEUE) {
+ assert(id >= 0 && id < SUB_QUEUE_MAX_COUNT);
+ }
+ return mSubQueues2[id];
}
- WordsPriorityQueue* getSubQueue2() {
- return mSubQueue2;
+
+ inline void clearAll() {
+ mMasterQueue->clear();
+ for (int i = 0; i < SUB_QUEUE_MAX_COUNT; ++i) {
+ mSubQueues1[i]->clear();
+ mSubQueues2[i]->clear();
+ }
+ }
+
+ void dumpSubQueue1TopSuggestions() {
+ AKLOGI("DUMP SUBQUEUE1 TOP SUGGESTIONS");
+ for (int i = 0; i < SUB_QUEUE_MAX_COUNT; ++i) {
+ mSubQueues1[i]->dumpTopWord();
+ }
}
private:
- WordsPriorityQueue *mMasterQueue;
- WordsPriorityQueue *mSubQueue1;
- WordsPriorityQueue *mSubQueue2;
+ WordsPriorityQueue* mMasterQueue;
+ WordsPriorityQueue* mSubQueues1[SUB_QUEUE_MAX_COUNT];
+ WordsPriorityQueue* mSubQueues2[SUB_QUEUE_MAX_COUNT];
+ char mMasterQueueBuf[sizeof(WordsPriorityQueue)];
+ char mSubQueueBuf1[SUB_QUEUE_MAX_COUNT * sizeof(WordsPriorityQueue)];
+ char mSubQueueBuf2[SUB_QUEUE_MAX_COUNT * sizeof(WordsPriorityQueue)];
};
}
diff --git a/tests/AndroidManifest.xml b/tests/AndroidManifest.xml
index 210e81489..38a2ecfeb 100644
--- a/tests/AndroidManifest.xml
+++ b/tests/AndroidManifest.xml
@@ -22,8 +22,6 @@
<application>
<uses-library android:name="android.test.runner" />
<!-- meta-data android:name="com.android.contacts.iconset" android:resource="@xml/iconset" /-->
- <uses-permission android:name="android.permission.READ_CONTACTS" />
-
</application>
<instrumentation android:name="android.test.InstrumentationTestRunner"
diff --git a/tests/data/bigramlist.xml b/tests/data/bigramlist.xml
index dd3f2916e..d3d8bb801 100644
--- a/tests/data/bigramlist.xml
+++ b/tests/data/bigramlist.xml
@@ -25,7 +25,7 @@
<bi w1="about" count="3">
<w w2="part" p="117" />
<w w2="business" p="100" />
- <w w2="being" p="10" />
+ <w w2="being" p="90" />
</bi>
<bi w1="business" count="1">
<w w2="people" p="100" />
diff --git a/tests/res/raw/test.dict b/tests/res/raw/test.dict
index 6a5d6d794..453fc9fce 100644
--- a/tests/res/raw/test.dict
+++ b/tests/res/raw/test.dict
Binary files differ
diff --git a/tests/src/com/android/inputmethod/keyboard/internal/KeyboardStateNonDistinctTests.java b/tests/src/com/android/inputmethod/keyboard/internal/KeyboardStateNonDistinctTests.java
new file mode 100644
index 000000000..e3f0e0718
--- /dev/null
+++ b/tests/src/com/android/inputmethod/keyboard/internal/KeyboardStateNonDistinctTests.java
@@ -0,0 +1,430 @@
+/*
+ * Copyright (C) 2012 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+
+package com.android.inputmethod.keyboard.internal;
+
+import android.test.AndroidTestCase;
+
+public class KeyboardStateNonDistinctTests extends AndroidTestCase
+ implements MockKeyboardSwitcher.Constants {
+ protected MockKeyboardSwitcher mSwitcher;
+
+ public boolean hasDistinctMultitouch() {
+ return false;
+ }
+
+ @Override
+ protected void setUp() throws Exception {
+ super.setUp();
+
+ mSwitcher = new MockKeyboardSwitcher();
+
+ final String layoutSwitchBackSymbols = "";
+ mSwitcher.loadKeyboard(layoutSwitchBackSymbols, hasDistinctMultitouch());
+ }
+
+ public void assertAlphabetNormal() {
+ assertTrue(mSwitcher.assertAlphabetNormal());
+ }
+
+ public void assertAlphabetManualShifted() {
+ assertTrue(mSwitcher.assertAlphabetManualShifted());
+ }
+
+ public void assertAlphabetAutomaticShifted() {
+ assertTrue(mSwitcher.assertAlphabetAutomaticShifted());
+ }
+
+ public void assertAlphabetShiftLocked() {
+ assertTrue(mSwitcher.assertAlphabetShiftLocked());
+ }
+
+ public void assertSymbolsNormal() {
+ assertTrue(mSwitcher.assertSymbolsNormal());
+ }
+
+ public void assertSymbolsShifted() {
+ assertTrue(mSwitcher.assertSymbolsShifted());
+ }
+
+ // Initial state test.
+ public void testLoadKeyboard() {
+ assertAlphabetNormal();
+ }
+
+ // Shift key in alphabet mode.
+ public void testShift() {
+ // Press/release shift key, enter into shift state.
+ mSwitcher.onPressKey(CODE_SHIFT);
+ assertAlphabetManualShifted();
+ mSwitcher.onCodeInput(CODE_SHIFT);
+ mSwitcher.onReleaseKey(CODE_SHIFT);
+ assertAlphabetManualShifted();
+ // Press/release shift key, back to normal state.
+ mSwitcher.onPressKey(CODE_SHIFT);
+ assertAlphabetManualShifted();
+ mSwitcher.onCodeInput(CODE_SHIFT);
+ mSwitcher.onReleaseKey(CODE_SHIFT);
+ assertAlphabetNormal();
+
+ // Press/release shift key, enter into shift state.
+ mSwitcher.onPressKey(CODE_SHIFT);
+ assertAlphabetManualShifted();
+ mSwitcher.onCodeInput(CODE_SHIFT);
+ mSwitcher.onReleaseKey(CODE_SHIFT);
+ assertAlphabetManualShifted();
+ // Press/release letter key, snap back to normal state.
+ mSwitcher.onPressKey('Z');
+ mSwitcher.onCodeInput('Z');
+ mSwitcher.onReleaseKey('Z');
+ assertAlphabetNormal();
+ }
+
+ // Shift key sliding input.
+ public void testShiftSliding() {
+ // Press shift key.
+ mSwitcher.onPressKey(CODE_SHIFT);
+ assertAlphabetManualShifted();
+ // Slide out shift key.
+ mSwitcher.onReleaseKey(CODE_SHIFT, SLIDING);
+ assertAlphabetManualShifted();
+
+ // Enter into letter key.
+ mSwitcher.onPressKey('Z');
+ assertAlphabetManualShifted();
+ // Release letter key, snap back to alphabet.
+ mSwitcher.onCodeInput('Z');
+ mSwitcher.onReleaseKey('Z');
+ assertAlphabetNormal();
+ }
+
+ public void enterSymbolsMode() {
+ // Press/release "?123" key.
+ mSwitcher.onPressKey(CODE_SYMBOL);
+ assertSymbolsNormal();
+ mSwitcher.onCodeInput(CODE_SYMBOL);
+ mSwitcher.onReleaseKey(CODE_SYMBOL);
+ assertSymbolsNormal();
+ }
+
+ public void leaveSymbolsMode() {
+ // Press/release "ABC" key.
+ mSwitcher.onPressKey(CODE_SYMBOL);
+ assertAlphabetNormal();
+ mSwitcher.onCodeInput(CODE_SYMBOL);
+ mSwitcher.onReleaseKey(CODE_SYMBOL);
+ assertAlphabetNormal();
+ }
+
+ // Switching between alphabet and symbols.
+ public void testAlphabetAndSymbols() {
+ enterSymbolsMode();
+ leaveSymbolsMode();
+ }
+
+ // Switching between alphabet shift locked and symbols.
+ public void testAlphabetShiftLockedAndSymbols() {
+ enterShiftLockWithLongPressShift();
+ enterSymbolsMode();
+
+ // Press/release "ABC" key, switch back to shift locked mode.
+ mSwitcher.onPressKey(CODE_SYMBOL);
+ assertAlphabetShiftLocked();
+ mSwitcher.onCodeInput(CODE_SYMBOL);
+ mSwitcher.onReleaseKey(CODE_SYMBOL);
+ assertAlphabetShiftLocked();
+ }
+
+ // Symbols key sliding input.
+ public void testSymbolsSliding() {
+ // Press "123?" key.
+ mSwitcher.onPressKey(CODE_SYMBOL);
+ assertSymbolsNormal();
+ // Slide out from "123?" key.
+ mSwitcher.onReleaseKey(CODE_SYMBOL, SLIDING);
+ assertSymbolsNormal();
+
+ // Enter into letter key.
+ mSwitcher.onPressKey('z');
+ assertSymbolsNormal();
+ // Release letter key, snap back to alphabet.
+ mSwitcher.onCodeInput('z');
+ mSwitcher.onReleaseKey('z');
+ assertAlphabetNormal();
+ }
+
+ // Switching between symbols and symbols shifted.
+ public void testSymbolsAndSymbolsShifted() {
+ enterSymbolsMode();
+
+ // Press/release "=\<" key.
+ mSwitcher.onPressKey(CODE_SHIFT);
+ assertSymbolsShifted();
+ mSwitcher.onCodeInput(CODE_SHIFT);
+ mSwitcher.onReleaseKey(CODE_SHIFT);
+ assertSymbolsShifted();
+
+ // Press/release "?123" key.
+ mSwitcher.onPressKey(CODE_SHIFT);
+ assertSymbolsNormal();
+ mSwitcher.onCodeInput(CODE_SHIFT);
+ mSwitcher.onReleaseKey(CODE_SHIFT);
+ assertSymbolsNormal();
+
+ leaveSymbolsMode();
+ }
+
+ // Symbols shift sliding input
+ public void testSymbolsShiftSliding() {
+ enterSymbolsMode();
+
+ // Press "=\<" key.
+ mSwitcher.onPressKey(CODE_SHIFT);
+ assertSymbolsShifted();
+ // Slide out "=\<" key.
+ mSwitcher.onReleaseKey(CODE_SHIFT, SLIDING);
+ assertSymbolsShifted();
+
+ // Enter into symbol shifted letter key.
+ mSwitcher.onPressKey('~');
+ assertSymbolsShifted();
+ // Release symbol shifted letter key, snap back to symbols.
+ mSwitcher.onCodeInput('~');
+ mSwitcher.onReleaseKey('~');
+ assertSymbolsNormal();
+ }
+
+ // Symbols shift sliding input from symbols shifted.
+ public void testSymbolsShiftSliding2() {
+ enterSymbolsMode();
+
+ // Press/release "=\<" key.
+ mSwitcher.onPressKey(CODE_SHIFT);
+ assertSymbolsShifted();
+ mSwitcher.onCodeInput(CODE_SHIFT);
+ mSwitcher.onReleaseKey(CODE_SHIFT);
+ assertSymbolsShifted();
+
+ // Press "123?" key.
+ mSwitcher.onPressKey(CODE_SHIFT);
+ assertSymbolsNormal();
+ // Slide out "123?" key.
+ mSwitcher.onReleaseKey(CODE_SHIFT, SLIDING);
+ assertSymbolsNormal();
+
+ // Enter into symbol letter key.
+ mSwitcher.onPressKey('1');
+ assertSymbolsNormal();
+ // Release symbol letter key, snap back to symbols shift.
+ mSwitcher.onCodeInput('1');
+ mSwitcher.onReleaseKey('1');
+ assertSymbolsShifted();
+ }
+
+ // Automatic snap back to alphabet from symbols by space key.
+ public void testSnapBackBySpace() {
+ enterSymbolsMode();
+
+ // Enter a symbol letter.
+ mSwitcher.onPressKey('1');
+ assertSymbolsNormal();
+ mSwitcher.onCodeInput('1');
+ mSwitcher.onReleaseKey('1');
+ assertSymbolsNormal();
+ // Enter space, snap back to alphabet.
+ mSwitcher.onPressKey(CODE_SPACE);
+ assertSymbolsNormal();
+ mSwitcher.onCodeInput(CODE_SPACE);
+ mSwitcher.onReleaseKey(CODE_SPACE);
+ assertAlphabetNormal();
+ }
+
+ // TODO: Add automatic snap back to shift locked test.
+
+ // Automatic snap back to alphabet from symbols by registered letters.
+ public void testSnapBack() {
+ final String snapBackChars = "'";
+ final int snapBackCode = snapBackChars.codePointAt(0);
+ final boolean hasDistinctMultitouch = true;
+ mSwitcher.loadKeyboard(snapBackChars, hasDistinctMultitouch);
+
+ enterSymbolsMode();
+
+ // Enter a symbol letter.
+ mSwitcher.onPressKey('1');
+ assertSymbolsNormal();
+ mSwitcher.onCodeInput('1');
+ mSwitcher.onReleaseKey('1');
+ assertSymbolsNormal();
+ // Enter snap back letter, snap back to alphabet.
+ mSwitcher.onPressKey(snapBackCode);
+ assertSymbolsNormal();
+ mSwitcher.onCodeInput(snapBackCode);
+ mSwitcher.onReleaseKey(snapBackCode);
+ assertAlphabetNormal();
+ }
+
+ // Automatic upper case test
+ public void testAutomaticUpperCase() {
+ mSwitcher.setAutoCapsMode(AUTO_CAPS);
+ // Update shift state with auto caps enabled.
+ mSwitcher.updateShiftState();
+ assertAlphabetAutomaticShifted();
+
+ // Press shift key.
+ mSwitcher.onPressKey(CODE_SHIFT);
+ assertAlphabetManualShifted();
+ // Release shift key.
+ mSwitcher.onCodeInput(CODE_SHIFT);
+ mSwitcher.onReleaseKey(CODE_SHIFT);
+ assertAlphabetNormal();
+ }
+
+ // Sliding from shift key in automatic upper case.
+ public void testAutomaticUpperCaseSliding() {
+ mSwitcher.setAutoCapsMode(AUTO_CAPS);
+ // Update shift state with auto caps enabled.
+ mSwitcher.updateShiftState();
+ assertAlphabetAutomaticShifted();
+
+ // Press shift key.
+ mSwitcher.onPressKey(CODE_SHIFT);
+ assertAlphabetManualShifted();
+ // Slide out shift key.
+ mSwitcher.onReleaseKey(CODE_SHIFT, SLIDING);
+ assertAlphabetManualShifted();
+ // Enter into letter key.
+ mSwitcher.onPressKey('Z');
+ assertAlphabetManualShifted();
+ // Release letter key, snap back to alphabet.
+ mSwitcher.onCodeInput('Z');
+ mSwitcher.onReleaseKey('Z');
+ assertAlphabetNormal();
+ }
+
+ // Sliding from symbol key in automatic upper case.
+ public void testAutomaticUpperCaseSliding2() {
+ mSwitcher.setAutoCapsMode(AUTO_CAPS);
+ // Update shift state with auto caps enabled.
+ mSwitcher.updateShiftState();
+ assertAlphabetAutomaticShifted();
+
+ // Press "123?" key.
+ mSwitcher.onPressKey(CODE_SYMBOL);
+ assertSymbolsNormal();
+ // Slide out "123?" key.
+ mSwitcher.onReleaseKey(CODE_SYMBOL, SLIDING);
+ assertSymbolsNormal();
+ // Enter into symbol letter keys.
+ mSwitcher.onPressKey('1');
+ assertSymbolsNormal();
+ // Release symbol letter key, snap back to alphabet.
+ mSwitcher.onCodeInput('1');
+ mSwitcher.onReleaseKey('1');
+ assertAlphabetNormal();
+ }
+
+ public void enterShiftLockWithLongPressShift() {
+ // Long press shift key
+ mSwitcher.onPressKey(CODE_SHIFT);
+ assertAlphabetManualShifted();
+ // Long press recognized in LatinKeyboardView.KeyTimerHandler.
+ mSwitcher.onCodeInput(CODE_CAPSLOCK);
+ assertAlphabetShiftLocked();
+ mSwitcher.onReleaseKey(CODE_SHIFT);
+ assertAlphabetShiftLocked();
+ }
+
+ public void leaveShiftLockWithLongPressShift() {
+ // Press shift key.
+ mSwitcher.onPressKey(CODE_SHIFT);
+ assertAlphabetManualShifted();
+ // Long press recognized in LatinKeyboardView.KeyTimerHandler.
+ mSwitcher.onCodeInput(CODE_CAPSLOCK);
+ assertAlphabetNormal();
+ mSwitcher.onReleaseKey(CODE_SHIFT);
+ assertAlphabetNormal();
+ }
+
+ // Long press shift key.
+ // TODO: Move long press recognizing timer/logic into KeyboardState.
+ public void testLongPressShift() {
+ enterShiftLockWithLongPressShift();
+ leaveShiftLockWithLongPressShift();
+ }
+
+ // Leave shift lock with single tap shift key.
+ public void testShiftInShiftLock() {
+ enterShiftLockWithLongPressShift();
+ assertAlphabetShiftLocked();
+
+ // Tap shift key.
+ mSwitcher.onPressKey(CODE_SHIFT);
+ assertAlphabetManualShifted();
+ mSwitcher.onCodeInput(CODE_SHIFT, SINGLE);
+ assertAlphabetManualShifted();
+ mSwitcher.onReleaseKey(CODE_SHIFT);
+ assertAlphabetNormal();
+ }
+
+ // Double tap shift key.
+ // TODO: Move double tap recognizing timer/logic into KeyboardState.
+ public void testDoubleTapShift() {
+ // First shift key tap.
+ mSwitcher.onPressKey(CODE_SHIFT);
+ assertAlphabetManualShifted();
+ mSwitcher.onCodeInput(CODE_SHIFT);
+ assertAlphabetManualShifted();
+ mSwitcher.onReleaseKey(CODE_SHIFT);
+ assertAlphabetManualShifted();
+ // Second shift key tap.
+ // Double tap recognized in LatinKeyboardView.KeyTimerHandler.
+ mSwitcher.onCodeInput(CODE_CAPSLOCK);
+ assertAlphabetShiftLocked();
+
+ // First shift key tap.
+ mSwitcher.onPressKey(CODE_SHIFT);
+ assertAlphabetManualShifted();
+ mSwitcher.onCodeInput(CODE_SHIFT);
+ assertAlphabetManualShifted();
+ mSwitcher.onReleaseKey(CODE_SHIFT);
+ assertAlphabetNormal();
+ // Second shift key tap.
+ // Second tap is ignored in LatinKeyboardView.KeyTimerHandler.
+ }
+
+ // Update shift state.
+ public void testUpdateShiftState() {
+ mSwitcher.setAutoCapsMode(AUTO_CAPS);
+ // Update shift state.
+ mSwitcher.updateShiftState();
+ assertAlphabetAutomaticShifted();
+ }
+
+ // Update shift state when shift locked.
+ public void testUpdateShiftStateInShiftLocked() {
+ mSwitcher.setAutoCapsMode(AUTO_CAPS);
+ enterShiftLockWithLongPressShift();
+ assertAlphabetShiftLocked();
+ // Update shift state when shift locked
+ mSwitcher.updateShiftState();
+ assertAlphabetShiftLocked();
+ }
+
+ // TODO: Change focus test.
+
+ // TODO: Change orientation test.
+}
diff --git a/tests/src/com/android/inputmethod/keyboard/internal/KeyboardStateTests.java b/tests/src/com/android/inputmethod/keyboard/internal/KeyboardStateTests.java
index 1f6141e50..19339f72e 100644
--- a/tests/src/com/android/inputmethod/keyboard/internal/KeyboardStateTests.java
+++ b/tests/src/com/android/inputmethod/keyboard/internal/KeyboardStateTests.java
@@ -16,246 +16,102 @@
package com.android.inputmethod.keyboard.internal;
-import android.test.AndroidTestCase;
-
-import com.android.inputmethod.keyboard.Keyboard;
-import com.android.inputmethod.keyboard.internal.KeyboardState.SwitchActions;
-
-public class KeyboardStateTests extends AndroidTestCase {
- private static final int ALPHABET_UNSHIFTED = 0;
- private static final int ALPHABET_MANUAL_SHIFTED = 1;
- private static final int ALPHABET_AUTOMATIC_SHIFTED = 2;
- private static final int ALPHABET_SHIFT_LOCKED = 3;
- private static final int SYMBOLS_UNSHIFTED = 4;
- private static final int SYMBOLS_SHIFTED = 5;
-
- static class KeyboardSwitcher implements KeyboardState.SwitchActions {
- public int mLayout = ALPHABET_UNSHIFTED;
-
- @Override
- public void setAlphabetKeyboard() {
- mLayout = ALPHABET_UNSHIFTED;
- }
-
- @Override
- public void setShifted(int shiftMode) {
- if (shiftMode == SwitchActions.UNSHIFT) {
- mLayout = ALPHABET_UNSHIFTED;
- } else if (shiftMode == SwitchActions.MANUAL_SHIFT) {
- mLayout = ALPHABET_MANUAL_SHIFTED;
- } else if (shiftMode == SwitchActions.AUTOMATIC_SHIFT) {
- mLayout = ALPHABET_AUTOMATIC_SHIFTED;
- }
- }
-
- @Override
- public void setShiftLocked(boolean shiftLocked) {
- if (shiftLocked) {
- mLayout = ALPHABET_SHIFT_LOCKED;
- } else {
- mLayout = ALPHABET_UNSHIFTED;
- }
- }
-
- @Override
- public void setSymbolsKeyboard() {
- mLayout = SYMBOLS_UNSHIFTED;
- }
-
- @Override
- public void setSymbolsShiftedKeyboard() {
- mLayout = SYMBOLS_SHIFTED;
- }
- }
-
- private KeyboardSwitcher mSwitcher;
- private KeyboardState mState;
-
+public class KeyboardStateTests extends KeyboardStateNonDistinctTests {
@Override
- protected void setUp() throws Exception {
- super.setUp();
-
- mSwitcher = new KeyboardSwitcher();
- mState = new KeyboardState(mSwitcher);
-
- final String layoutSwitchBackCharacter = "";
- // TODO: Unit tests for non-distinct multi touch device.
- final boolean hasDistinctMultitouch = true;
- mState.onLoadKeyboard(layoutSwitchBackCharacter, hasDistinctMultitouch);
- }
-
- // Argument for KeyboardState.onPressShift and onReleaseShift.
- private static final boolean NOT_SLIDING = false;
- private static final boolean SLIDING = true;
- // Argument for KeyboardState.onCodeInput.
- private static final boolean SINGLE = true;
- private static final boolean MULTI = false;
- private static final boolean NO_AUTO_CAPS = false;
- private static final boolean AUTO_CAPS = true;
-
- private void assertAlphabetNormal() {
- assertEquals(ALPHABET_UNSHIFTED, mSwitcher.mLayout);
+ public boolean hasDistinctMultitouch() {
+ return true;
}
- private void assertAlphabetManualShifted() {
- assertEquals(ALPHABET_MANUAL_SHIFTED, mSwitcher.mLayout);
- }
-
- private void assertAlphabetAutomaticShifted() {
- assertEquals(ALPHABET_AUTOMATIC_SHIFTED, mSwitcher.mLayout);
- }
-
- private void assertAlphabetShiftLocked() {
- assertEquals(ALPHABET_SHIFT_LOCKED, mSwitcher.mLayout);
- }
-
- private void assertSymbolsNormal() {
- assertEquals(SYMBOLS_UNSHIFTED, mSwitcher.mLayout);
- }
-
- private void assertSymbolsShifted() {
- assertEquals(SYMBOLS_SHIFTED, mSwitcher.mLayout);
- }
-
- // Initial state test.
- public void testLoadKeyboard() {
- assertAlphabetNormal();
- }
+ // Shift key chording input.
+ public void testShiftChording() {
+ // Press shift key and hold, enter into choring shift state.
+ mSwitcher.onPressKey(CODE_SHIFT);
+ assertAlphabetManualShifted();
- // Shift key in alphabet mode.
- public void testShift() {
- // Press/release shift key.
- mState.onPressShift(NOT_SLIDING);
+ // Press/release letter keys.
+ mSwitcher.onPressKey('Z');
+ mSwitcher.onCodeInput('Z', MULTI);
+ mSwitcher.onReleaseKey('Z');
assertAlphabetManualShifted();
- mState.onReleaseShift(NOT_SLIDING);
+ mSwitcher.onPressKey('X');
+ mSwitcher.onCodeInput('X', MULTI);
+ mSwitcher.onReleaseKey('X');
assertAlphabetManualShifted();
- // Press/release shift key.
- mState.onPressShift(NOT_SLIDING);
- assertAlphabetManualShifted();
- mState.onReleaseShift(NOT_SLIDING);
+ // Release shift key, snap back to normal state.
+ mSwitcher.onCodeInput(CODE_SHIFT);
+ mSwitcher.onReleaseKey(CODE_SHIFT);
+ mSwitcher.updateShiftState();
assertAlphabetNormal();
-
- // TODO: Sliding test
}
- // Switching between alphabet and symbols.
- public void testAlphabetAndSymbols() {
- // Press/release "?123" key.
- mState.onPressSymbol();
- assertSymbolsNormal();
- mState.onReleaseSymbol();
+ // Symbols key chording input.
+ public void testSymbolsChording() {
+ // Press symbols key and hold, enter into choring shift state.
+ mSwitcher.onPressKey(CODE_SYMBOL);
assertSymbolsNormal();
- // Press/release "ABC" key.
- mState.onPressSymbol();
- assertAlphabetNormal();
- mState.onReleaseSymbol();
- assertAlphabetNormal();
-
- // TODO: Sliding test
- // TODO: Snap back test
- }
-
- // Switching between symbols and symbols shifted.
- public void testSymbolsAndSymbolsShifted() {
- // Press/release "?123" key.
- mState.onPressSymbol();
+ // Press/release symbol letter keys.
+ mSwitcher.onPressKey('1');
+ mSwitcher.onCodeInput('1', MULTI);
+ mSwitcher.onReleaseKey('1');
assertSymbolsNormal();
- mState.onReleaseSymbol();
+ mSwitcher.onPressKey('2');
+ mSwitcher.onCodeInput('2', MULTI);
+ mSwitcher.onReleaseKey('2');
assertSymbolsNormal();
- // Press/release "=\<" key.
- mState.onPressShift(NOT_SLIDING);
- assertSymbolsShifted();
- mState.onReleaseShift(NOT_SLIDING);
- assertSymbolsShifted();
-
- // Press/release "ABC" key.
- mState.onPressSymbol();
+ // Release shift key, snap back to normal state.
+ mSwitcher.onCodeInput(CODE_SYMBOL);
+ mSwitcher.onReleaseKey(CODE_SYMBOL);
+ mSwitcher.updateShiftState();
assertAlphabetNormal();
- mState.onReleaseSymbol();
- assertAlphabetNormal();
-
- // TODO: Sliding test
- // TODO: Snap back test
}
- // Automatic upper case test
- public void testAutomaticUpperCase() {
+ // Chording shift key in automatic upper case.
+ public void testAutomaticUpperCaseChording() {
+ mSwitcher.setAutoCapsMode(AUTO_CAPS);
// Update shift state with auto caps enabled.
- mState.onUpdateShiftState(true);
+ mSwitcher.updateShiftState();
assertAlphabetAutomaticShifted();
// Press shift key.
- mState.onPressShift(NOT_SLIDING);
+ mSwitcher.onPressKey(CODE_SHIFT);
assertAlphabetManualShifted();
- // Release shift key.
- mState.onReleaseShift(NOT_SLIDING);
+ // Press/release letter keys.
+ mSwitcher.onPressKey('Z');
+ mSwitcher.onCodeInput('Z', MULTI);
+ mSwitcher.onReleaseKey('Z');
+ assertAlphabetManualShifted();
+ // Release shift key, snap back to alphabet.
+ mSwitcher.onCodeInput(CODE_SHIFT);
+ mSwitcher.onReleaseKey(CODE_SHIFT);
assertAlphabetNormal();
-
- // TODO: Chording test.
}
- // TODO: UpdateShiftState with shift locked, etc.
-
- // TODO: Multitouch test
-
- // TODO: Change focus test.
-
- // TODO: Change orientation test.
-
- // Long press shift key.
- // TODO: Move long press recognizing timer/logic into KeyboardState.
- public void testLongPressShift() {
- // Long press shift key
- mState.onPressShift(NOT_SLIDING);
- assertAlphabetManualShifted();
- // Long press recognized in LatinKeyboardView.KeyTimerHandler.
- mState.onToggleCapsLock();
- assertAlphabetShiftLocked();
- mState.onCodeInput(Keyboard.CODE_CAPSLOCK, SINGLE, NO_AUTO_CAPS);
- assertAlphabetShiftLocked();
- mState.onReleaseShift(NOT_SLIDING);
- assertAlphabetShiftLocked();
+ // Chording symbol key in automatic upper case.
+ public void testAutomaticUpperCaseChrding2() {
+ mSwitcher.setAutoCapsMode(AUTO_CAPS);
+ // Update shift state with auto caps enabled.
+ mSwitcher.updateShiftState();
+ assertAlphabetAutomaticShifted();
- // Long press shift key.
- mState.onPressShift(NOT_SLIDING);
- assertAlphabetManualShifted();
- // Long press recognized in LatinKeyboardView.KeyTimerHandler.
- mState.onToggleCapsLock();
- assertAlphabetNormal();
- mState.onCodeInput(Keyboard.CODE_CAPSLOCK, SINGLE, NO_AUTO_CAPS);
- assertAlphabetNormal();
- mState.onReleaseShift(NOT_SLIDING);
+ // Press "123?" key.
+ mSwitcher.onPressKey(CODE_SYMBOL);
+ assertSymbolsNormal();
+ // Press/release symbol letter keys.
+ mSwitcher.onPressKey('1');
+ assertSymbolsNormal();
+ mSwitcher.onCodeInput('1', MULTI);
+ mSwitcher.onReleaseKey('1');
+ assertSymbolsNormal();
+ // Release "123?" key, snap back to alphabet.
+ mSwitcher.onCodeInput(CODE_SYMBOL);
+ mSwitcher.onReleaseKey(CODE_SYMBOL);
assertAlphabetNormal();
}
- // Double tap shift key.
- // TODO: Move double tap recognizing timer/logic into KeyboardState.
- public void testDoubleTapShift() {
- // First shift key tap.
- mState.onPressShift(NOT_SLIDING);
- assertAlphabetManualShifted();
- mState.onCodeInput(Keyboard.CODE_SHIFT, SINGLE, NO_AUTO_CAPS);
- assertAlphabetManualShifted();
- mState.onReleaseShift(NOT_SLIDING);
- assertAlphabetManualShifted();
- // Second shift key tap.
- // Double tap recognized in LatinKeyboardView.KeyTimerHandler.
- mState.onToggleCapsLock();
- assertAlphabetShiftLocked();
- mState.onCodeInput(Keyboard.CODE_SHIFT, SINGLE, NO_AUTO_CAPS);
- assertAlphabetShiftLocked();
+ // TODO: Multitouch test
- // First shift key tap.
- mState.onPressShift(NOT_SLIDING);
- assertAlphabetManualShifted();
- mState.onCodeInput(Keyboard.CODE_SHIFT, SINGLE, NO_AUTO_CAPS);
- assertAlphabetManualShifted();
- mState.onReleaseShift(NOT_SLIDING);
- assertAlphabetNormal();
- // Second shift key tap.
- // Second tap is ignored in LatinKeyboardView.KeyTimerHandler.
- }
+ // TODO: n-Keys roll over test
}
diff --git a/tests/src/com/android/inputmethod/keyboard/internal/MockKeyboardSwitcher.java b/tests/src/com/android/inputmethod/keyboard/internal/MockKeyboardSwitcher.java
new file mode 100644
index 000000000..1e294d179
--- /dev/null
+++ b/tests/src/com/android/inputmethod/keyboard/internal/MockKeyboardSwitcher.java
@@ -0,0 +1,157 @@
+/*
+ * Copyright (C) 2012 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+
+package com.android.inputmethod.keyboard.internal;
+
+import com.android.inputmethod.keyboard.Keyboard;
+import com.android.inputmethod.keyboard.internal.KeyboardState.SwitchActions;
+
+public class MockKeyboardSwitcher implements KeyboardState.SwitchActions {
+ public interface Constants {
+ // Argument for KeyboardState.onPressKey and onReleaseKey.
+ public static final boolean NOT_SLIDING = false;
+ public static final boolean SLIDING = true;
+ // Argument for KeyboardState.onCodeInput.
+ public static final boolean SINGLE = true;
+ public static final boolean MULTI = false;
+ public static final boolean NO_AUTO_CAPS = false;
+ public static final boolean AUTO_CAPS = true;
+
+ public static final int CODE_SHIFT = Keyboard.CODE_SHIFT;
+ public static final int CODE_SYMBOL = Keyboard.CODE_SWITCH_ALPHA_SYMBOL;
+ public static final int CODE_CAPSLOCK = Keyboard.CODE_CAPSLOCK;
+ public static final int CODE_SPACE = Keyboard.CODE_SPACE;
+ }
+
+ public static final String WORD_SEPARATORS = " ,.";
+
+ private static final int ALPHABET_UNSHIFTED = 0;
+ private static final int ALPHABET_MANUAL_SHIFTED = 1;
+ private static final int ALPHABET_AUTOMATIC_SHIFTED = 2;
+ private static final int ALPHABET_SHIFT_LOCKED = 3;
+ private static final int SYMBOLS_UNSHIFTED = 4;
+ private static final int SYMBOLS_SHIFTED = 5;
+
+ private int mLayout = ALPHABET_UNSHIFTED;
+
+ private boolean mAutoCapsMode = Constants.NO_AUTO_CAPS;
+ // Following InputConnection's behavior. Simulating InputType.TYPE_TEXT_FLAG_CAP_WORDS.
+ private boolean mAutoCapsState = true;
+
+ private final KeyboardState mState = new KeyboardState(this);
+
+ public boolean assertAlphabetNormal() {
+ return mLayout == ALPHABET_UNSHIFTED;
+ }
+
+ public boolean assertAlphabetManualShifted() {
+ return mLayout == ALPHABET_MANUAL_SHIFTED;
+ }
+
+ public boolean assertAlphabetAutomaticShifted() {
+ return mLayout == ALPHABET_AUTOMATIC_SHIFTED;
+ }
+
+ public boolean assertAlphabetShiftLocked() {
+ return mLayout == ALPHABET_SHIFT_LOCKED;
+ }
+
+ public boolean assertSymbolsNormal() {
+ return mLayout == SYMBOLS_UNSHIFTED;
+ }
+
+ public boolean assertSymbolsShifted() {
+ return mLayout == SYMBOLS_SHIFTED;
+ }
+
+ public void setAutoCapsMode(boolean autoCaps) {
+ mAutoCapsMode = autoCaps;
+ }
+
+ @Override
+ public void setAlphabetKeyboard() {
+ mLayout = ALPHABET_UNSHIFTED;
+ }
+
+ @Override
+ public void setShifted(int shiftMode) {
+ if (shiftMode == SwitchActions.UNSHIFT) {
+ mLayout = ALPHABET_UNSHIFTED;
+ } else if (shiftMode == SwitchActions.MANUAL_SHIFT) {
+ mLayout = ALPHABET_MANUAL_SHIFTED;
+ } else if (shiftMode == SwitchActions.AUTOMATIC_SHIFT) {
+ mLayout = ALPHABET_AUTOMATIC_SHIFTED;
+ }
+ }
+
+ @Override
+ public void setShiftLocked(boolean shiftLocked) {
+ if (shiftLocked) {
+ mLayout = ALPHABET_SHIFT_LOCKED;
+ } else {
+ mLayout = ALPHABET_UNSHIFTED;
+ }
+ }
+
+ @Override
+ public void setSymbolsKeyboard() {
+ mLayout = SYMBOLS_UNSHIFTED;
+ }
+
+ @Override
+ public void setSymbolsShiftedKeyboard() {
+ mLayout = SYMBOLS_SHIFTED;
+ }
+
+ @Override
+ public void requestUpdatingShiftState() {
+ mState.onUpdateShiftState(mAutoCapsMode && mAutoCapsState);
+ }
+
+ public void updateShiftState() {
+ mState.onUpdateShiftState(mAutoCapsMode && mAutoCapsState);
+ }
+
+ public void loadKeyboard(String layoutSwitchBackSymbols,
+ boolean hasDistinctMultitouch) {
+ mState.onLoadKeyboard(layoutSwitchBackSymbols, hasDistinctMultitouch);
+ }
+
+ public void onPressKey(int code) {
+ mState.onPressKey(code);
+ }
+
+ public void onReleaseKey(int code) {
+ onReleaseKey(code, Constants.NOT_SLIDING);
+ }
+
+ public void onReleaseKey(int code, boolean withSliding) {
+ mState.onReleaseKey(code, withSliding);
+ }
+
+ public void onCodeInput(int code) {
+ onCodeInput(code, Constants.SINGLE);
+ }
+
+ public void onCodeInput(int code, boolean isSinglePointer) {
+ mAutoCapsState = (WORD_SEPARATORS.indexOf(code) >= 0);
+ mState.onCodeInput(code, isSinglePointer, mAutoCapsMode && mAutoCapsState);
+ }
+
+ public void onCancelInput(boolean isSinglePointer) {
+ mState.onCancelInput(isSinglePointer);
+ }
+} \ No newline at end of file
diff --git a/tests/src/com/android/inputmethod/latin/SubtypeLocaleTests.java b/tests/src/com/android/inputmethod/latin/SubtypeLocaleTests.java
index fec3e8ee1..7925d1a49 100644
--- a/tests/src/com/android/inputmethod/latin/SubtypeLocaleTests.java
+++ b/tests/src/com/android/inputmethod/latin/SubtypeLocaleTests.java
@@ -16,10 +16,7 @@
package com.android.inputmethod.latin;
-import com.android.inputmethod.latin.LocaleUtils;
-
import android.content.Context;
-import android.content.res.Resources;
import android.test.AndroidTestCase;
import android.view.inputmethod.InputMethodInfo;
import android.view.inputmethod.InputMethodManager;
@@ -30,24 +27,22 @@ import java.util.List;
import java.util.Locale;
public class SubtypeLocaleTests extends AndroidTestCase {
- private static final String PACKAGE = LatinIME.class.getPackage().getName();
-
- private Resources mRes;
- private List<InputMethodSubtype> mKeyboardSubtypes = new ArrayList<InputMethodSubtype>();
+ private List<InputMethodSubtype> mKeyboardSubtypes;
@Override
protected void setUp() throws Exception {
super.setUp();
final Context context = getContext();
- mRes = context.getResources();
+ final String packageName = context.getApplicationInfo().packageName;
SubtypeLocale.init(context);
final InputMethodManager imm = (InputMethodManager) context.getSystemService(
Context.INPUT_METHOD_SERVICE);
for (final InputMethodInfo imi : imm.getInputMethodList()) {
- if (imi.getPackageName().equals(PACKAGE)) {
+ if (imi.getPackageName().equals(packageName)) {
+ mKeyboardSubtypes = new ArrayList<InputMethodSubtype>();
final int subtypeCount = imi.getSubtypeCount();
for (int i = 0; i < subtypeCount; ++i) {
InputMethodSubtype subtype = imi.getSubtypeAt(i);
@@ -58,37 +53,29 @@ public class SubtypeLocaleTests extends AndroidTestCase {
break;
}
}
- assertNotNull("Can not find input method " + PACKAGE, mKeyboardSubtypes);
+ assertNotNull("Can not find input method " + packageName, mKeyboardSubtypes);
assertTrue("Can not find keyboard subtype", mKeyboardSubtypes.size() > 0);
}
- private String getStringWithLocale(int resId, Locale locale) {
- final Locale savedLocale = Locale.getDefault();
- try {
- Locale.setDefault(locale);
- return mRes.getString(resId);
- } finally {
- Locale.setDefault(savedLocale);
- }
- }
-
public void testSubtypeLocale() {
final StringBuilder messages = new StringBuilder();
int failedCount = 0;
for (final InputMethodSubtype subtype : mKeyboardSubtypes) {
- final String localeCode = subtype.getLocale();
- final Locale locale = LocaleUtils.constructLocaleFromString(localeCode);
- // The locale name which will be displayed on spacebar. For example 'English (US)' or
- // 'Francais (Canada)'. (c=\u008d)
- final String displayName = SubtypeLocale.getFullDisplayName(locale);
- // The subtype name in its locale. For example 'English (US) Keyboard' or
- // 'Clavier Francais (Canada)'. (c=\u008d)
- final String subtypeName = getStringWithLocale(subtype.getNameResId(), locale);
- if (subtypeName.contains(displayName)) {
+ final Locale locale = LocaleUtils.constructLocaleFromString(subtype.getLocale());
+ final String subtypeLocaleString =
+ subtype.containsExtraValueKey(LatinIME.SUBTYPE_EXTRA_VALUE_KEYBOARD_LOCALE)
+ ? subtype.getExtraValueOf(LatinIME.SUBTYPE_EXTRA_VALUE_KEYBOARD_LOCALE)
+ : subtype.getLocale();
+ final Locale subtypeLocale = LocaleUtils.constructLocaleFromString(subtypeLocaleString);
+ // The subtype name in its locale. For example 'English (US)' or 'Deutsch (QWERTY)'.
+ final String subtypeName = SubtypeLocale.getFullDisplayName(subtypeLocale);
+ // The locale language name in its locale.
+ final String languageName = locale.getDisplayLanguage(locale);
+ if (!subtypeName.contains(languageName)) {
failedCount++;
messages.append(String.format(
- "subtype name is '%s' and should contain locale '%s' name '%s'\n",
- subtypeName, localeCode, displayName));
+ "subtype name is '%s' and should contain locale '%s' language name '%s'\n",
+ subtypeName, subtypeLocale, languageName));
}
}
assertEquals(messages.toString(), 0, failedCount);
diff --git a/tests/src/com/android/inputmethod/latin/SuggestHelper.java b/tests/src/com/android/inputmethod/latin/SuggestHelper.java
index cccd1a4a9..2e362458b 100644
--- a/tests/src/com/android/inputmethod/latin/SuggestHelper.java
+++ b/tests/src/com/android/inputmethod/latin/SuggestHelper.java
@@ -21,7 +21,7 @@ import android.text.TextUtils;
import com.android.inputmethod.keyboard.KeyDetector;
import com.android.inputmethod.keyboard.Keyboard;
-import com.android.inputmethod.keyboard.KeyboardId;
+import com.android.inputmethod.keyboard.KeyboardSet;
import java.io.File;
import java.util.Locale;
@@ -32,24 +32,20 @@ public class SuggestHelper {
protected final Keyboard mKeyboard;
private final KeyDetector mKeyDetector;
- public static final int ALPHABET_KEYBOARD = com.android.inputmethod.latin.R.xml.kbd_qwerty;
-
- public SuggestHelper(Context context, int dictionaryId, KeyboardId keyboardId) {
+ public SuggestHelper(Context context, int dictionaryId, KeyboardSet keyboardSet) {
// Use null as the locale for Suggest so as to force it to use the internal dictionary
// (and not try to find a dictionary provider for a specified locale)
mSuggest = new Suggest(context, dictionaryId, null);
- mKeyboard = new Keyboard.Builder<Keyboard.Params>(context, new Keyboard.Params())
- .load(ALPHABET_KEYBOARD, keyboardId).build();
+ mKeyboard = keyboardSet.getMainKeyboard();
mKeyDetector = new KeyDetector(0);
init();
}
protected SuggestHelper(final Context context, final File dictionaryPath,
- final long startOffset, final long length, final KeyboardId keyboardId,
+ final long startOffset, final long length, final KeyboardSet keyboardSet,
final Locale locale) {
mSuggest = new Suggest(context, dictionaryPath, startOffset, length, null, locale);
- mKeyboard = new Keyboard.Builder<Keyboard.Params>(context, new Keyboard.Params())
- .load(ALPHABET_KEYBOARD, keyboardId).build();
+ mKeyboard = keyboardSet.getMainKeyboard();
mKeyDetector = new KeyDetector(0);
init();
}
diff --git a/tests/src/com/android/inputmethod/latin/SuggestTests.java b/tests/src/com/android/inputmethod/latin/SuggestTests.java
index 4080f34be..e12ae58c4 100644
--- a/tests/src/com/android/inputmethod/latin/SuggestTests.java
+++ b/tests/src/com/android/inputmethod/latin/SuggestTests.java
@@ -33,7 +33,7 @@ public class SuggestTests extends SuggestTestsBase {
final Locale locale = Locale.US;
mHelper = new SuggestHelper(
getContext(), mTestPackageFile, dict.getStartOffset(), dict.getLength(),
- createKeyboardId(locale, Configuration.ORIENTATION_PORTRAIT), locale);
+ createKeyboardSet(locale, Configuration.ORIENTATION_PORTRAIT), locale);
mHelper.setCorrectionMode(Suggest.CORRECTION_FULL_BIGRAM);
}
@@ -183,7 +183,8 @@ public class SuggestTests extends SuggestTestsBase {
"part", mHelper.getBigramAutoCorrection("about", "pa"));
// TODO: The following test fails.
// suggested("single: said", "said", mHelper.getAutoCorrection("sa"));
- suggested("bigram: from sa[me]",
- "same", mHelper.getBigramAutoCorrection("from", "sa"));
+ // TODO: The following test fails due to "transpose correction".
+ // suggested("bigram: from sa[me]",
+ // "same", mHelper.getBigramAutoCorrection("from", "sa"));
}
}
diff --git a/tests/src/com/android/inputmethod/latin/SuggestTestsBase.java b/tests/src/com/android/inputmethod/latin/SuggestTestsBase.java
index 9dd61d78c..73e34ba6f 100644
--- a/tests/src/com/android/inputmethod/latin/SuggestTestsBase.java
+++ b/tests/src/com/android/inputmethod/latin/SuggestTestsBase.java
@@ -22,8 +22,9 @@ import android.test.AndroidTestCase;
import android.text.InputType;
import android.text.TextUtils;
import android.util.DisplayMetrics;
+import android.view.inputmethod.EditorInfo;
-import com.android.inputmethod.keyboard.KeyboardId;
+import com.android.inputmethod.keyboard.KeyboardSet;
import java.io.File;
import java.io.InputStream;
@@ -38,7 +39,12 @@ public class SuggestTestsBase extends AndroidTestCase {
mTestPackageFile = new File(getTestContext().getApplicationInfo().sourceDir);
}
- protected KeyboardId createKeyboardId(Locale locale, int orientation) {
+ protected KeyboardSet createKeyboardSet(Locale locale, int orientation) {
+ return createKeyboardSet(locale, orientation, false);
+ }
+
+ protected KeyboardSet createKeyboardSet(Locale locale, int orientation,
+ boolean touchPositionCorrectionEnabled) {
final DisplayMetrics dm = getContext().getResources().getDisplayMetrics();
final int width;
if (orientation == Configuration.ORIENTATION_LANDSCAPE) {
@@ -50,8 +56,12 @@ public class SuggestTestsBase extends AndroidTestCase {
+ "orientation=" + orientation);
return null;
}
- return new KeyboardId(KeyboardId.ELEMENT_ALPHABET, locale, orientation, width,
- KeyboardId.MODE_TEXT, InputType.TYPE_CLASS_TEXT, 0, false, false, false, false);
+ final EditorInfo editorInfo = new EditorInfo();
+ editorInfo.inputType = InputType.TYPE_CLASS_TEXT;
+ final KeyboardSet.Builder builder = new KeyboardSet.Builder(getContext(), editorInfo);
+ builder.setScreenGeometry(orientation, width);
+ builder.setSubtype(locale, true, touchPositionCorrectionEnabled);
+ return builder.build();
}
protected InputStream openTestRawResource(int resIdInTest) {
diff --git a/tests/src/com/android/inputmethod/latin/UserBigramSuggestHelper.java b/tests/src/com/android/inputmethod/latin/UserBigramSuggestHelper.java
index 863c2b254..74fadf76b 100644
--- a/tests/src/com/android/inputmethod/latin/UserBigramSuggestHelper.java
+++ b/tests/src/com/android/inputmethod/latin/UserBigramSuggestHelper.java
@@ -16,11 +16,11 @@
package com.android.inputmethod.latin;
-import com.android.inputmethod.keyboard.KeyboardId;
-
import android.content.Context;
import android.text.TextUtils;
+import com.android.inputmethod.keyboard.KeyboardSet;
+
import java.io.File;
import java.util.Locale;
import java.util.StringTokenizer;
@@ -31,8 +31,8 @@ public class UserBigramSuggestHelper extends SuggestHelper {
public UserBigramSuggestHelper(final Context context, final File dictionaryPath,
final long startOffset, final long length, final int userBigramMax,
- final int userBigramDelete, final KeyboardId keyboardId, final Locale locale) {
- super(context, dictionaryPath, startOffset, length, keyboardId, locale);
+ final int userBigramDelete, final KeyboardSet keyboardSet, final Locale locale) {
+ super(context, dictionaryPath, startOffset, length, keyboardSet, locale);
mContext = context;
mUserBigram = new UserBigramDictionary(context, null, locale.toString(),
Suggest.DIC_USER);
diff --git a/tests/src/com/android/inputmethod/latin/UserBigramSuggestTests.java b/tests/src/com/android/inputmethod/latin/UserBigramSuggestTests.java
index 2bc0aabc0..2b88a7ca6 100644
--- a/tests/src/com/android/inputmethod/latin/UserBigramSuggestTests.java
+++ b/tests/src/com/android/inputmethod/latin/UserBigramSuggestTests.java
@@ -23,7 +23,7 @@ import com.android.inputmethod.latin.tests.R;
import java.util.Locale;
public class UserBigramSuggestTests extends SuggestTestsBase {
- private static final int SUGGESTION_STARTS = 6;
+ private static final int SUGGESTION_STARTS = 1;
private static final int MAX_DATA = 20;
private static final int DELETE_DATA = 10;
@@ -37,7 +37,7 @@ public class UserBigramSuggestTests extends SuggestTestsBase {
mHelper = new UserBigramSuggestHelper(
getContext(), mTestPackageFile, dict.getStartOffset(), dict.getLength(),
MAX_DATA, DELETE_DATA,
- createKeyboardId(locale, Configuration.ORIENTATION_PORTRAIT), locale);
+ createKeyboardSet(locale, Configuration.ORIENTATION_PORTRAIT), locale);
}
/************************** Tests ************************/
diff --git a/tests/src/com/android/inputmethod/latin/UtilsTests.java b/tests/src/com/android/inputmethod/latin/UtilsTests.java
index 5c0b03a0a..2ef4e2ff5 100644
--- a/tests/src/com/android/inputmethod/latin/UtilsTests.java
+++ b/tests/src/com/android/inputmethod/latin/UtilsTests.java
@@ -18,8 +18,6 @@ package com.android.inputmethod.latin;
import android.test.AndroidTestCase;
-import com.android.inputmethod.latin.tests.R;
-
public class UtilsTests extends AndroidTestCase {
// The following is meant to be a reasonable default for
diff --git a/tools/makedict/src/com/android/inputmethod/latin/BinaryDictInputOutput.java b/tools/makedict/src/com/android/inputmethod/latin/BinaryDictInputOutput.java
index fcbb645f5..7aadc677b 100644
--- a/tools/makedict/src/com/android/inputmethod/latin/BinaryDictInputOutput.java
+++ b/tools/makedict/src/com/android/inputmethod/latin/BinaryDictInputOutput.java
@@ -144,7 +144,6 @@ public class BinaryDictInputOutput {
private static final int GROUP_CHARACTERS_TERMINATOR = 0x1F;
- private static final int GROUP_COUNT_SIZE = 1;
private static final int GROUP_TERMINATOR_SIZE = 1;
private static final int GROUP_FLAGS_SIZE = 1;
private static final int GROUP_FREQUENCY_SIZE = 1;
@@ -155,9 +154,8 @@ public class BinaryDictInputOutput {
private static final int NO_CHILDREN_ADDRESS = Integer.MIN_VALUE;
private static final int INVALID_CHARACTER = -1;
- // Limiting to 127 for upward compatibility
- // TODO: implement a scheme to be able to shoot 256 chargroups in a node
- private static final int MAX_CHARGROUPS_IN_A_NODE = 127;
+ private static final int MAX_CHARGROUPS_FOR_ONE_BYTE_CHARGROUP_COUNT = 0x7F; // 127
+ private static final int MAX_CHARGROUPS_IN_A_NODE = 0x7FFF; // 32767
private static final int MAX_TERMINAL_FREQUENCY = 255;
@@ -267,6 +265,31 @@ public class BinaryDictInputOutput {
}
/**
+ * Compute the binary size of the group count
+ * @param count the group count
+ * @return the size of the group count, either 1 or 2 bytes.
+ */
+ private static int getGroupCountSize(final int count) {
+ if (MAX_CHARGROUPS_FOR_ONE_BYTE_CHARGROUP_COUNT >= count) {
+ return 1;
+ } else if (MAX_CHARGROUPS_IN_A_NODE >= count) {
+ return 2;
+ } else {
+ throw new RuntimeException("Can't have more than " + MAX_CHARGROUPS_IN_A_NODE
+ + " groups in a node (found " + count +")");
+ }
+ }
+
+ /**
+ * Compute the binary size of the group count for a node
+ * @param node the node
+ * @return the size of the group count, either 1 or 2 bytes.
+ */
+ private static int getGroupCountSize(final Node node) {
+ return getGroupCountSize(node.mData.size());
+ }
+
+ /**
* Compute the maximum size of a CharGroup, assuming 3-byte addresses for everything.
*
* @param group the CharGroup to compute the size of.
@@ -295,7 +318,7 @@ public class BinaryDictInputOutput {
* @param node the node to compute the maximum size of.
*/
private static void setNodeMaximumSize(Node node) {
- int size = GROUP_COUNT_SIZE;
+ int size = getGroupCountSize(node);
for (CharGroup g : node.mData) {
final int groupSize = getCharGroupMaximumSize(g);
g.mCachedSize = groupSize;
@@ -394,7 +417,7 @@ public class BinaryDictInputOutput {
* @param dict the dictionary in which the word/attributes are to be found.
*/
private static void computeActualNodeSize(Node node, FusionDictionary dict) {
- int size = GROUP_COUNT_SIZE;
+ int size = getGroupCountSize(node);
for (CharGroup group : node.mData) {
int groupSize = GROUP_FLAGS_SIZE + getGroupCharactersSize(group);
if (group.isTerminal()) groupSize += GROUP_FREQUENCY_SIZE;
@@ -437,12 +460,13 @@ public class BinaryDictInputOutput {
int nodeOffset = 0;
for (Node n : flatNodes) {
n.mCachedAddress = nodeOffset;
+ int groupCountSize = getGroupCountSize(n);
int groupOffset = 0;
for (CharGroup g : n.mData) {
- g.mCachedAddress = GROUP_COUNT_SIZE + nodeOffset + groupOffset;
+ g.mCachedAddress = groupCountSize + nodeOffset + groupOffset;
groupOffset += g.mCachedSize;
}
- if (groupOffset + GROUP_COUNT_SIZE != n.mCachedSize) {
+ if (groupOffset + groupCountSize != n.mCachedSize) {
throw new RuntimeException("Bug : Stored and computed node size differ");
}
nodeOffset += n.mCachedSize;
@@ -582,7 +606,9 @@ public class BinaryDictInputOutput {
}
flags |= FLAG_HAS_BIGRAMS;
}
- // TODO: fill in the FLAG_IS_SHORTCUT_ONLY
+ if (group.mIsShortcutOnly) {
+ flags |= FLAG_IS_SHORTCUT_ONLY;
+ }
return flags;
}
@@ -629,13 +655,20 @@ public class BinaryDictInputOutput {
private static int writePlacedNode(FusionDictionary dict, byte[] buffer, Node node) {
int index = node.mCachedAddress;
- final int size = node.mData.size();
- if (size > MAX_CHARGROUPS_IN_A_NODE)
- throw new RuntimeException("A node has a group count over 127 (" + size + ").");
-
- buffer[index++] = (byte)size;
+ final int groupCount = node.mData.size();
+ final int countSize = getGroupCountSize(node);
+ if (1 == countSize) {
+ buffer[index++] = (byte)groupCount;
+ } else if (2 == countSize) {
+ // We need to signal 2-byte size by setting the top bit of the MSB to 1, so
+ // we | 0x80 to do this.
+ buffer[index++] = (byte)((groupCount >> 8) | 0x80);
+ buffer[index++] = (byte)(groupCount & 0xFF);
+ } else {
+ throw new RuntimeException("Strange size from getGroupCountSize : " + countSize);
+ }
int groupAddress = index;
- for (int i = 0; i < size; ++i) {
+ for (int i = 0; i < groupCount; ++i) {
CharGroup group = node.mData.get(i);
if (index != group.mCachedAddress) throw new RuntimeException("Bug: write index is not "
+ "the same as the cached address of the group");
@@ -891,7 +924,7 @@ public class BinaryDictInputOutput {
addressPointer += 3;
break;
default:
- throw new RuntimeException("Has attribute with no address");
+ throw new RuntimeException("Has shortcut targets with no address");
}
shortcutTargets.add(new PendingAttribute(targetFlags & FLAG_ATTRIBUTE_FREQUENCY,
targetAddress));
@@ -922,7 +955,7 @@ public class BinaryDictInputOutput {
addressPointer += 3;
break;
default:
- throw new RuntimeException("Has attribute with no address");
+ throw new RuntimeException("Has bigrams with no address");
}
bigrams.add(new PendingAttribute(bigramFlags & FLAG_ATTRIBUTE_FREQUENCY,
bigramAddress));
@@ -934,6 +967,19 @@ public class BinaryDictInputOutput {
}
/**
+ * Reads and returns the char group count out of a file and forwards the pointer.
+ */
+ private static int readCharGroupCount(RandomAccessFile source) throws IOException {
+ final int msb = source.readUnsignedByte();
+ if (MAX_CHARGROUPS_FOR_ONE_BYTE_CHARGROUP_COUNT >= msb) {
+ return msb;
+ } else {
+ return ((MAX_CHARGROUPS_FOR_ONE_BYTE_CHARGROUP_COUNT & msb) << 8)
+ + source.readUnsignedByte();
+ }
+ }
+
+ /**
* Finds, as a string, the word at the address passed as an argument.
*
* @param source the file to read from.
@@ -946,8 +992,8 @@ public class BinaryDictInputOutput {
int address) throws IOException {
final long originalPointer = source.getFilePointer();
source.seek(headerSize);
- final int count = source.readUnsignedByte();
- int groupOffset = 1; // 1 for the group count
+ final int count = readCharGroupCount(source);
+ int groupOffset = getGroupCountSize(count);
final StringBuilder builder = new StringBuilder();
String result = null;
@@ -1003,9 +1049,9 @@ public class BinaryDictInputOutput {
Map<Integer, Node> reverseNodeMap, Map<Integer, CharGroup> reverseGroupMap)
throws IOException {
final int nodeOrigin = (int)(source.getFilePointer() - headerSize);
- final int count = source.readUnsignedByte();
+ final int count = readCharGroupCount(source);
final ArrayList<CharGroup> nodeContents = new ArrayList<CharGroup>();
- int groupOffset = nodeOrigin + 1; // 1 byte for the group count
+ int groupOffset = nodeOrigin + getGroupCountSize(count);
for (int i = count; i > 0; --i) {
CharGroupInfo info = readCharGroup(source, groupOffset);
ArrayList<WeightedString> shortcutTargets = null;
diff --git a/tools/makedict/src/com/android/inputmethod/latin/FusionDictionary.java b/tools/makedict/src/com/android/inputmethod/latin/FusionDictionary.java
index 2327e1972..918b1ca4b 100644
--- a/tools/makedict/src/com/android/inputmethod/latin/FusionDictionary.java
+++ b/tools/makedict/src/com/android/inputmethod/latin/FusionDictionary.java
@@ -171,6 +171,24 @@ public class FusionDictionary implements Iterable<Word> {
}
/**
+ * Helper method to add all words in a list as 0-frequency entries
+ *
+ * These words are added when shortcuts targets or bigrams are not found in the dictionary
+ * yet. The same words may be added later with an actual frequency - this is handled by
+ * the private version of add().
+ */
+ private void addNeutralWords(final ArrayList<WeightedString> words) {
+ if (null != words) {
+ for (WeightedString word : words) {
+ final CharGroup t = findWordInTree(mRoot, word.mWord);
+ if (null == t) {
+ add(getCodePoints(word.mWord), 0, null, null, false /* isShortcutOnly */);
+ }
+ }
+ }
+ }
+
+ /**
* Helper method to add a word as a string.
*
* This method adds a word to the dictionary with the given frequency. Optional
@@ -186,22 +204,12 @@ public class FusionDictionary implements Iterable<Word> {
final ArrayList<WeightedString> shortcutTargets,
final ArrayList<WeightedString> bigrams) {
if (null != shortcutTargets) {
- for (WeightedString target : shortcutTargets) {
- final CharGroup t = findWordInTree(mRoot, target.mWord);
- if (null == t) {
- add(getCodePoints(target.mWord), 0, null, null);
- }
- }
+ addNeutralWords(shortcutTargets);
}
if (null != bigrams) {
- for (WeightedString bigram : bigrams) {
- final CharGroup t = findWordInTree(mRoot, bigram.mWord);
- if (null == t) {
- add(getCodePoints(bigram.mWord), 0, null, null);
- }
- }
+ addNeutralWords(bigrams);
}
- add(getCodePoints(word), frequency, shortcutTargets, bigrams);
+ add(getCodePoints(word), frequency, shortcutTargets, bigrams, false /* isShortcutOnly */);
}
/**
@@ -223,6 +231,22 @@ public class FusionDictionary implements Iterable<Word> {
}
/**
+ * Helper method to add a shortcut that should not be a dictionary word.
+ *
+ * @param word the word to add.
+ * @param frequency the frequency of the word, in the range [0..255].
+ * @param shortcutTargets a list of shortcut targets. May not be null.
+ */
+ public void addShortcutOnly(final String word, final int frequency,
+ final ArrayList<WeightedString> shortcutTargets) {
+ if (null == shortcutTargets) {
+ throw new RuntimeException("Can't add a shortcut without targets");
+ }
+ addNeutralWords(shortcutTargets);
+ add(getCodePoints(word), frequency, shortcutTargets, null, true /* isShortcutOnly */);
+ }
+
+ /**
* Add a word to this dictionary.
*
* The shortcuts and bigrams, if any, have to be in the dictionary already. If they aren't,
@@ -232,10 +256,12 @@ public class FusionDictionary implements Iterable<Word> {
* @param frequency the frequency of the word, in the range [0..255].
* @param shortcutTargets an optional list of shortcut targets for this word (null if none).
* @param bigrams an optional list of bigrams for this word (null if none).
+ * @param isShortcutOnly whether this should be a shortcut only.
*/
private void add(final int[] word, final int frequency,
final ArrayList<WeightedString> shortcutTargets,
- final ArrayList<WeightedString> bigrams) {
+ final ArrayList<WeightedString> bigrams,
+ final boolean isShortcutOnly) {
assert(frequency >= 0 && frequency <= 255);
Node currentNode = mRoot;
int charIndex = 0;
@@ -260,7 +286,7 @@ public class FusionDictionary implements Iterable<Word> {
final int insertionIndex = findInsertionIndex(currentNode, word[charIndex]);
final CharGroup newGroup = new CharGroup(
Arrays.copyOfRange(word, charIndex, word.length),
- shortcutTargets, bigrams, frequency, false /* isShortcutOnly */);
+ shortcutTargets, bigrams, frequency, isShortcutOnly);
currentNode.mData.add(insertionIndex, newGroup);
checkStack(currentNode);
} else {
@@ -275,7 +301,7 @@ public class FusionDictionary implements Iterable<Word> {
} else {
final CharGroup newNode = new CharGroup(currentGroup.mChars,
shortcutTargets, bigrams, frequency, currentGroup.mChildren,
- false /* isShortcutOnly */);
+ isShortcutOnly);
currentNode.mData.set(nodeIndex, newNode);
checkStack(currentNode);
}
@@ -284,8 +310,7 @@ public class FusionDictionary implements Iterable<Word> {
// We only have to create a new node and add it to the end of this.
final CharGroup newNode = new CharGroup(
Arrays.copyOfRange(word, charIndex + differentCharIndex, word.length),
- shortcutTargets, bigrams, frequency,
- false /* isShortcutOnly */);
+ shortcutTargets, bigrams, frequency, isShortcutOnly);
currentGroup.mChildren = new Node();
currentGroup.mChildren.mData.add(newNode);
}
@@ -300,7 +325,8 @@ public class FusionDictionary implements Iterable<Word> {
}
final CharGroup newGroup = new CharGroup(word,
currentGroup.mShortcutTargets, currentGroup.mBigrams,
- frequency, currentGroup.mChildren, false /* isShortcutOnly */);
+ frequency, currentGroup.mChildren,
+ currentGroup.mIsShortcutOnly && isShortcutOnly);
currentNode.mData.set(nodeIndex, newGroup);
}
} else {
@@ -318,16 +344,18 @@ public class FusionDictionary implements Iterable<Word> {
if (charIndex + differentCharIndex >= word.length) {
newParent = new CharGroup(
Arrays.copyOfRange(currentGroup.mChars, 0, differentCharIndex),
- shortcutTargets, bigrams, frequency, newChildren,
- false /* isShortcutOnly */);
+ shortcutTargets, bigrams, frequency, newChildren, isShortcutOnly);
} else {
+ // isShortcutOnly makes no sense for non-terminal nodes. The following node
+ // is non-terminal (frequency 0 in FusionDictionary representation) so we
+ // pass false for isShortcutOnly
newParent = new CharGroup(
Arrays.copyOfRange(currentGroup.mChars, 0, differentCharIndex),
null, null, -1, newChildren, false /* isShortcutOnly */);
final CharGroup newWord = new CharGroup(
Arrays.copyOfRange(word, charIndex + differentCharIndex,
word.length), shortcutTargets, bigrams, frequency,
- false /* isShortcutOnly */);
+ isShortcutOnly);
final int addIndex = word[charIndex + differentCharIndex]
> currentGroup.mChars[differentCharIndex] ? 1 : 0;
newChildren.mData.add(addIndex, newWord);
@@ -435,6 +463,16 @@ public class FusionDictionary implements Iterable<Word> {
}
/**
+ * Helper method to find out whether a word is in the dict or not.
+ */
+ public boolean hasWord(final String s) {
+ if (null == s || "".equals(s)) {
+ throw new RuntimeException("Can't search for a null or empty string");
+ }
+ return null != findWordInTree(mRoot, s);
+ }
+
+ /**
* Recursively count the number of character groups in a given branch of the trie.
*
* @param node the parent node.
@@ -609,7 +647,8 @@ public class FusionDictionary implements Iterable<Word> {
}
if (currentGroup.mFrequency >= 0)
return new Word(mCurrentString.toString(), currentGroup.mFrequency,
- currentGroup.mShortcutTargets, currentGroup.mBigrams);
+ currentGroup.mShortcutTargets, currentGroup.mBigrams,
+ currentGroup.mIsShortcutOnly);
} else {
mPositions.removeLast();
currentPos = mPositions.getLast();
diff --git a/tools/makedict/src/com/android/inputmethod/latin/Word.java b/tools/makedict/src/com/android/inputmethod/latin/Word.java
index 561b21bb3..cf6116f91 100644
--- a/tools/makedict/src/com/android/inputmethod/latin/Word.java
+++ b/tools/makedict/src/com/android/inputmethod/latin/Word.java
@@ -28,16 +28,18 @@ import java.util.ArrayList;
public class Word implements Comparable<Word> {
final String mWord;
final int mFrequency;
+ final boolean mIsShortcutOnly;
final ArrayList<WeightedString> mShortcutTargets;
final ArrayList<WeightedString> mBigrams;
public Word(final String word, final int frequency,
final ArrayList<WeightedString> shortcutTargets,
- final ArrayList<WeightedString> bigrams) {
+ final ArrayList<WeightedString> bigrams, final boolean isShortcutOnly) {
mWord = word;
mFrequency = frequency;
mShortcutTargets = shortcutTargets;
mBigrams = bigrams;
+ mIsShortcutOnly = isShortcutOnly;
}
/**
diff --git a/tools/makedict/src/com/android/inputmethod/latin/XmlDictInputOutput.java b/tools/makedict/src/com/android/inputmethod/latin/XmlDictInputOutput.java
index d6c03ed70..77c536668 100644
--- a/tools/makedict/src/com/android/inputmethod/latin/XmlDictInputOutput.java
+++ b/tools/makedict/src/com/android/inputmethod/latin/XmlDictInputOutput.java
@@ -45,6 +45,9 @@ public class XmlDictInputOutput {
private static final String SHORTCUT_TAG = "shortcut";
private static final String FREQUENCY_ATTR = "f";
private static final String WORD_ATTR = "word";
+ private static final String SHORTCUT_ONLY_ATTR = "shortcutOnly";
+
+ private static final int SHORTCUT_ONLY_DEFAULT_FREQ = 1;
/**
* SAX handler for a unigram XML file.
@@ -232,6 +235,15 @@ public class XmlDictInputOutput {
new UnigramHandler(dict, shortcutHandler.getShortcutMap(),
bigramHandler.getBigramMap());
parser.parse(unigrams, unigramHandler);
+
+ final HashMap<String, ArrayList<WeightedString>> shortcutMap =
+ shortcutHandler.getShortcutMap();
+ for (final String shortcut : shortcutMap.keySet()) {
+ if (dict.hasWord(shortcut)) continue;
+ // TODO: list a frequency in the shortcut file and use it here, instead of
+ // a constant freq
+ dict.addShortcutOnly(shortcut, SHORTCUT_ONLY_DEFAULT_FREQ, shortcutMap.get(shortcut));
+ }
return dict;
}
@@ -264,9 +276,11 @@ public class XmlDictInputOutput {
}
// TODO: use an XMLSerializer if this gets big
destination.write("<wordlist format=\"2\">\n");
+ destination.write("<!-- Warning: there is no code to read this format yet. -->\n");
for (Word word : set) {
destination.write(" <" + WORD_TAG + " " + WORD_ATTR + "=\"" + word.mWord + "\" "
- + FREQUENCY_ATTR + "=\"" + word.mFrequency + "\">");
+ + FREQUENCY_ATTR + "=\"" + word.mFrequency + "\" " + SHORTCUT_ONLY_ATTR
+ + "=\"" + word.mIsShortcutOnly + "\">");
if (null != word.mShortcutTargets) {
destination.write("\n");
for (WeightedString target : word.mShortcutTargets) {