diff options
10 files changed, 542 insertions, 474 deletions
diff --git a/java/AndroidManifest.xml b/java/AndroidManifest.xml index b0525326c..888d73f5c 100644 --- a/java/AndroidManifest.xml +++ b/java/AndroidManifest.xml @@ -5,7 +5,6 @@ <uses-permission android:name="android.permission.VIBRATE"/> <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE"/> <uses-permission android:name="android.permission.READ_USER_DICTIONARY" /> - <uses-permission android:name="android.permission.WRITE_USER_DICTIONARY" /> <uses-permission android:name="android.permission.READ_CONTACTS" /> <application android:label="@string/english_ime_name" diff --git a/java/src/com/android/inputmethod/keyboard/KeyboardSwitcher.java b/java/src/com/android/inputmethod/keyboard/KeyboardSwitcher.java index 71a71d6fd..734189689 100644 --- a/java/src/com/android/inputmethod/keyboard/KeyboardSwitcher.java +++ b/java/src/com/android/inputmethod/keyboard/KeyboardSwitcher.java @@ -20,7 +20,6 @@ import android.content.Context; import android.content.SharedPreferences; import android.content.res.Configuration; import android.content.res.Resources; -import android.text.TextUtils; import android.util.DisplayMetrics; import android.util.Log; import android.view.ContextThemeWrapper; @@ -44,7 +43,8 @@ import java.lang.ref.SoftReference; import java.util.HashMap; import java.util.Locale; -public class KeyboardSwitcher implements SharedPreferences.OnSharedPreferenceChangeListener { +public class KeyboardSwitcher implements KeyboardState.SwitchActions, + SharedPreferences.OnSharedPreferenceChangeListener { private static final String TAG = KeyboardSwitcher.class.getSimpleName(); private static final boolean DEBUG_CACHE = LatinImeLogger.sDBG; public static final boolean DEBUG_STATE = false; @@ -69,9 +69,6 @@ public class KeyboardSwitcher implements SharedPreferences.OnSharedPreferenceCha private Resources mResources; private KeyboardState mState; - private static final int UNSHIFT = 0; - private static final int MANUAL_SHIFT = 1; - private static final int AUTOMATIC_SHIFT = 2; private KeyboardId mMainKeyboardId; private KeyboardId mSymbolsKeyboardId; @@ -81,74 +78,15 @@ public class KeyboardSwitcher implements SharedPreferences.OnSharedPreferenceCha private final HashMap<KeyboardId, SoftReference<LatinKeyboard>> mKeyboardCache = new HashMap<KeyboardId, SoftReference<LatinKeyboard>>(); - private KeyboardLayoutState mSavedKeyboardState = new KeyboardLayoutState(); - /** mIsAutoCorrectionActive indicates that auto corrected word will be input instead of * what user actually typed. */ private boolean mIsAutoCorrectionActive; - // TODO: Encapsulate these state handling to separate class and combine with ShiftKeyState - // and ModifierKeyState into KeyboardState. - private static final int SWITCH_STATE_ALPHA = 0; - private static final int SWITCH_STATE_SYMBOL_BEGIN = 1; - private static final int SWITCH_STATE_SYMBOL = 2; - // The following states are used only on the distinct multi-touch panel devices. - private static final int SWITCH_STATE_MOMENTARY_ALPHA_AND_SYMBOL = 3; - private static final int SWITCH_STATE_MOMENTARY_SYMBOL_AND_MORE = 4; - private static final int SWITCH_STATE_CHORDING_ALPHA = 5; - private static final int SWITCH_STATE_CHORDING_SYMBOL = 6; - private int mSwitchState = SWITCH_STATE_ALPHA; - - private String mLayoutSwitchBackSymbols; - private int mThemeIndex = -1; private Context mThemeContext; private static final KeyboardSwitcher sInstance = new KeyboardSwitcher(); - // TODO: Move this to KeyboardState. - private class KeyboardLayoutState { - private boolean mIsValid; - private boolean mIsAlphabetMode; - private boolean mIsShiftLocked; - private boolean mIsShifted; - - public void save() { - mIsAlphabetMode = isAlphabetMode(); - if (mIsAlphabetMode) { - mIsShiftLocked = mState.isShiftLocked(); - mIsShifted = !mIsShiftLocked && mState.isShiftedOrShiftLocked(); - } else { - mIsShiftLocked = false; - mIsShifted = isSymbolShifted(); - } - mIsValid = true; - } - - public void restore() { - mPrevMainKeyboardWasShiftLocked = false; - if (!mIsValid || mIsAlphabetMode) { - setAlphabetKeyboard(); - } else { - if (mIsShifted) { - setSymbolsShiftedKeyboard(); - } else { - setSymbolsKeyboard(); - } - } - - if (!mIsValid) return; - mIsValid = false; - - if (mIsAlphabetMode) { - setShiftLocked(mIsShiftLocked); - if (!mIsShiftLocked) { - setShifted(mIsShifted ? MANUAL_SHIFT : UNSHIFT); - } - } - } - } - public static KeyboardSwitcher getInstance() { return sInstance; } @@ -167,7 +105,7 @@ public class KeyboardSwitcher implements SharedPreferences.OnSharedPreferenceCha mResources = ims.getResources(); mPrefs = prefs; mSubtypeSwitcher = SubtypeSwitcher.getInstance(); - mState = new KeyboardState(); + mState = new KeyboardState(this); setContextThemeWrapper(ims, getKeyboardThemeIndex(ims, prefs)); prefs.registerOnSharedPreferenceChangeListener(this); } @@ -199,9 +137,7 @@ public class KeyboardSwitcher implements SharedPreferences.OnSharedPreferenceCha mMainKeyboardId = getKeyboardId(editorInfo, false, false, settingsValues); mSymbolsKeyboardId = getKeyboardId(editorInfo, true, false, settingsValues); mSymbolsShiftedKeyboardId = getKeyboardId(editorInfo, true, true, settingsValues); - mState.onLoadKeyboard(); - mLayoutSwitchBackSymbols = mResources.getString(R.string.layout_switch_back_symbols); - mSavedKeyboardState.restore(); + mState.onLoadKeyboard(mResources.getString(R.string.layout_switch_back_symbols)); } catch (RuntimeException e) { Log.w(TAG, "loading keyboard failed: " + mMainKeyboardId, e); LatinImeLogger.logOnException(mMainKeyboardId.toString(), e); @@ -210,7 +146,7 @@ public class KeyboardSwitcher implements SharedPreferences.OnSharedPreferenceCha public void saveKeyboardState() { if (mCurrentId != null) { - mSavedKeyboardState.save(); + mState.onSaveKeyboardState(isAlphabetMode(), isSymbolShifted()); } } @@ -227,7 +163,7 @@ public class KeyboardSwitcher implements SharedPreferences.OnSharedPreferenceCha mKeyboardView.setKeyboard(keyboard); mCurrentInputView.setKeyboardGeometry(keyboard.mTopPadding); mCurrentId = keyboard.mId; - mSwitchState = getSwitchState(); + mState.onSetKeyboard(isAlphabetMode()); updateShiftLockState(keyboard); mKeyboardView.setKeyPreviewPopupEnabled( Settings.Values.isKeyPreviewPopupEnabled(mPrefs, mResources), @@ -238,10 +174,6 @@ public class KeyboardSwitcher implements SharedPreferences.OnSharedPreferenceCha updateShiftState(); } - private int getSwitchState() { - return isAlphabetMode() ? SWITCH_STATE_ALPHA : SWITCH_STATE_SYMBOL_BEGIN; - } - private void updateShiftLockState(Keyboard keyboard) { if (mCurrentId.equals(mSymbolsShiftedKeyboardId)) { // Symbol keyboard may have an ALT key that has a caps lock style indicator (a.k.a. @@ -346,6 +278,7 @@ public class KeyboardSwitcher implements SharedPreferences.OnSharedPreferenceCha return mCurrentId != null ? mCurrentId.mMode : KeyboardId.MODE_TEXT; } + // TODO: Delegate to KeyboardState public boolean isAlphabetMode() { return mCurrentId != null && mCurrentId.isAlphabetKeyboard(); } @@ -377,7 +310,9 @@ public class KeyboardSwitcher implements SharedPreferences.OnSharedPreferenceCha return mState.isManualTemporaryUpperCase(); } - private void setShifted(int shiftMode) { + // Implements {@link KeyboardState.SwitchActions}. + @Override + public void setShifted(int shiftMode) { mInputMethodService.mHandler.cancelUpdateShiftState(); LatinKeyboard latinKeyboard = getLatinKeyboard(); if (latinKeyboard == null) @@ -401,7 +336,9 @@ public class KeyboardSwitcher implements SharedPreferences.OnSharedPreferenceCha mKeyboardView.invalidateAllKeys(); } - private void setShiftLocked(boolean shiftLocked) { + // Implements {@link KeyboardState.SwitchActions}. + @Override + public void setShiftLocked(boolean shiftLocked) { mInputMethodService.mHandler.cancelUpdateShiftState(); LatinKeyboard latinKeyboard = getLatinKeyboard(); if (latinKeyboard == null) @@ -427,10 +364,17 @@ public class KeyboardSwitcher implements SharedPreferences.OnSharedPreferenceCha if (isAlphabetMode()) { setShifted(mState.isShiftedOrShiftLocked() ? UNSHIFT : MANUAL_SHIFT); } else { - toggleShiftInSymbols(); + if (isSymbolShifted()) { + setSymbolsKeyboard(); + } else { + setSymbolsShiftedKeyboard(); + } } } + /** + * Toggle caps lock state triggered by user touch event. + */ public void toggleCapsLock() { if (DEBUG_STATE) { Log.d(TAG, "toggleCapsLock: " + mState); @@ -448,11 +392,18 @@ public class KeyboardSwitcher implements SharedPreferences.OnSharedPreferenceCha } } + /** + * Toggle keyboard mode triggered by user touch event. + */ public void toggleKeyboardMode() { if (DEBUG_STATE) { - Log.d(TAG, "toggleKeyboard: " + mState); + Log.d(TAG, "toggleKeyboardMode: " + mState); + } + if (isAlphabetMode()) { + setSymbolsKeyboard(); + } else { + setAlphabetKeyboard(); } - toggleAlphabetAndSymbols(); } /** @@ -463,19 +414,7 @@ public class KeyboardSwitcher implements SharedPreferences.OnSharedPreferenceCha Log.d(TAG, "updateShiftState: " + mState + " autoCaps=" + mInputMethodService.getCurrentAutoCapsState()); } - final boolean isAlphabetMode = isAlphabetMode(); - final boolean isShiftLocked = mState.isShiftLocked(); - if (isAlphabetMode) { - if (!isShiftLocked && !mState.isShiftKeyIgnoring()) { - if (mState.isShiftKeyReleasing() && mInputMethodService.getCurrentAutoCapsState()) { - // Only when shift key is releasing, automatic temporary upper case will be set. - setShifted(AUTOMATIC_SHIFT); - } else { - setShifted(mState.isShiftKeyMomentary() ? MANUAL_SHIFT : UNSHIFT); - } - } - } - mState.onUpdateShiftState(isAlphabetMode); + mState.onUpdateShiftState(isAlphabetMode(), mInputMethodService.getCurrentAutoCapsState()); } public void onPressShift(boolean withSliding) { @@ -484,33 +423,7 @@ public class KeyboardSwitcher implements SharedPreferences.OnSharedPreferenceCha if (DEBUG_STATE) { Log.d(TAG, "onPressShift: " + mState + " sliding=" + withSliding); } - final boolean isAlphabetMode = isAlphabetMode(); - final boolean isShiftLocked = mState.isShiftLocked(); - final boolean isAutomaticTemporaryUpperCase = mState.isAutomaticTemporaryUpperCase(); - final boolean isShiftedOrShiftLocked = mState.isShiftedOrShiftLocked(); - if (isAlphabetMode) { - if (isShiftLocked) { - // Shift key is pressed while caps lock state, we will treat this state as shifted - // caps lock state and mark as if shift key pressed while normal state. - setShifted(MANUAL_SHIFT); - } else if (isAutomaticTemporaryUpperCase) { - // Shift key is pressed while automatic temporary upper case, we have to move to - // manual temporary upper case. - setShifted(MANUAL_SHIFT); - } else if (isShiftedOrShiftLocked) { - // In manual upper case state, we just record shift key has been pressing while - // shifted state. - } else { - // In base layout, chording or manual temporary upper case mode is started. - setShifted(MANUAL_SHIFT); - } - } else { - // In symbol mode, just toggle symbol and symbol more keyboard. - toggleShiftInSymbols(); - mSwitchState = SWITCH_STATE_MOMENTARY_SYMBOL_AND_MORE; - } - mState.onPressShift(isAlphabetMode, isShiftLocked, isAutomaticTemporaryUpperCase, - isShiftedOrShiftLocked); + mState.onPressShift(isAlphabetMode(), isSymbolShifted()); } public void onReleaseShift(boolean withSliding) { @@ -519,61 +432,21 @@ public class KeyboardSwitcher implements SharedPreferences.OnSharedPreferenceCha if (DEBUG_STATE) { Log.d(TAG, "onReleaseShift: " + mState + " sliding=" + withSliding); } - final boolean isAlphabetMode = isAlphabetMode(); - final boolean isShiftLocked = mState.isShiftLocked(); - final boolean isShiftLockShifted = mState.isShiftLockShifted(); - final boolean isShiftedOrShiftLocked = mState.isShiftedOrShiftLocked(); - final boolean isManualTemporaryUpperCaseFromAuto = - mState.isManualTemporaryUpperCaseFromAuto(); - if (isAlphabetMode) { - if (mState.isShiftKeyMomentary()) { - // After chording input while normal state. - setShifted(UNSHIFT); - } else if (isShiftLocked && !isShiftLockShifted && (mState.isShiftKeyPressing() - || mState.isShiftKeyPressingOnShifted()) && !withSliding) { - // Shift has been long pressed, ignore this release. - } else if (isShiftLocked && !mState.isShiftKeyIgnoring() && !withSliding) { - // Shift has been pressed without chording while caps lock state. - setShiftLocked(false); - } else if (isShiftedOrShiftLocked && mState.isShiftKeyPressingOnShifted() - && !withSliding) { - // Shift has been pressed without chording while shifted state. - setShifted(UNSHIFT); - } else if (isManualTemporaryUpperCaseFromAuto && mState.isShiftKeyPressing() - && !withSliding) { - // Shift has been pressed without chording while manual temporary upper case - // transited from automatic temporary upper case. - setShifted(UNSHIFT); - } - } else { - // In symbol mode, snap back to the previous keyboard mode if the user chords the shift - // key and another key, then releases the shift key. - if (mSwitchState == SWITCH_STATE_CHORDING_SYMBOL) { - toggleShiftInSymbols(); - } - } - mState.onReleaseShift(); + mState.onReleaseShift(isAlphabetMode(), isSymbolShifted(), withSliding); } public void onPressSymbol() { if (DEBUG_STATE) { Log.d(TAG, "onPressSymbol: " + mState); } - toggleAlphabetAndSymbols(); - mState.onPressSymbol(); - mSwitchState = SWITCH_STATE_MOMENTARY_ALPHA_AND_SYMBOL; + mState.onPressSymbol(isAlphabetMode()); } public void onReleaseSymbol() { if (DEBUG_STATE) { Log.d(TAG, "onReleaseSymbol: " + mState); - } - // 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) { - toggleAlphabetAndSymbols(); } - mState.onReleaseSymbol(); + mState.onReleaseSymbol(isAlphabetMode()); } public void onOtherKeyPressed() { @@ -584,57 +457,36 @@ public class KeyboardSwitcher implements SharedPreferences.OnSharedPreferenceCha } public void onCancelInput() { - // Snap back to the previous keyboard mode if the user cancels sliding input. - if (isSinglePointer()) { - if (mSwitchState == SWITCH_STATE_MOMENTARY_ALPHA_AND_SYMBOL) { - toggleAlphabetAndSymbols(); - } else if (mSwitchState == SWITCH_STATE_MOMENTARY_SYMBOL_AND_MORE) { - toggleShiftInSymbols(); - } - } + mState.onCancelInput(isAlphabetMode(), isSymbolShifted(), isSinglePointer()); } - // TODO: Move this variable to KeyboardState. - private boolean mPrevMainKeyboardWasShiftLocked; - - private void setSymbolsKeyboard() { - mPrevMainKeyboardWasShiftLocked = mState.isShiftLocked(); + // Implements {@link KeyboardState.SwitchActions}. + @Override + public void setSymbolsKeyboard() { + mState.onSaveShiftLockState(); setKeyboard(getKeyboard(mSymbolsKeyboardId)); } - private void setAlphabetKeyboard() { + // Implements {@link KeyboardState.SwitchActions}. + @Override + public void setAlphabetKeyboard() { setKeyboard(getKeyboard(mMainKeyboardId)); - setShiftLocked(mPrevMainKeyboardWasShiftLocked); - mPrevMainKeyboardWasShiftLocked = false; - } - - private void toggleAlphabetAndSymbols() { - if (isAlphabetMode()) { - setSymbolsKeyboard(); - } else { - setAlphabetKeyboard(); - } + mState.onRestoreShiftLockState(); } + // TODO: Remove this method private boolean isSymbolShifted() { return mCurrentId != null && mCurrentId.equals(mSymbolsShiftedKeyboardId); } - private void setSymbolsShiftedKeyboard() { + // Implements {@link KeyboardState.SwitchActions}. + @Override + public void setSymbolsShiftedKeyboard() { setKeyboard(getKeyboard(mSymbolsShiftedKeyboardId)); } - private void toggleShiftInSymbols() { - if (isSymbolShifted()) { - setSymbolsKeyboard(); - } else { - setSymbolsShiftedKeyboard(); - } - } - public boolean isInMomentarySwitchState() { - return mSwitchState == SWITCH_STATE_MOMENTARY_ALPHA_AND_SYMBOL - || mSwitchState == SWITCH_STATE_MOMENTARY_SYMBOL_AND_MORE; + return mState.isInMomentarySwitchState(); } public boolean isVibrateAndSoundFeedbackRequired() { @@ -649,84 +501,15 @@ public class KeyboardSwitcher implements SharedPreferences.OnSharedPreferenceCha return mKeyboardView != null && mKeyboardView.hasDistinctMultitouch(); } - private static boolean isSpaceCharacter(int c) { - return c == Keyboard.CODE_SPACE || c == Keyboard.CODE_ENTER; - } - - private boolean isLayoutSwitchBackCharacter(int c) { - if (TextUtils.isEmpty(mLayoutSwitchBackSymbols)) return false; - if (mLayoutSwitchBackSymbols.indexOf(c) >= 0) return true; - return false; - } - /** * Updates state machine to figure out when to automatically snap back to the previous mode. */ - public void onKey(int code) { + public void onCodeInput(int code) { if (DEBUG_STATE) { - Log.d(TAG, "onKey: code=" + code + " switchState=" + mSwitchState - + " isSinglePointer=" + isSinglePointer()); - } - switch (mSwitchState) { - case SWITCH_STATE_MOMENTARY_ALPHA_AND_SYMBOL: - // Only distinct multi touch devices can be in this state. - // On non-distinct multi touch devices, mode change key is handled by - // {@link LatinIME#onCodeInput}, not by {@link LatinIME#onPress} and - // {@link LatinIME#onRelease}. So, on such devices, {@link #mSwitchState} starts - // from {@link #SWITCH_STATE_SYMBOL_BEGIN}, or {@link #SWITCH_STATE_ALPHA}, not from - // {@link #SWITCH_STATE_MOMENTARY}. - if (code == Keyboard.CODE_SWITCH_ALPHA_SYMBOL) { - // Detected only the mode change key has been pressed, and then released. - if (mCurrentId.equals(mMainKeyboardId)) { - mSwitchState = SWITCH_STATE_ALPHA; - } else { - mSwitchState = SWITCH_STATE_SYMBOL_BEGIN; - } - } else if (isSinglePointer()) { - // Snap back to the previous keyboard mode if the user pressed the mode change key - // and slid to other key, then released the finger. - // If the user cancels the sliding input, snapping back to the previous keyboard - // mode is handled by {@link #onCancelInput}. - toggleAlphabetAndSymbols(); - } else { - // Chording input is being started. The keyboard mode will be snapped back to the - // previous mode in {@link onReleaseSymbol} when the mode change key is released. - mSwitchState = SWITCH_STATE_CHORDING_ALPHA; - } - break; - case SWITCH_STATE_MOMENTARY_SYMBOL_AND_MORE: - if (code == Keyboard.CODE_SHIFT) { - // Detected only the shift key has been pressed on symbol layout, and then released. - mSwitchState = SWITCH_STATE_SYMBOL_BEGIN; - } else if (isSinglePointer()) { - // Snap back to the previous keyboard mode if the user pressed the shift key on - // symbol mode and slid to other key, then released the finger. - toggleShiftInSymbols(); - mSwitchState = SWITCH_STATE_SYMBOL; - } else { - // Chording input is being started. The keyboard mode will be snapped back to the - // previous mode in {@link onReleaseShift} when the shift key is released. - mSwitchState = SWITCH_STATE_CHORDING_SYMBOL; - } - break; - case SWITCH_STATE_SYMBOL_BEGIN: - if (!isSpaceCharacter(code) && code >= 0) { - mSwitchState = SWITCH_STATE_SYMBOL; - } - // Snap back to alpha keyboard mode immediately if user types a quote character. - if (isLayoutSwitchBackCharacter(code)) { - setAlphabetKeyboard(); - } - break; - case SWITCH_STATE_SYMBOL: - case SWITCH_STATE_CHORDING_SYMBOL: - // Snap back to alpha keyboard mode if user types one or more non-space/enter - // characters followed by a space/enter or a quote character. - if (isSpaceCharacter(code) || isLayoutSwitchBackCharacter(code)) { - setAlphabetKeyboard(); - } - break; + Log.d(TAG, "onCodeInput: code=" + code + " isSinglePointer=" + isSinglePointer() + + " " + mState); } + mState.onCodeInput(isAlphabetMode(), isSymbolShifted(), code, isSinglePointer()); } public LatinKeyboardView getKeyboardView() { diff --git a/java/src/com/android/inputmethod/keyboard/ProximityInfo.java b/java/src/com/android/inputmethod/keyboard/ProximityInfo.java index 6c5c3e7be..778aac3de 100644 --- a/java/src/com/android/inputmethod/keyboard/ProximityInfo.java +++ b/java/src/com/android/inputmethod/keyboard/ProximityInfo.java @@ -65,12 +65,12 @@ public class ProximityInfo { return new ProximityInfo(1, 1, 1, 1, 1, 1, Collections.<Key>emptyList(), null); } - public static ProximityInfo createSpellCheckerProximityInfo() { + public static ProximityInfo createSpellCheckerProximityInfo(final int[] proximity) { final ProximityInfo spellCheckerProximityInfo = createDummyProximityInfo(); spellCheckerProximityInfo.mNativeProximityInfo = spellCheckerProximityInfo.setProximityInfoNative( SpellCheckerProximityInfo.ROW_SIZE, - 480, 300, 10, 3, SpellCheckerProximityInfo.PROXIMITY, + 480, 300, 11, 3, proximity, 0, null, null, null, null, null, null, null, null); return spellCheckerProximityInfo; } diff --git a/java/src/com/android/inputmethod/keyboard/internal/KeyboardState.java b/java/src/com/android/inputmethod/keyboard/internal/KeyboardState.java index e56159405..95c9162ef 100644 --- a/java/src/com/android/inputmethod/keyboard/internal/KeyboardState.java +++ b/java/src/com/android/inputmethod/keyboard/internal/KeyboardState.java @@ -16,22 +16,119 @@ package com.android.inputmethod.keyboard.internal; +import android.text.TextUtils; +import android.util.Log; + +import com.android.inputmethod.keyboard.Keyboard; + // TODO: Add unit tests public class KeyboardState { + private static final String TAG = KeyboardState.class.getSimpleName(); + private static final boolean DEBUG_STATE = false; + + public interface SwitchActions { + public void setAlphabetKeyboard(); + public static final int UNSHIFT = 0; + public static final int MANUAL_SHIFT = 1; + public static final int AUTOMATIC_SHIFT = 2; + public void setShifted(int shiftMode); + public void setShiftLocked(boolean shiftLocked); + public void setSymbolsKeyboard(); + public void setSymbolsShiftedKeyboard(); + } + private KeyboardShiftState mKeyboardShiftState = new KeyboardShiftState(); - // TODO: Combine these key state objects with auto mode switch state. private ShiftKeyState mShiftKeyState = new ShiftKeyState("Shift"); private ModifierKeyState mSymbolKeyState = new ModifierKeyState("Symbol"); - public KeyboardState() { + private static final int SWITCH_STATE_ALPHA = 0; + private static final int SWITCH_STATE_SYMBOL_BEGIN = 1; + private static final int SWITCH_STATE_SYMBOL = 2; + // The following states are used only on the distinct multi-touch panel devices. + private static final int SWITCH_STATE_MOMENTARY_ALPHA_AND_SYMBOL = 3; + private static final int SWITCH_STATE_MOMENTARY_SYMBOL_AND_MORE = 4; + private static final int SWITCH_STATE_CHORDING_ALPHA = 5; + private static final int SWITCH_STATE_CHORDING_SYMBOL = 6; + private int mSwitchState = SWITCH_STATE_ALPHA; + + private String mLayoutSwitchBackSymbols; + + private final SwitchActions mSwitchActions; + + private final SavedKeyboardState mSavedKeyboardState = new SavedKeyboardState(); + private boolean mPrevMainKeyboardWasShiftLocked; + + private static class SavedKeyboardState { + public boolean mIsValid; + public boolean mIsAlphabetMode; + public boolean mIsShiftLocked; + public boolean mIsShifted; } - public void onLoadKeyboard() { + public KeyboardState(SwitchActions switchActions) { + mSwitchActions = switchActions; + } + + public void onLoadKeyboard(String layoutSwitchBackSymbols) { + mLayoutSwitchBackSymbols = layoutSwitchBackSymbols; mKeyboardShiftState.setShifted(false); mKeyboardShiftState.setShiftLocked(false); mShiftKeyState.onRelease(); mSymbolKeyState.onRelease(); + mPrevMainKeyboardWasShiftLocked = false; + onRestoreKeyboardState(); + } + + // TODO: Get rid of isAlphabetMode and isSymbolShifted arguments. + public void onSaveKeyboardState(boolean isAlphabetMode, boolean isSymbolShifted) { + final SavedKeyboardState state = mSavedKeyboardState; + state.mIsAlphabetMode = isAlphabetMode; + if (isAlphabetMode) { + state.mIsShiftLocked = isShiftLocked(); + state.mIsShifted = !state.mIsShiftLocked && isShiftedOrShiftLocked(); + } else { + state.mIsShiftLocked = false; + state.mIsShifted = isSymbolShifted; + } + state.mIsValid = true; + if (DEBUG_STATE) { + Log.d(TAG, "save: alphabet=" + state.mIsAlphabetMode + + " shiftLocked=" + state.mIsShiftLocked + " shift=" + state.mIsShifted); + } + } + + private void onRestoreKeyboardState() { + final SavedKeyboardState state = mSavedKeyboardState; + if (DEBUG_STATE) { + Log.d(TAG, "restore: valid=" + state.mIsValid + " alphabet=" + state.mIsAlphabetMode + + " shiftLocked=" + state.mIsShiftLocked + " shift=" + state.mIsShifted); + } + if (!state.mIsValid || state.mIsAlphabetMode) { + mSwitchActions.setAlphabetKeyboard(); + } else { + if (state.mIsShifted) { + mSwitchActions.setSymbolsShiftedKeyboard(); + } else { + mSwitchActions.setSymbolsKeyboard(); + } + } + + if (!state.mIsValid) return; + state.mIsValid = false; + + if (state.mIsAlphabetMode) { + mSwitchActions.setShiftLocked(state.mIsShiftLocked); + if (!state.mIsShiftLocked) { + mSwitchActions.setShifted( + state.mIsShifted ? SwitchActions.MANUAL_SHIFT : SwitchActions.UNSHIFT); + } + } + } + + // TODO: Get rid of this method + public void onSetKeyboard(boolean isAlphabetMode) { + mSwitchState = isAlphabetMode ? SWITCH_STATE_ALPHA : SWITCH_STATE_SYMBOL_BEGIN; } public boolean isShiftLocked() { @@ -73,40 +170,50 @@ public class KeyboardState { mKeyboardShiftState.setAutomaticTemporaryUpperCase(); } - // TODO: Get rid of this method - public boolean isShiftKeyIgnoring() { - return mShiftKeyState.isIgnoring(); - } - - // TODO: Get rid of this method - public boolean isShiftKeyReleasing() { - return mShiftKeyState.isReleasing(); + private void toggleAlphabetAndSymbols(boolean isAlphabetMode) { + if (isAlphabetMode) { + mSwitchActions.setSymbolsKeyboard(); + } else { + mSwitchActions.setAlphabetKeyboard(); + } } - // TODO: Get rid of this method - public boolean isShiftKeyMomentary() { - return mShiftKeyState.isMomentary(); + private void toggleShiftInSymbols(boolean isSymbolShifted) { + if (isSymbolShifted) { + mSwitchActions.setSymbolsKeyboard(); + } else { + mSwitchActions.setSymbolsShiftedKeyboard(); + } } - // TODO: Get rid of this method - public boolean isShiftKeyPressing() { - return mShiftKeyState.isPressing(); + public void onRestoreShiftLockState() { + mSwitchActions.setShiftLocked(mPrevMainKeyboardWasShiftLocked); + mPrevMainKeyboardWasShiftLocked = false; } - // TODO: Get rid of this method - public boolean isShiftKeyPressingOnShifted() { - return mShiftKeyState.isPressingOnShifted(); + public void onSaveShiftLockState() { + mPrevMainKeyboardWasShiftLocked = isShiftLocked(); } + // TODO: Remove this method. public void onReleaseCapsLock() { mShiftKeyState.onRelease(); } - public void onPressSymbol() { + // TODO: Get rid of isAlphabetMode argument. + public void onPressSymbol(boolean isAlphabetMode) { + toggleAlphabetAndSymbols(isAlphabetMode); mSymbolKeyState.onPress(); + mSwitchState = SWITCH_STATE_MOMENTARY_ALPHA_AND_SYMBOL; } - public void onReleaseSymbol() { + // TODO: Get rid of isAlphabetMode argument. + public void onReleaseSymbol(boolean isAlphabetMode) { + // 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) { + toggleAlphabetAndSymbols(isAlphabetMode); + } mSymbolKeyState.onRelease(); } @@ -115,48 +222,200 @@ public class KeyboardState { mSymbolKeyState.onOtherKeyPressed(); } - public void onUpdateShiftState(boolean isAlphabetMode) { - if (!isAlphabetMode) { + // TODO: Get rid of isAlphabetMode argument. + public void onUpdateShiftState(boolean isAlphabetMode, boolean autoCaps) { + if (isAlphabetMode) { + if (!isShiftLocked() && !mShiftKeyState.isIgnoring()) { + if (mShiftKeyState.isReleasing() && autoCaps) { + // Only when shift key is releasing, automatic temporary upper case will be set. + mSwitchActions.setShifted(SwitchActions.AUTOMATIC_SHIFT); + } else { + mSwitchActions.setShifted(mShiftKeyState.isMomentary() + ? SwitchActions.MANUAL_SHIFT : SwitchActions.UNSHIFT); + } + } + } else { // In symbol keyboard mode, we should clear shift key state because only alphabet // keyboard has shift key. mSymbolKeyState.onRelease(); } } - // TODO: Get rid of these boolean arguments. - public void onPressShift(boolean isAlphabetMode, boolean isShiftLocked, - boolean isAutomaticTemporaryUpperCase, boolean isShiftedOrShiftLocked) { + // TODO: Get rid of isAlphabetMode and isSymbolShifted arguments. + public void onPressShift(boolean isAlphabetMode, boolean isSymbolShifted) { if (isAlphabetMode) { - if (isShiftLocked) { + if (isShiftLocked()) { // Shift key is pressed while caps lock state, we will treat this state as shifted // caps lock state and mark as if shift key pressed while normal state. + mSwitchActions.setShifted(SwitchActions.MANUAL_SHIFT); mShiftKeyState.onPress(); - } else if (isAutomaticTemporaryUpperCase) { + } else if (isAutomaticTemporaryUpperCase()) { // Shift key is pressed while automatic temporary upper case, we have to move to // manual temporary upper case. + mSwitchActions.setShifted(SwitchActions.MANUAL_SHIFT); mShiftKeyState.onPress(); - } else if (isShiftedOrShiftLocked) { + } else if (isShiftedOrShiftLocked()) { // In manual upper case state, we just record shift key has been pressing while // shifted state. mShiftKeyState.onPressOnShifted(); } else { // In base layout, chording or manual temporary upper case mode is started. + mSwitchActions.setShifted(SwitchActions.MANUAL_SHIFT); mShiftKeyState.onPress(); } } else { // In symbol mode, just toggle symbol and symbol more keyboard. + toggleShiftInSymbols(isSymbolShifted); + mSwitchState = SWITCH_STATE_MOMENTARY_SYMBOL_AND_MORE; mShiftKeyState.onPress(); } } - public void onReleaseShift() { + // TODO: Get rid of isAlphabetMode and isSymbolShifted arguments. + public void onReleaseShift(boolean isAlphabetMode, boolean isSymbolShifted, + boolean withSliding) { + if (isAlphabetMode) { + final boolean isShiftLocked = isShiftLocked(); + if (mShiftKeyState.isMomentary()) { + // After chording input while normal state. + mSwitchActions.setShifted(SwitchActions.UNSHIFT); + } else if (isShiftLocked && !isShiftLockShifted() && (mShiftKeyState.isPressing() + || mShiftKeyState.isPressingOnShifted()) && !withSliding) { + // Shift has been long pressed, ignore this release. + } else if (isShiftLocked && !mShiftKeyState.isIgnoring() && !withSliding) { + // Shift has been pressed without chording while caps lock state. + mSwitchActions.setShiftLocked(false); + } else if (isShiftedOrShiftLocked() && mShiftKeyState.isPressingOnShifted() + && !withSliding) { + // Shift has been pressed without chording while shifted state. + mSwitchActions.setShifted(SwitchActions.UNSHIFT); + } else if (isManualTemporaryUpperCaseFromAuto() && mShiftKeyState.isPressing() + && !withSliding) { + // Shift has been pressed without chording while manual temporary upper case + // transited from automatic temporary upper case. + mSwitchActions.setShifted(SwitchActions.UNSHIFT); + } + } else { + // In symbol mode, snap back to the previous keyboard mode if the user chords the shift + // key and another key, then releases the shift key. + if (mSwitchState == SWITCH_STATE_CHORDING_SYMBOL) { + toggleShiftInSymbols(isSymbolShifted); + } + } mShiftKeyState.onRelease(); } + // TODO: Get rid of isAlphabetMode and isSymbolShifted arguments. + public void onCancelInput(boolean isAlphabetMode, boolean isSymbolShifted, + boolean isSinglePointer) { + // Snap back to the previous keyboard mode if the user cancels sliding input. + if (isSinglePointer) { + if (mSwitchState == SWITCH_STATE_MOMENTARY_ALPHA_AND_SYMBOL) { + toggleAlphabetAndSymbols(isAlphabetMode); + } else if (mSwitchState == SWITCH_STATE_MOMENTARY_SYMBOL_AND_MORE) { + toggleShiftInSymbols(isSymbolShifted); + } + } + } + + public boolean isInMomentarySwitchState() { + return mSwitchState == SWITCH_STATE_MOMENTARY_ALPHA_AND_SYMBOL + || mSwitchState == SWITCH_STATE_MOMENTARY_SYMBOL_AND_MORE; + } + + private static boolean isSpaceCharacter(int c) { + return c == Keyboard.CODE_SPACE || c == Keyboard.CODE_ENTER; + } + + private boolean isLayoutSwitchBackCharacter(int c) { + if (TextUtils.isEmpty(mLayoutSwitchBackSymbols)) return false; + if (mLayoutSwitchBackSymbols.indexOf(c) >= 0) return true; + return false; + } + + // TODO: Get rid of isAlphabetMode and isSymbolShifted arguments. + public void onCodeInput(boolean isAlphabetMode, boolean isSymbolShifted, int code, + boolean isSinglePointer) { + switch (mSwitchState) { + case SWITCH_STATE_MOMENTARY_ALPHA_AND_SYMBOL: + // Only distinct multi touch devices can be in this state. + // On non-distinct multi touch devices, mode change key is handled by + // {@link LatinIME#onCodeInput}, not by {@link LatinIME#onPress} and + // {@link LatinIME#onRelease}. So, on such devices, {@link #mSwitchState} starts + // from {@link #SWITCH_STATE_SYMBOL_BEGIN}, or {@link #SWITCH_STATE_ALPHA}, not from + // {@link #SWITCH_STATE_MOMENTARY}. + if (code == Keyboard.CODE_SWITCH_ALPHA_SYMBOL) { + // Detected only the mode change key has been pressed, and then released. + if (isAlphabetMode) { + mSwitchState = SWITCH_STATE_ALPHA; + } else { + mSwitchState = SWITCH_STATE_SYMBOL_BEGIN; + } + } else if (isSinglePointer) { + // Snap back to the previous keyboard mode if the user pressed the mode change key + // and slid to other key, then released the finger. + // If the user cancels the sliding input, snapping back to the previous keyboard + // mode is handled by {@link #onCancelInput}. + toggleAlphabetAndSymbols(isAlphabetMode); + } else { + // Chording input is being started. The keyboard mode will be snapped back to the + // previous mode in {@link onReleaseSymbol} when the mode change key is released. + mSwitchState = SWITCH_STATE_CHORDING_ALPHA; + } + break; + case SWITCH_STATE_MOMENTARY_SYMBOL_AND_MORE: + if (code == Keyboard.CODE_SHIFT) { + // Detected only the shift key has been pressed on symbol layout, and then released. + mSwitchState = SWITCH_STATE_SYMBOL_BEGIN; + } else if (isSinglePointer) { + // Snap back to the previous keyboard mode if the user pressed the shift key on + // symbol mode and slid to other key, then released the finger. + toggleShiftInSymbols(isSymbolShifted); + mSwitchState = SWITCH_STATE_SYMBOL; + } else { + // Chording input is being started. The keyboard mode will be snapped back to the + // previous mode in {@link onReleaseShift} when the shift key is released. + mSwitchState = SWITCH_STATE_CHORDING_SYMBOL; + } + break; + case SWITCH_STATE_SYMBOL_BEGIN: + if (!isSpaceCharacter(code) && code >= 0) { + mSwitchState = SWITCH_STATE_SYMBOL; + } + // Snap back to alpha keyboard mode immediately if user types a quote character. + if (isLayoutSwitchBackCharacter(code)) { + mSwitchActions.setAlphabetKeyboard(); + } + break; + case SWITCH_STATE_SYMBOL: + case SWITCH_STATE_CHORDING_SYMBOL: + // Snap back to alpha keyboard mode if user types one or more non-space/enter + // characters followed by a space/enter or a quote character. + if (isSpaceCharacter(code) || isLayoutSwitchBackCharacter(code)) { + mSwitchActions.setAlphabetKeyboard(); + } + break; + } + } + + private static String switchStateToString(int switchState) { + switch (switchState) { + case SWITCH_STATE_ALPHA: return "ALPHA"; + case SWITCH_STATE_SYMBOL_BEGIN: return "SYMBOL-BEGIN"; + case SWITCH_STATE_SYMBOL: return "SYMBOL"; + case SWITCH_STATE_MOMENTARY_ALPHA_AND_SYMBOL: return "MOMENTARY-ALPHA-SYMBOL"; + case SWITCH_STATE_MOMENTARY_SYMBOL_AND_MORE: return "MOMENTARY-SYMBOL-MORE"; + case SWITCH_STATE_CHORDING_ALPHA: return "CHORDING-ALPHA"; + case SWITCH_STATE_CHORDING_SYMBOL: return "CHORDING-SYMBOL"; + default: return null; + } + } + @Override public String toString() { return "[keyboard=" + mKeyboardShiftState + " shift=" + mShiftKeyState - + " symbol=" + mSymbolKeyState + "]"; + + " symbol=" + mSymbolKeyState + + " switch=" + switchStateToString(mSwitchState) + "]"; } } diff --git a/java/src/com/android/inputmethod/latin/LatinIME.java b/java/src/com/android/inputmethod/latin/LatinIME.java index c2656b891..5ee69d1c4 100644 --- a/java/src/com/android/inputmethod/latin/LatinIME.java +++ b/java/src/com/android/inputmethod/latin/LatinIME.java @@ -1374,7 +1374,7 @@ public class LatinIME extends InputMethodServiceCompatWrapper implements Keyboar mExpectingUpdateSelection = true; break; } - switcher.onKey(primaryCode); + switcher.onCodeInput(primaryCode); // Reset after any single keystroke mEnteredText = null; } @@ -1390,7 +1390,7 @@ public class LatinIME extends InputMethodServiceCompatWrapper implements Keyboar ic.commitText(text, 1); ic.endBatchEdit(); mKeyboardSwitcher.updateShiftState(); - mKeyboardSwitcher.onKey(Keyboard.CODE_DUMMY); + mKeyboardSwitcher.onCodeInput(Keyboard.CODE_DUMMY); mSpaceState = SPACE_STATE_NONE; mEnteredText = text; } @@ -2396,11 +2396,8 @@ public class LatinIME extends InputMethodServiceCompatWrapper implements Keyboar // TODO: cleanup messy flags final boolean shouldAutoCorrect = mSettingsValues.mAutoCorrectEnabled && !mInputTypeNoAutoCorrect; - mCorrectionMode = (shouldAutoCorrect && mSettingsValues.mAutoCorrectEnabled) - ? Suggest.CORRECTION_FULL - : (shouldAutoCorrect ? Suggest.CORRECTION_BASIC : Suggest.CORRECTION_NONE); - mCorrectionMode = (mSettingsValues.mBigramSuggestionEnabled && shouldAutoCorrect - && mSettingsValues.mAutoCorrectEnabled) + mCorrectionMode = shouldAutoCorrect ? Suggest.CORRECTION_FULL : Suggest.CORRECTION_NONE; + mCorrectionMode = (mSettingsValues.mBigramSuggestionEnabled && shouldAutoCorrect) ? Suggest.CORRECTION_FULL_BIGRAM : mCorrectionMode; if (mSuggest != null) { mSuggest.setCorrectionMode(mCorrectionMode); diff --git a/java/src/com/android/inputmethod/latin/Suggest.java b/java/src/com/android/inputmethod/latin/Suggest.java index 2a36f8266..b618ca7ed 100644 --- a/java/src/com/android/inputmethod/latin/Suggest.java +++ b/java/src/com/android/inputmethod/latin/Suggest.java @@ -171,10 +171,6 @@ public class Suggest implements Dictionary.WordCallback { }.start(); } - public int getCorrectionMode() { - return mCorrectionMode; - } - public void setCorrectionMode(int mode) { mCorrectionMode = mode; } diff --git a/java/src/com/android/inputmethod/latin/SuggestionsView.java b/java/src/com/android/inputmethod/latin/SuggestionsView.java index 8c49ba0cf..10f5ec9db 100644 --- a/java/src/com/android/inputmethod/latin/SuggestionsView.java +++ b/java/src/com/android/inputmethod/latin/SuggestionsView.java @@ -672,34 +672,8 @@ public class SuggestionsView extends RelativeLayout implements OnClickListener, mPreviewPopup.dismiss(); } - private void showPreview(View view, CharSequence word) { - if (TextUtils.isEmpty(word)) - return; - - final TextView previewText = mPreviewText; - previewText.setTextColor(mParams.mColorTypedWord); - previewText.setText(word); - previewText.measure( - ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT); - final int[] offsetInWindow = new int[2]; - view.getLocationInWindow(offsetInWindow); - final int posX = offsetInWindow[0]; - final int posY = offsetInWindow[1] - previewText.getMeasuredHeight(); - final PopupWindow previewPopup = mPreviewPopup; - if (previewPopup.isShowing()) { - previewPopup.update(posX, posY, previewPopup.getWidth(), previewPopup.getHeight()); - } else { - previewPopup.showAtLocation(this, Gravity.NO_GRAVITY, posX, posY); - } - previewText.setVisibility(VISIBLE); - mHandler.postHidePreview(); - } - private void addToDictionary(CharSequence word) { - if (mListener.addWordToDictionary(word.toString())) { - final CharSequence message = getContext().getString(R.string.added_word, word); - showPreview(mParams.mWordToSaveView, message); - } + mListener.addWordToDictionary(word.toString()); } private final KeyboardActionListener mMoreSuggestionsListener = diff --git a/java/src/com/android/inputmethod/latin/UserDictionary.java b/java/src/com/android/inputmethod/latin/UserDictionary.java index 3e53bb0a3..8c28324d8 100644 --- a/java/src/com/android/inputmethod/latin/UserDictionary.java +++ b/java/src/com/android/inputmethod/latin/UserDictionary.java @@ -20,6 +20,7 @@ import android.content.ContentProviderClient; import android.content.ContentResolver; import android.content.ContentValues; import android.content.Context; +import android.content.Intent; import android.database.ContentObserver; import android.database.Cursor; import android.net.Uri; @@ -38,11 +39,9 @@ public class UserDictionary extends ExpandableDictionary { Words.FREQUENCY, }; - private static final String[] PROJECTION_ADD = { - Words._ID, - Words.FREQUENCY, - Words.LOCALE, - }; + // This is not exported by the framework so we pretty much have to write it here verbatim + private static final String ACTION_USER_DICTIONARY_INSERT = + "com.android.settings.USER_DICTIONARY_INSERT"; private ContentObserver mObserver; final private String mLocale; @@ -164,54 +163,19 @@ public class UserDictionary extends ExpandableDictionary { public synchronized void addWord(final String word, final int frequency) { // Force load the dictionary here synchronously if (getRequiresReload()) loadDictionaryAsync(); + // TODO: do something for the UI. With the following, any sufficiently long word will + // look like it will go to the user dictionary but it won't. // Safeguard against adding long words. Can cause stack overflow. if (word.length() >= getMaxWordLength()) return; super.addWord(word, frequency); - // Update the user dictionary provider - final ContentValues values = new ContentValues(5); - values.put(Words.WORD, word); - values.put(Words.FREQUENCY, frequency); - values.put(Words.LOCALE, mLocale); - values.put(Words.APP_ID, 0); - - final ContentResolver contentResolver = getContext().getContentResolver(); - final ContentProviderClient client = - contentResolver.acquireContentProviderClient(Words.CONTENT_URI); - if (null == client) return; - new Thread("addWord") { - @Override - public void run() { - Cursor cursor = null; - try { - cursor = client.query(Words.CONTENT_URI, PROJECTION_ADD, - "word=? and ((locale IS NULL) or (locale=?))", - new String[] { word, mLocale }, null); - if (cursor != null && cursor.moveToFirst()) { - final String locale = cursor.getString(cursor.getColumnIndex(Words.LOCALE)); - // If locale is null, we will not override the entry. - if (locale != null && locale.equals(mLocale.toString())) { - final long id = cursor.getLong(cursor.getColumnIndex(Words._ID)); - final Uri uri = - Uri.withAppendedPath(Words.CONTENT_URI, Long.toString(id)); - // Update the entry with new frequency value. - client.update(uri, values, null, null); - } - } else { - // Insert new entry. - client.insert(Words.CONTENT_URI, values); - } - } catch (RemoteException e) { - // If we come here, the activity is already about to be killed, and we - // have no means of contacting the content provider any more. - // See ContentResolver#insert, inside the catch(){} - } finally { - if (null != cursor) cursor.close(); - client.release(); - } - } - }.start(); + // TODO: Add an argument to the intent to specify the frequency. + Intent intent = new Intent(ACTION_USER_DICTIONARY_INSERT); + intent.putExtra(Words.WORD, word); + intent.putExtra(Words.LOCALE, mLocale); + intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); + getContext().startActivity(intent); // In case the above does a synchronous callback of the change observer setRequiresReload(false); diff --git a/java/src/com/android/inputmethod/latin/spellcheck/AndroidSpellCheckerService.java b/java/src/com/android/inputmethod/latin/spellcheck/AndroidSpellCheckerService.java index a89ef001e..8b6ecbef9 100644 --- a/java/src/com/android/inputmethod/latin/spellcheck/AndroidSpellCheckerService.java +++ b/java/src/com/android/inputmethod/latin/spellcheck/AndroidSpellCheckerService.java @@ -99,6 +99,25 @@ public class AndroidSpellCheckerService extends SpellCheckerService private final HashSet<WeakReference<DictionaryCollection>> mDictionaryCollectionsList = new HashSet<WeakReference<DictionaryCollection>>(); + public static final int SCRIPT_LATIN = 0; + public static final int SCRIPT_CYRILLIC = 1; + private static final TreeMap<String, Integer> mLanguageToScript; + static { + // List of the supported languages and their associated script. We won't check + // words written in another script than the selected script, because we know we + // don't have those in our dictionary so we will underline everything and we + // will never have any suggestions, so it makes no sense checking them. + mLanguageToScript = new TreeMap<String, Integer>(); + mLanguageToScript.put("en", SCRIPT_LATIN); + mLanguageToScript.put("fr", SCRIPT_LATIN); + mLanguageToScript.put("de", SCRIPT_LATIN); + mLanguageToScript.put("nl", SCRIPT_LATIN); + mLanguageToScript.put("cs", SCRIPT_LATIN); + mLanguageToScript.put("es", SCRIPT_LATIN); + mLanguageToScript.put("it", SCRIPT_LATIN); + mLanguageToScript.put("ru", SCRIPT_CYRILLIC); + } + @Override public void onCreate() { super.onCreate(); mSuggestionThreshold = @@ -110,6 +129,15 @@ public class AndroidSpellCheckerService extends SpellCheckerService onSharedPreferenceChanged(prefs, PREF_USE_CONTACTS_KEY); } + private static int getScriptFromLocale(final Locale locale) { + final Integer script = mLanguageToScript.get(locale.getLanguage()); + if (null == script) { + throw new RuntimeException("We have been called with an unsupported language: \"" + + locale.getLanguage() + "\". Framework bug?"); + } + return script; + } + @Override public void onSharedPreferenceChanged(final SharedPreferences prefs, final String key) { if (!PREF_USE_CONTACTS_KEY.equals(key)) return; @@ -363,7 +391,9 @@ public class AndroidSpellCheckerService extends SpellCheckerService } public DictAndProximity createDictAndProximity(final Locale locale) { - final ProximityInfo proximityInfo = ProximityInfo.createSpellCheckerProximityInfo(); + final int script = getScriptFromLocale(locale); + final ProximityInfo proximityInfo = ProximityInfo.createSpellCheckerProximityInfo( + SpellCheckerProximityInfo.getProximityForScript(script)); final Resources resources = getResources(); final int fallbackResourceId = Utils.getMainDictionaryResourceId(resources); final DictionaryCollection dictionaryCollection = @@ -415,25 +445,6 @@ public class AndroidSpellCheckerService extends SpellCheckerService } private static class AndroidSpellCheckerSession extends Session { - private static final int SCRIPT_LATIN = 0; - private static final int SCRIPT_CYRILLIC = 1; - private static final TreeMap<String, Integer> mLanguageToScript; - static { - // List of the supported languages and their associated script. We won't check - // words written in another script than the selected script, because we know we - // don't have those in our dictionary so we will underline everything and we - // will never have any suggestions, so it makes no sense checking them. - mLanguageToScript = new TreeMap<String, Integer>(); - mLanguageToScript.put("en", SCRIPT_LATIN); - mLanguageToScript.put("fr", SCRIPT_LATIN); - mLanguageToScript.put("de", SCRIPT_LATIN); - mLanguageToScript.put("nl", SCRIPT_LATIN); - mLanguageToScript.put("cs", SCRIPT_LATIN); - mLanguageToScript.put("es", SCRIPT_LATIN); - mLanguageToScript.put("it", SCRIPT_LATIN); - mLanguageToScript.put("ru", SCRIPT_CYRILLIC); - } - // Immutable, but need the locale which is not available in the constructor yet private DictionaryPool mDictionaryPool; // Likewise @@ -452,12 +463,7 @@ public class AndroidSpellCheckerService extends SpellCheckerService final String localeString = getLocale(); mDictionaryPool = mService.getDictionaryPool(localeString); mLocale = LocaleUtils.constructLocaleFromString(localeString); - final Integer script = mLanguageToScript.get(mLocale.getLanguage()); - if (null == script) { - throw new RuntimeException("We have been called with an unsupported language: \"" - + mLocale.getLanguage() + "\". Framework bug?"); - } - mScript = script; + mScript = getScriptFromLocale(mLocale); } /* @@ -565,12 +571,17 @@ public class AndroidSpellCheckerService extends SpellCheckerService final int length = text.length(); for (int i = 0; i < length; ++i) { final int character = text.codePointAt(i); - final int proximityIndex = SpellCheckerProximityInfo.getIndexOf(character); + final int proximityIndex = + SpellCheckerProximityInfo.getIndexOfCodeForScript(character, mScript); final int[] proximities; if (-1 == proximityIndex) { proximities = new int[] { character }; } else { - proximities = Arrays.copyOfRange(SpellCheckerProximityInfo.PROXIMITY, + // TODO: an initial examination seems to reveal this is actually used + // read-only. It should be possible to compute the arrays statically once + // and skip doing a copy each time here. + proximities = Arrays.copyOfRange( + SpellCheckerProximityInfo.getProximityForScript(mScript), proximityIndex, proximityIndex + SpellCheckerProximityInfo.ROW_SIZE); } diff --git a/java/src/com/android/inputmethod/latin/spellcheck/SpellCheckerProximityInfo.java b/java/src/com/android/inputmethod/latin/spellcheck/SpellCheckerProximityInfo.java index d5b04b27c..9a2bebfdf 100644 --- a/java/src/com/android/inputmethod/latin/spellcheck/SpellCheckerProximityInfo.java +++ b/java/src/com/android/inputmethod/latin/spellcheck/SpellCheckerProximityInfo.java @@ -29,65 +29,150 @@ public class SpellCheckerProximityInfo { // as the size of the passed array afterwards so they can't be different. final public static int ROW_SIZE = ProximityInfo.MAX_PROXIMITY_CHARS_SIZE; - // This is a map from the code point to the index in the PROXIMITY array. - // At the time the native code to read the binary dictionary needs the proximity info be passed - // as a flat array spaced by MAX_PROXIMITY_CHARS_SIZE columns, one for each input character. - // Since we need to build such an array, we want to be able to search in our big proximity data - // quickly by character, and a map is probably the best way to do this. - final private static TreeMap<Integer, Integer> INDICES = new TreeMap<Integer, Integer>(); + // Helper methods + final protected static void buildProximityIndices(final int[] proximity, + final TreeMap<Integer, Integer> indices) { + for (int i = 0; i < proximity.length; i += ROW_SIZE) { + if (NUL != proximity[i]) indices.put(proximity[i], i); + } + } + final protected static int computeIndex(final int characterCode, + final TreeMap<Integer, Integer> indices) { + final Integer result = indices.get(characterCode); + if (null == result) return -1; + return result; + } - // The proximity here is the union of - // - the proximity for a QWERTY keyboard. - // - the proximity for an AZERTY keyboard. - // - the proximity for a QWERTZ keyboard. - // ...plus, add all characters in the ('a', 'e', 'i', 'o', 'u') set to each other. - // - // The reasoning behind this construction is, almost any alphabetic text we may want - // to spell check has been entered with one of the keyboards above. Also, specifically - // to English, many spelling errors consist of the last vowel of the word being wrong - // because in English vowels tend to merge with each other in pronunciation. - final public static int[] PROXIMITY = { - 'q', 'w', 's', 'a', 'z', NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, - 'w', 'q', 'a', 's', 'd', 'e', 'x', NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, - 'e', 'w', 's', 'd', 'f', 'r', 'a', 'i', 'o', 'u', NUL, NUL, NUL, NUL, NUL, NUL, - 'r', 'e', 'd', 'f', 'g', 't', NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, - 't', 'r', 'f', 'g', 'h', 'y', NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, - 'y', 't', 'g', 'h', 'j', 'u', 'a', 's', 'd', 'x', NUL, NUL, NUL, NUL, NUL, NUL, - 'u', 'y', 'h', 'j', 'k', 'i', 'a', 'e', 'o', NUL, NUL, NUL, NUL, NUL, NUL, NUL, - 'i', 'u', 'j', 'k', 'l', 'o', 'a', 'e', NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, - 'o', 'i', 'k', 'l', 'p', 'a', 'e', 'u', NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, - 'p', 'o', 'l', NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, + static class Latin { + // This is a map from the code point to the index in the PROXIMITY array. + // At the time the native code to read the binary dictionary needs the proximity info be + // passed as a flat array spaced by MAX_PROXIMITY_CHARS_SIZE columns, one for each input + // character. + // Since we need to build such an array, we want to be able to search in our big proximity + // data quickly by character, and a map is probably the best way to do this. + final private static TreeMap<Integer, Integer> INDICES = new TreeMap<Integer, Integer>(); - 'a', 'z', 'x', 's', 'w', 'q', 'e', 'i', 'o', 'u', NUL, NUL, NUL, NUL, NUL, NUL, - 's', 'q', 'a', 'z', 'x', 'c', 'd', 'e', 'w', NUL, NUL, NUL, NUL, NUL, NUL, NUL, - 'd', 'w', 's', 'x', 'c', 'v', 'f', 'r', 'e', 'w', NUL, NUL, NUL, NUL, NUL, NUL, - 'f', 'e', 'd', 'c', 'v', 'b', 'g', 't', 'r', NUL, NUL, NUL, NUL, NUL, NUL, NUL, - 'g', 'r', 'f', 'v', 'b', 'n', 'h', 'y', 't', NUL, NUL, NUL, NUL, NUL, NUL, NUL, - 'h', 't', 'g', 'b', 'n', 'm', 'j', 'u', 'y', NUL, NUL, NUL, NUL, NUL, NUL, NUL, - 'j', 'y', 'h', 'n', 'm', 'k', 'i', 'u', NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, - 'k', 'u', 'j', 'm', 'l', 'o', 'i', NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, - 'l', 'i', 'k', 'p', 'o', NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, - NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, + // The proximity here is the union of + // - the proximity for a QWERTY keyboard. + // - the proximity for an AZERTY keyboard. + // - the proximity for a QWERTZ keyboard. + // ...plus, add all characters in the ('a', 'e', 'i', 'o', 'u') set to each other. + // + // The reasoning behind this construction is, almost any alphabetic text we may want + // to spell check has been entered with one of the keyboards above. Also, specifically + // to English, many spelling errors consist of the last vowel of the word being wrong + // because in English vowels tend to merge with each other in pronunciation. + final private static int[] PROXIMITY = { + 'q', 'w', 's', 'a', 'z', NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, + 'w', 'q', 'a', 's', 'd', 'e', 'x', NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, + 'e', 'w', 's', 'd', 'f', 'r', 'a', 'i', 'o', 'u', NUL, NUL, NUL, NUL, NUL, NUL, + 'r', 'e', 'd', 'f', 'g', 't', NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, + 't', 'r', 'f', 'g', 'h', 'y', NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, + 'y', 't', 'g', 'h', 'j', 'u', 'a', 's', 'd', 'x', NUL, NUL, NUL, NUL, NUL, NUL, + 'u', 'y', 'h', 'j', 'k', 'i', 'a', 'e', 'o', NUL, NUL, NUL, NUL, NUL, NUL, NUL, + 'i', 'u', 'j', 'k', 'l', 'o', 'a', 'e', NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, + 'o', 'i', 'k', 'l', 'p', 'a', 'e', 'u', NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, + 'p', 'o', 'l', NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, + NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, - 'z', 'a', 's', 'd', 'x', 't', 'g', 'h', 'j', 'u', 'q', 'e', NUL, NUL, NUL, NUL, - 'x', 'z', 'a', 's', 'd', 'c', NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, - 'c', 'x', 's', 'd', 'f', 'v', NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, - 'v', 'c', 'd', 'f', 'g', 'b', NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, - 'b', 'v', 'f', 'g', 'h', 'n', NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, - 'n', 'b', 'g', 'h', 'j', 'm', NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, - 'm', 'n', 'h', 'j', 'k', 'l', 'o', 'p', NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, - NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, - NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, - NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, - }; - static { - for (int i = 0; i < PROXIMITY.length; i += ROW_SIZE) { - if (NUL != PROXIMITY[i]) INDICES.put(PROXIMITY[i], i); + 'a', 'z', 'x', 's', 'w', 'q', 'e', 'i', 'o', 'u', NUL, NUL, NUL, NUL, NUL, NUL, + 's', 'q', 'a', 'z', 'x', 'c', 'd', 'e', 'w', NUL, NUL, NUL, NUL, NUL, NUL, NUL, + 'd', 'w', 's', 'x', 'c', 'v', 'f', 'r', 'e', 'w', NUL, NUL, NUL, NUL, NUL, NUL, + 'f', 'e', 'd', 'c', 'v', 'b', 'g', 't', 'r', NUL, NUL, NUL, NUL, NUL, NUL, NUL, + 'g', 'r', 'f', 'v', 'b', 'n', 'h', 'y', 't', NUL, NUL, NUL, NUL, NUL, NUL, NUL, + 'h', 't', 'g', 'b', 'n', 'm', 'j', 'u', 'y', NUL, NUL, NUL, NUL, NUL, NUL, NUL, + 'j', 'y', 'h', 'n', 'm', 'k', 'i', 'u', NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, + 'k', 'u', 'j', 'm', 'l', 'o', 'i', NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, + 'l', 'i', 'k', 'p', 'o', NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, + NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, + NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, + + 'z', 'a', 's', 'd', 'x', 't', 'g', 'h', 'j', 'u', 'q', 'e', NUL, NUL, NUL, NUL, + 'x', 'z', 'a', 's', 'd', 'c', NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, + 'c', 'x', 's', 'd', 'f', 'v', NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, + 'v', 'c', 'd', 'f', 'g', 'b', NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, + 'b', 'v', 'f', 'g', 'h', 'n', NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, + 'n', 'b', 'g', 'h', 'j', 'm', NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, + 'm', 'n', 'h', 'j', 'k', 'l', 'o', 'p', NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, + NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, + NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, + NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, + }; + static { + buildProximityIndices(PROXIMITY, INDICES); + } + private static int getIndexOf(int characterCode) { + return computeIndex(characterCode, INDICES); } } - public static int getIndexOf(int characterCode) { - final Integer result = INDICES.get(characterCode); - if (null == result) return -1; - return result; + + static class Cyrillic { + final private static TreeMap<Integer, Integer> INDICES = new TreeMap<Integer, Integer>(); + final private static int[] PROXIMITY = { + // TODO: This table is solely based on the keyboard layout. Consult with Russian + // speakers on commonly misspelled words/letters. + 'й', 'ц', 'ф', 'ы', NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, + 'ц', 'й', 'ф', 'ы', 'в', 'у', NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, + 'у', 'ц', 'ы', 'в', 'а', 'к', NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, + 'к', 'у', 'в', 'а', 'п', 'е', NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, + 'е', 'к', 'а', 'п', 'р', 'н', NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, + 'н', 'е', 'п', 'р', 'о', 'г', NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, + 'г', 'н', 'р', 'о', 'л', 'ш', NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, + 'ш', 'г', 'о', 'л', 'д', 'щ', NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, + 'щ', 'ш', 'л', 'д', 'ж', 'з', NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, + 'з', 'щ', 'д', 'ж', 'э', 'х', NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, + 'х', 'з', 'ж', 'э', NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, + + 'ф', 'й', 'ц', 'ы', 'я', NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, + 'ы', 'й', 'ц', 'у', 'ф', 'в', 'я', 'ч', NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, + 'в', 'ц', 'у', 'к', 'ы', 'а', 'я', 'ч', 'с', NUL, NUL, NUL, NUL, NUL, NUL, NUL, + 'а', 'у', 'к', 'е', 'в', 'п', 'ч', 'с', 'м', NUL, NUL, NUL, NUL, NUL, NUL, NUL, + 'п', 'к', 'е', 'н', 'а', 'р', 'с', 'м', 'и', NUL, NUL, NUL, NUL, NUL, NUL, NUL, + 'р', 'е', 'н', 'г', 'п', 'о', 'м', 'и', 'т', NUL, NUL, NUL, NUL, NUL, NUL, NUL, + 'о', 'н', 'г', 'ш', 'р', 'л', 'и', 'т', 'ь', NUL, NUL, NUL, NUL, NUL, NUL, NUL, + 'л', 'г', 'ш', 'щ', 'о', 'д', 'т', 'ь', 'б', NUL, NUL, NUL, NUL, NUL, NUL, NUL, + 'д', 'ш', 'щ', 'з', 'л', 'ж', 'ь', 'б', 'ю', NUL, NUL, NUL, NUL, NUL, NUL, NUL, + 'ж', 'щ', 'з', 'х', 'д', 'э', 'б', 'ю', NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, + 'э', 'з', 'х', 'ю', NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, + + 'я', 'ф', 'ы', 'в', 'ч', NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, + 'ч', 'ы', 'в', 'а', 'я', 'с', NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, + 'с', 'в', 'а', 'п', 'ч', 'м', NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, + 'м', 'а', 'п', 'р', 'с', 'и', NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, + 'и', 'п', 'р', 'о', 'м', 'т', NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, + 'т', 'р', 'о', 'л', 'и', 'ь', NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, + 'ь', 'о', 'л', 'д', 'т', 'б', NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, + 'б', 'л', 'д', 'ж', 'ь', 'ю', NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, + 'ю', 'д', 'ж', 'э', 'б', NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, + NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, + NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, + }; + static { + buildProximityIndices(PROXIMITY, INDICES); + } + private static int getIndexOf(int characterCode) { + return computeIndex(characterCode, INDICES); + } + } + + public static int[] getProximityForScript(final int script) { + switch (script) { + case AndroidSpellCheckerService.SCRIPT_LATIN: + return Latin.PROXIMITY; + case AndroidSpellCheckerService.SCRIPT_CYRILLIC: + return Cyrillic.PROXIMITY; + default: + throw new RuntimeException("Wrong script supplied: " + script); + } + } + public static int getIndexOfCodeForScript(final int characterCode, final int script) { + switch (script) { + case AndroidSpellCheckerService.SCRIPT_LATIN: + return Latin.getIndexOf(characterCode); + case AndroidSpellCheckerService.SCRIPT_CYRILLIC: + return Cyrillic.getIndexOf(characterCode); + default: + throw new RuntimeException("Wrong script supplied: " + script); + } } } |