aboutsummaryrefslogtreecommitdiffstats
path: root/java/src
diff options
context:
space:
mode:
Diffstat (limited to 'java/src')
-rw-r--r--java/src/com/android/inputmethod/accessibility/AccessibilityUtils.java12
-rw-r--r--java/src/com/android/inputmethod/accessibility/AccessibleKeyboardViewProxy.java22
-rw-r--r--java/src/com/android/inputmethod/accessibility/KeyCodeDescriptionMapper.java21
-rw-r--r--java/src/com/android/inputmethod/compat/InputMethodManagerCompatWrapper.java3
-rw-r--r--java/src/com/android/inputmethod/deprecated/VoiceProxy.java10
-rw-r--r--java/src/com/android/inputmethod/deprecated/voice/FieldContext.java4
-rw-r--r--java/src/com/android/inputmethod/keyboard/Key.java103
-rw-r--r--java/src/com/android/inputmethod/keyboard/KeyDetector.java60
-rw-r--r--java/src/com/android/inputmethod/keyboard/Keyboard.java24
-rw-r--r--java/src/com/android/inputmethod/keyboard/KeyboardId.java16
-rw-r--r--java/src/com/android/inputmethod/keyboard/KeyboardSwitcher.java5
-rw-r--r--java/src/com/android/inputmethod/keyboard/KeyboardView.java31
-rw-r--r--java/src/com/android/inputmethod/keyboard/LatinKeyboardView.java69
-rw-r--r--java/src/com/android/inputmethod/keyboard/MoreKeysDetector.java23
-rw-r--r--java/src/com/android/inputmethod/keyboard/PointerTracker.java385
-rw-r--r--java/src/com/android/inputmethod/keyboard/ProximityInfo.java8
-rw-r--r--java/src/com/android/inputmethod/keyboard/internal/KeyStyles.java25
-rw-r--r--java/src/com/android/inputmethod/keyboard/internal/PointerTrackerQueue.java51
-rw-r--r--java/src/com/android/inputmethod/latin/AutoCorrection.java2
-rw-r--r--java/src/com/android/inputmethod/latin/BinaryDictionary.java12
-rw-r--r--java/src/com/android/inputmethod/latin/DictionaryCollection.java22
-rw-r--r--java/src/com/android/inputmethod/latin/LatinIME.java503
-rw-r--r--java/src/com/android/inputmethod/latin/Settings.java8
-rw-r--r--java/src/com/android/inputmethod/latin/Suggest.java56
-rw-r--r--java/src/com/android/inputmethod/latin/SuggestionsView.java16
-rw-r--r--java/src/com/android/inputmethod/latin/TextEntryState.java68
-rw-r--r--java/src/com/android/inputmethod/latin/UserBigramDictionary.java8
-rw-r--r--java/src/com/android/inputmethod/latin/UserDictionary.java7
-rw-r--r--java/src/com/android/inputmethod/latin/UserUnigramDictionary.java4
-rw-r--r--java/src/com/android/inputmethod/latin/Utils.java4
-rw-r--r--java/src/com/android/inputmethod/latin/WordComposer.java78
-rw-r--r--java/src/com/android/inputmethod/latin/spellcheck/AndroidSpellCheckerService.java171
32 files changed, 1090 insertions, 741 deletions
diff --git a/java/src/com/android/inputmethod/accessibility/AccessibilityUtils.java b/java/src/com/android/inputmethod/accessibility/AccessibilityUtils.java
index 4a2542dec..dcebb1c04 100644
--- a/java/src/com/android/inputmethod/accessibility/AccessibilityUtils.java
+++ b/java/src/com/android/inputmethod/accessibility/AccessibilityUtils.java
@@ -116,8 +116,8 @@ public class AccessibilityUtils {
* @return {@code true} if the device should not speak text (eg.
* non-control) characters
*/
- public boolean shouldObscureInput(EditorInfo attribute) {
- if (attribute == null)
+ public boolean shouldObscureInput(EditorInfo editorInfo) {
+ if (editorInfo == null)
return false;
// Always speak if the user is listening through headphones.
@@ -125,7 +125,7 @@ public class AccessibilityUtils {
return false;
// Don't speak if the IME is connected to a password field.
- return InputTypeCompatUtils.isPasswordInputType(attribute.inputType);
+ return InputTypeCompatUtils.isPasswordInputType(editorInfo.inputType);
}
/**
@@ -159,11 +159,11 @@ public class AccessibilityUtils {
* Handles speaking the "connect a headset to hear passwords" notification
* when connecting to a password field.
*
- * @param attribute The input connection's editor info attribute.
+ * @param editorInfo The input connection's editor info attribute.
* @param restarting Whether the connection is being restarted.
*/
- public void onStartInputViewInternal(EditorInfo attribute, boolean restarting) {
- if (shouldObscureInput(attribute)) {
+ public void onStartInputViewInternal(EditorInfo editorInfo, boolean restarting) {
+ if (shouldObscureInput(editorInfo)) {
final CharSequence text = mContext.getText(R.string.spoken_use_headphones);
speak(text);
}
diff --git a/java/src/com/android/inputmethod/accessibility/AccessibleKeyboardViewProxy.java b/java/src/com/android/inputmethod/accessibility/AccessibleKeyboardViewProxy.java
index cef82267f..4cb2f20b9 100644
--- a/java/src/com/android/inputmethod/accessibility/AccessibleKeyboardViewProxy.java
+++ b/java/src/com/android/inputmethod/accessibility/AccessibleKeyboardViewProxy.java
@@ -29,7 +29,6 @@ import android.view.inputmethod.EditorInfo;
import com.android.inputmethod.compat.AccessibilityEventCompatUtils;
import com.android.inputmethod.compat.MotionEventCompatUtils;
import com.android.inputmethod.keyboard.Key;
-import com.android.inputmethod.keyboard.KeyDetector;
import com.android.inputmethod.keyboard.LatinKeyboardView;
import com.android.inputmethod.keyboard.PointerTracker;
@@ -42,7 +41,7 @@ public class AccessibleKeyboardViewProxy {
private LatinKeyboardView mView;
private AccessibleKeyboardActionListener mListener;
- private int mLastHoverKeyIndex = KeyDetector.NOT_A_KEY;
+ private Key mLastHoverKey = null;
public static void init(InputMethodService inputMethod, SharedPreferences prefs) {
sInstance.initInternal(inputMethod, prefs);
@@ -81,7 +80,7 @@ public class AccessibleKeyboardViewProxy {
switch (event.getEventType()) {
case AccessibilityEventCompatUtils.TYPE_VIEW_HOVER_ENTER:
- final Key key = tracker.getKey(mLastHoverKeyIndex);
+ final Key key = mLastHoverKey;
if (key == null)
break;
@@ -130,12 +129,12 @@ public class AccessibleKeyboardViewProxy {
switch (event.getAction()) {
case MotionEventCompatUtils.ACTION_HOVER_ENTER:
case MotionEventCompatUtils.ACTION_HOVER_MOVE:
- final int keyIndex = tracker.getKeyIndexOn(x, y);
+ final Key key = tracker.getKeyOn(x, y);
- if (keyIndex != mLastHoverKeyIndex) {
- fireKeyHoverEvent(tracker, mLastHoverKeyIndex, false);
- mLastHoverKeyIndex = keyIndex;
- fireKeyHoverEvent(tracker, mLastHoverKeyIndex, true);
+ if (key != mLastHoverKey) {
+ fireKeyHoverEvent(tracker, mLastHoverKey, false);
+ mLastHoverKey = key;
+ fireKeyHoverEvent(tracker, mLastHoverKey, true);
}
return true;
@@ -144,7 +143,7 @@ public class AccessibleKeyboardViewProxy {
return false;
}
- private void fireKeyHoverEvent(PointerTracker tracker, int keyIndex, boolean entering) {
+ private void fireKeyHoverEvent(PointerTracker tracker, Key key, boolean entering) {
if (mListener == null) {
Log.e(TAG, "No accessible keyboard action listener set!");
return;
@@ -155,11 +154,6 @@ public class AccessibleKeyboardViewProxy {
return;
}
- if (keyIndex == KeyDetector.NOT_A_KEY)
- return;
-
- final Key key = tracker.getKey(keyIndex);
-
if (key == null)
return;
diff --git a/java/src/com/android/inputmethod/accessibility/KeyCodeDescriptionMapper.java b/java/src/com/android/inputmethod/accessibility/KeyCodeDescriptionMapper.java
index 7302830d4..e01262c20 100644
--- a/java/src/com/android/inputmethod/accessibility/KeyCodeDescriptionMapper.java
+++ b/java/src/com/android/inputmethod/accessibility/KeyCodeDescriptionMapper.java
@@ -136,16 +136,14 @@ public class KeyCodeDescriptionMapper {
if (!TextUtils.isEmpty(key.mLabel)) {
final String label = key.mLabel.toString().trim();
+ // First, attempt to map the label to a pre-defined description.
if (mKeyLabelMap.containsKey(label)) {
return context.getString(mKeyLabelMap.get(label));
- } else if (label.length() == 1
- || (keyboard.isManualTemporaryUpperCase() && !TextUtils
- .isEmpty(key.mHintLabel))) {
- return getDescriptionForKeyCode(context, keyboard, key, shouldObscure);
- } else {
- return label;
}
- } else if (key.mCode != Keyboard.CODE_DUMMY) {
+ }
+
+ // Just attempt to speak the description.
+ if (key.mCode != Keyboard.CODE_DUMMY) {
return getDescriptionForKeyCode(context, keyboard, key, shouldObscure);
}
@@ -187,11 +185,14 @@ public class KeyCodeDescriptionMapper {
* @return the key code for the specified key
*/
private int getCorrectKeyCode(Keyboard keyboard, Key key) {
- if (keyboard.isManualTemporaryUpperCase() && !TextUtils.isEmpty(key.mHintLabel)) {
+ // If keyboard is in manual temporary upper case state and key has
+ // manual temporary uppercase letter as key hint letter, alternate
+ // character code should be sent.
+ if (keyboard.isManualTemporaryUpperCase() && key.hasUppercaseLetter()
+ && !TextUtils.isEmpty(key.mHintLabel)) {
return key.mHintLabel.charAt(0);
- } else {
- return key.mCode;
}
+ return key.mCode;
}
/**
diff --git a/java/src/com/android/inputmethod/compat/InputMethodManagerCompatWrapper.java b/java/src/com/android/inputmethod/compat/InputMethodManagerCompatWrapper.java
index 51dc4cd37..0e5f8c80a 100644
--- a/java/src/com/android/inputmethod/compat/InputMethodManagerCompatWrapper.java
+++ b/java/src/com/android/inputmethod/compat/InputMethodManagerCompatWrapper.java
@@ -153,8 +153,7 @@ public class InputMethodManagerCompatWrapper {
return Utils.getInputMethodInfo(this, mLatinImePackageName);
}
- @SuppressWarnings("unused")
- private InputMethodSubtypeCompatWrapper getLastResortSubtype(String mode) {
+ private static InputMethodSubtypeCompatWrapper getLastResortSubtype(String mode) {
if (VOICE_MODE.equals(mode) && !FORCE_ENABLE_VOICE_EVEN_WITH_NO_VOICE_SUBTYPES)
return null;
Locale inputLocale = SubtypeSwitcher.getInstance().getInputLocale();
diff --git a/java/src/com/android/inputmethod/deprecated/VoiceProxy.java b/java/src/com/android/inputmethod/deprecated/VoiceProxy.java
index 3f8c2ef8f..a013ebca9 100644
--- a/java/src/com/android/inputmethod/deprecated/VoiceProxy.java
+++ b/java/src/com/android/inputmethod/deprecated/VoiceProxy.java
@@ -695,12 +695,12 @@ public class VoiceProxy implements VoiceInput.UiListener {
&& !mVoiceInput.isBlacklistedField(fieldContext);
}
- private boolean shouldShowVoiceButton(FieldContext fieldContext, EditorInfo attribute) {
+ private boolean shouldShowVoiceButton(FieldContext fieldContext, EditorInfo editorInfo) {
@SuppressWarnings("deprecation")
final boolean noMic = Utils.inPrivateImeOptions(null,
- LatinIME.IME_OPTION_NO_MICROPHONE_COMPAT, attribute)
+ LatinIME.IME_OPTION_NO_MICROPHONE_COMPAT, editorInfo)
|| Utils.inPrivateImeOptions(mService.getPackageName(),
- LatinIME.IME_OPTION_NO_MICROPHONE, attribute);
+ LatinIME.IME_OPTION_NO_MICROPHONE, editorInfo);
return ENABLE_VOICE_BUTTON && fieldCanDoVoice(fieldContext) && !noMic
&& SpeechRecognizer.isRecognitionAvailable(mService);
}
@@ -709,7 +709,7 @@ public class VoiceProxy implements VoiceInput.UiListener {
return SpeechRecognizer.isRecognitionAvailable(context);
}
- public void loadSettings(EditorInfo attribute, SharedPreferences sp) {
+ public void loadSettings(EditorInfo editorInfo, SharedPreferences sp) {
if (!VOICE_INSTALLED) {
return;
}
@@ -723,7 +723,7 @@ public class VoiceProxy implements VoiceInput.UiListener {
final String voiceMode = sp.getString(PREF_VOICE_MODE,
mService.getString(R.string.voice_mode_main));
mVoiceButtonEnabled = !voiceMode.equals(mService.getString(R.string.voice_mode_off))
- && shouldShowVoiceButton(makeFieldContext(), attribute);
+ && shouldShowVoiceButton(makeFieldContext(), editorInfo);
mVoiceButtonOnPrimary = voiceMode.equals(mService.getString(R.string.voice_mode_main));
}
diff --git a/java/src/com/android/inputmethod/deprecated/voice/FieldContext.java b/java/src/com/android/inputmethod/deprecated/voice/FieldContext.java
index 3c79cc218..fd2cf3d25 100644
--- a/java/src/com/android/inputmethod/deprecated/voice/FieldContext.java
+++ b/java/src/com/android/inputmethod/deprecated/voice/FieldContext.java
@@ -43,10 +43,10 @@ public class FieldContext {
Bundle mFieldInfo;
- public FieldContext(InputConnection conn, EditorInfo info,
+ public FieldContext(InputConnection conn, EditorInfo editorInfo,
String selectedLanguage, String[] enabledLanguages) {
mFieldInfo = new Bundle();
- addEditorInfoToBundle(info, mFieldInfo);
+ addEditorInfoToBundle(editorInfo, mFieldInfo);
addInputConnectionToBundle(conn, mFieldInfo);
addLanguageInfoToBundle(selectedLanguage, enabledLanguages, mFieldInfo);
if (DBG) Log.i("FieldContext", "Bundle = " + mFieldInfo.toString());
diff --git a/java/src/com/android/inputmethod/keyboard/Key.java b/java/src/com/android/inputmethod/keyboard/Key.java
index f1ae0b313..b2b68f0ad 100644
--- a/java/src/com/android/inputmethod/keyboard/Key.java
+++ b/java/src/com/android/inputmethod/keyboard/Key.java
@@ -46,27 +46,28 @@ public class Key {
* The key code (unicode or custom code) that this key generates.
*/
public final int mCode;
+ public final int mAltCode;
/** Label to display */
public final CharSequence mLabel;
/** Hint label to display on the key in conjunction with the label */
public final CharSequence mHintLabel;
- /** Option of the label */
- private final int mLabelOption;
- private static final int LABEL_OPTION_ALIGN_LEFT = 0x01;
- private static final int LABEL_OPTION_ALIGN_RIGHT = 0x02;
- private static final int LABEL_OPTION_ALIGN_LEFT_OF_CENTER = 0x08;
- private static final int LABEL_OPTION_LARGE_LETTER = 0x10;
- private static final int LABEL_OPTION_FONT_NORMAL = 0x20;
- private static final int LABEL_OPTION_FONT_MONO_SPACE = 0x40;
- private static final int LABEL_OPTION_FOLLOW_KEY_LETTER_RATIO = 0x80;
- private static final int LABEL_OPTION_FOLLOW_KEY_HINT_LABEL_RATIO = 0x100;
- private static final int LABEL_OPTION_HAS_POPUP_HINT = 0x200;
- private static final int LABEL_OPTION_HAS_UPPERCASE_LETTER = 0x400;
- private static final int LABEL_OPTION_HAS_HINT_LABEL = 0x800;
- private static final int LABEL_OPTION_WITH_ICON_LEFT = 0x1000;
- private static final int LABEL_OPTION_WITH_ICON_RIGHT = 0x2000;
- private static final int LABEL_OPTION_AUTO_X_SCALE = 0x4000;
+ /** Flags of the label */
+ private final int mLabelFlags;
+ private static final int LABEL_FLAGS_ALIGN_LEFT = 0x01;
+ private static final int LABEL_FLAGS_ALIGN_RIGHT = 0x02;
+ private static final int LABEL_FLAGS_ALIGN_LEFT_OF_CENTER = 0x08;
+ private static final int LABEL_FLAGS_LARGE_LETTER = 0x10;
+ private static final int LABEL_FLAGS_FONT_NORMAL = 0x20;
+ private static final int LABEL_FLAGS_FONT_MONO_SPACE = 0x40;
+ private static final int LABEL_FLAGS_FOLLOW_KEY_LETTER_RATIO = 0x80;
+ private static final int LABEL_FLAGS_FOLLOW_KEY_HINT_LABEL_RATIO = 0x100;
+ private static final int LABEL_FLAGS_HAS_POPUP_HINT = 0x200;
+ private static final int LABEL_FLAGS_HAS_UPPERCASE_LETTER = 0x400;
+ private static final int LABEL_FLAGS_HAS_HINT_LABEL = 0x800;
+ private static final int LABEL_FLAGS_WITH_ICON_LEFT = 0x1000;
+ private static final int LABEL_FLAGS_WITH_ICON_RIGHT = 0x2000;
+ private static final int LABEL_FLAGS_AUTO_X_SCALE = 0x4000;
/** Icon to display instead of a label. Icon takes precedence over a label */
private Drawable mIcon;
@@ -105,8 +106,10 @@ public class Key {
public static final int BACKGROUND_TYPE_ACTION = 2;
public static final int BACKGROUND_TYPE_STICKY = 3;
- /** Whether this key repeats itself when held down */
- public final boolean mRepeatable;
+ private final int mActionFlags;
+ private static final int ACTION_FLAGS_IS_REPEATABLE = 0x01;
+ private static final int ACTION_FLAGS_NO_KEY_PREVIEW = 0x02;
+ private static final int ACTION_FLAGS_ALT_CODE_WHILE_TYPING = 0x04;
/** The current pressed state of this key */
private boolean mPressed;
@@ -181,14 +184,15 @@ public class Key {
mVisualInsetsLeft = mVisualInsetsRight = 0;
mWidth = width - mHorizontalGap;
mHintLabel = hintLabel;
- mLabelOption = 0;
+ mLabelFlags = 0;
mBackgroundType = BACKGROUND_TYPE_NORMAL;
- mRepeatable = false;
+ mActionFlags = 0;
mMoreKeys = null;
mMaxMoreKeysColumn = 0;
mLabel = label;
mOutputText = outputText;
mCode = code;
+ mAltCode = Keyboard.CODE_DUMMY;
mIcon = icon;
// Horizontal gap is divided equally to both sides of the key.
mX = x + mHorizontalGap / 2;
@@ -223,7 +227,7 @@ public class Key {
if (style == null)
throw new ParseException("Unknown key style: " + styleName, parser);
} else {
- style = keyStyles.getEmptyKeyStyle();
+ style = KeyStyles.getEmptyKeyStyle();
}
final float keyXPos = row.getKeyX(keyAttr);
@@ -254,8 +258,7 @@ public class Key {
mBackgroundType = style.getInt(keyAttr,
R.styleable.Keyboard_Key_backgroundType, BACKGROUND_TYPE_NORMAL);
- mRepeatable = style.getBoolean(keyAttr, R.styleable.Keyboard_Key_isRepeatable, false);
- mEnabled = style.getBoolean(keyAttr, R.styleable.Keyboard_Key_enabled, true);
+ mActionFlags = style.getFlag(keyAttr, R.styleable.Keyboard_Key_keyActionFlags, 0);
final KeyboardIconsSet iconsSet = params.mIconsSet;
mVisualInsetsLeft = (int) KeyboardBuilder.getDimensionOrFraction(keyAttr,
@@ -275,7 +278,7 @@ public class Key {
mHintLabel = style.getText(keyAttr, R.styleable.Keyboard_Key_keyHintLabel);
mLabel = style.getText(keyAttr, R.styleable.Keyboard_Key_keyLabel);
- mLabelOption = style.getFlag(keyAttr, R.styleable.Keyboard_Key_keyLabelOption, 0);
+ mLabelFlags = style.getFlag(keyAttr, R.styleable.Keyboard_Key_keyLabelFlags, 0);
mOutputText = style.getText(keyAttr, R.styleable.Keyboard_Key_keyOutputText);
// Choose the first letter of the label as primary code if not
// specified.
@@ -289,6 +292,8 @@ public class Key {
} else {
mCode = Keyboard.CODE_DUMMY;
}
+ mAltCode = style.getInt(keyAttr,
+ R.styleable.Keyboard_Key_altCode, Keyboard.CODE_DUMMY);
keyAttr.recycle();
}
@@ -317,11 +322,31 @@ public class Key {
return false;
}
+ public boolean isShift() {
+ return mCode == Keyboard.CODE_SHIFT;
+ }
+
+ public boolean isModifier() {
+ return mCode == Keyboard.CODE_SHIFT || mCode == Keyboard.CODE_SWITCH_ALPHA_SYMBOL;
+ }
+
+ public boolean isRepeatable() {
+ return (mActionFlags & ACTION_FLAGS_IS_REPEATABLE) != 0;
+ }
+
+ public boolean noKeyPreview() {
+ return (mActionFlags & ACTION_FLAGS_NO_KEY_PREVIEW) != 0;
+ }
+
+ public boolean altCodeWhileTyping() {
+ return (mActionFlags & ACTION_FLAGS_ALT_CODE_WHILE_TYPING) != 0;
+ }
+
public Typeface selectTypeface(Typeface defaultTypeface) {
// TODO: Handle "bold" here too?
- if ((mLabelOption & LABEL_OPTION_FONT_NORMAL) != 0) {
+ if ((mLabelFlags & LABEL_FLAGS_FONT_NORMAL) != 0) {
return Typeface.DEFAULT;
- } else if ((mLabelOption & LABEL_OPTION_FONT_MONO_SPACE) != 0) {
+ } else if ((mLabelFlags & LABEL_FLAGS_FONT_MONO_SPACE) != 0) {
return Typeface.MONOSPACE;
} else {
return defaultTypeface;
@@ -330,12 +355,12 @@ public class Key {
public int selectTextSize(int letter, int largeLetter, int label, int hintLabel) {
if (mLabel.length() > 1
- && (mLabelOption & (LABEL_OPTION_FOLLOW_KEY_LETTER_RATIO
- | LABEL_OPTION_FOLLOW_KEY_HINT_LABEL_RATIO)) == 0) {
+ && (mLabelFlags & (LABEL_FLAGS_FOLLOW_KEY_LETTER_RATIO
+ | LABEL_FLAGS_FOLLOW_KEY_HINT_LABEL_RATIO)) == 0) {
return label;
- } else if ((mLabelOption & LABEL_OPTION_FOLLOW_KEY_HINT_LABEL_RATIO) != 0) {
+ } else if ((mLabelFlags & LABEL_FLAGS_FOLLOW_KEY_HINT_LABEL_RATIO) != 0) {
return hintLabel;
- } else if ((mLabelOption & LABEL_OPTION_LARGE_LETTER) != 0) {
+ } else if ((mLabelFlags & LABEL_FLAGS_LARGE_LETTER) != 0) {
return largeLetter;
} else {
return letter;
@@ -343,19 +368,19 @@ public class Key {
}
public boolean isAlignLeft() {
- return (mLabelOption & LABEL_OPTION_ALIGN_LEFT) != 0;
+ return (mLabelFlags & LABEL_FLAGS_ALIGN_LEFT) != 0;
}
public boolean isAlignRight() {
- return (mLabelOption & LABEL_OPTION_ALIGN_RIGHT) != 0;
+ return (mLabelFlags & LABEL_FLAGS_ALIGN_RIGHT) != 0;
}
public boolean isAlignLeftOfCenter() {
- return (mLabelOption & LABEL_OPTION_ALIGN_LEFT_OF_CENTER) != 0;
+ return (mLabelFlags & LABEL_FLAGS_ALIGN_LEFT_OF_CENTER) != 0;
}
public boolean hasPopupHint() {
- return (mLabelOption & LABEL_OPTION_HAS_POPUP_HINT) != 0;
+ return (mLabelFlags & LABEL_FLAGS_HAS_POPUP_HINT) != 0;
}
public void setNeedsSpecialPopupHint(boolean needsSpecialPopupHint) {
@@ -367,23 +392,23 @@ public class Key {
}
public boolean hasUppercaseLetter() {
- return (mLabelOption & LABEL_OPTION_HAS_UPPERCASE_LETTER) != 0;
+ return (mLabelFlags & LABEL_FLAGS_HAS_UPPERCASE_LETTER) != 0;
}
public boolean hasHintLabel() {
- return (mLabelOption & LABEL_OPTION_HAS_HINT_LABEL) != 0;
+ return (mLabelFlags & LABEL_FLAGS_HAS_HINT_LABEL) != 0;
}
public boolean hasLabelWithIconLeft() {
- return (mLabelOption & LABEL_OPTION_WITH_ICON_LEFT) != 0;
+ return (mLabelFlags & LABEL_FLAGS_WITH_ICON_LEFT) != 0;
}
public boolean hasLabelWithIconRight() {
- return (mLabelOption & LABEL_OPTION_WITH_ICON_RIGHT) != 0;
+ return (mLabelFlags & LABEL_FLAGS_WITH_ICON_RIGHT) != 0;
}
public boolean needsXScale() {
- return (mLabelOption & LABEL_OPTION_AUTO_X_SCALE) != 0;
+ return (mLabelFlags & LABEL_FLAGS_AUTO_X_SCALE) != 0;
}
public Drawable getIcon() {
diff --git a/java/src/com/android/inputmethod/keyboard/KeyDetector.java b/java/src/com/android/inputmethod/keyboard/KeyDetector.java
index 3298c41cf..2a6e0a2de 100644
--- a/java/src/com/android/inputmethod/keyboard/KeyDetector.java
+++ b/java/src/com/android/inputmethod/keyboard/KeyDetector.java
@@ -26,7 +26,7 @@ public class KeyDetector {
private static final boolean DEBUG = false;
public static final int NOT_A_CODE = -1;
- public static final int NOT_A_KEY = -1;
+ private static final int NOT_A_KEY = -1;
private final int mKeyHysteresisDistanceSquared;
@@ -96,22 +96,22 @@ public class KeyDetector {
}
/**
- * Computes maximum size of the array that can contain all nearby key indices returned by
- * {@link #getKeyIndexAndNearbyCodes}.
+ * Computes maximum size of the array that can contain all nearby key codes returned by
+ * {@link #getKeyAndNearbyCodes}.
*
- * @return Returns maximum size of the array that can contain all nearby key indices returned
- * by {@link #getKeyIndexAndNearbyCodes}.
+ * @return Returns maximum size of the array that can contain all nearby key codes returned
+ * by {@link #getKeyAndNearbyCodes}.
*/
protected int getMaxNearbyKeys() {
return MAX_NEARBY_KEYS;
}
/**
- * Allocates array that can hold all key indices returned by {@link #getKeyIndexAndNearbyCodes}
+ * Allocates array that can hold all key codes returned by {@link #getKeyAndNearbyCodes}
* method. The maximum size of the array should be computed by {@link #getMaxNearbyKeys}.
*
- * @return Allocates and returns an array that can hold all key indices returned by
- * {@link #getKeyIndexAndNearbyCodes} method. All elements in the returned array are
+ * @return Allocates and returns an array that can hold all key codes returned by
+ * {@link #getKeyAndNearbyCodes} method. All elements in the returned array are
* initialized by {@link #NOT_A_CODE} value.
*/
public int[] newCodeArray() {
@@ -180,31 +180,32 @@ public class KeyDetector {
}
/**
- * Finds all possible nearby key indices around a touch event point and returns the nearest key
- * index. The algorithm to determine the nearby keys depends on the threshold set by
+ * Finds all possible nearby key codes around a touch event point and returns the nearest key.
+ * The algorithm to determine the nearby keys depends on the threshold set by
* {@link #setProximityThreshold(int)} and the mode set by
* {@link #setProximityCorrectionEnabled(boolean)}.
*
* @param x The x-coordinate of a touch point
* @param y The y-coordinate of a touch point
- * @param allCodes All nearby key code except functional key are returned in this array
- * @return The nearest key index
+ * @param allCodes All nearby key codes except functional key are returned in this array
+ * @return The nearest key
*/
- public int getKeyIndexAndNearbyCodes(int x, int y, final int[] allCodes) {
+ public Key getKeyAndNearbyCodes(int x, int y, final int[] allCodes) {
final List<Key> keys = getKeyboard().mKeys;
final int touchX = getTouchX(x);
final int touchY = getTouchY(y);
initializeNearbyKeys();
- int primaryIndex = NOT_A_KEY;
+ Key primaryKey = null;
for (final int index : mKeyboard.getNearestKeys(touchX, touchY)) {
final Key key = keys.get(index);
final boolean isOnKey = key.isOnKey(touchX, touchY);
final int distance = key.squaredDistanceToEdge(touchX, touchY);
if (isOnKey || (mProximityCorrectOn && distance < mProximityThresholdSquare)) {
final int insertedPosition = sortNearbyKeys(index, distance, isOnKey);
- if (insertedPosition == 0 && isOnKey)
- primaryIndex = index;
+ if (insertedPosition == 0 && isOnKey) {
+ primaryKey = key;
+ }
}
}
@@ -212,12 +213,31 @@ public class KeyDetector {
getNearbyKeyCodes(allCodes);
if (DEBUG) {
Log.d(TAG, "x=" + x + " y=" + y
- + " primary="
- + (primaryIndex == NOT_A_KEY ? "none" : keys.get(primaryIndex).mCode)
- + " codes=" + Arrays.toString(allCodes));
+ + " primary=" + printableCode(primaryKey)
+ + " codes=" + printableCodes(allCodes));
}
}
- return primaryIndex;
+ return primaryKey;
+ }
+
+ public static String printableCode(Key key) {
+ return key != null ? printableCode(key.mCode) : "none";
+ }
+
+ public static String printableCode(int primaryCode) {
+ if (primaryCode < 0) return String.format("%4d", primaryCode);
+ if (primaryCode < 0x100) return String.format("\\u%02x", primaryCode);
+ return String.format("\\u04x", primaryCode);
+ }
+
+ public static String printableCodes(int[] codes) {
+ final StringBuilder sb = new StringBuilder();
+ for (final int code : codes) {
+ if (code == NOT_A_CODE) break;
+ if (sb.length() > 0) sb.append(", ");
+ sb.append(code);
+ }
+ return "[" + sb + "]";
}
}
diff --git a/java/src/com/android/inputmethod/keyboard/Keyboard.java b/java/src/com/android/inputmethod/keyboard/Keyboard.java
index 4578507fc..b3b20ca21 100644
--- a/java/src/com/android/inputmethod/keyboard/Keyboard.java
+++ b/java/src/com/android/inputmethod/keyboard/Keyboard.java
@@ -24,6 +24,7 @@ import com.android.inputmethod.keyboard.internal.KeyboardParams;
import com.android.inputmethod.keyboard.internal.KeyboardShiftState;
import java.util.Collections;
+import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
@@ -65,7 +66,6 @@ public class Keyboard {
public static final int CODE_DIGIT0 = '0';
public static final int CODE_PLUS = '+';
-
/** Special keys code. These should be aligned with values/keycodes.xml */
public static final int CODE_DUMMY = 0;
public static final int CODE_SHIFT = -1;
@@ -75,7 +75,6 @@ public class Keyboard {
public static final int CODE_DELETE = -5;
public static final int CODE_SETTINGS = -6;
public static final int CODE_SHORTCUT = -7;
- public static final int CODE_HAPTIC_AND_AUDIO_FEEDBACK_ONLY = -98;
// Code value representing the code is not specified.
public static final int CODE_UNSPECIFIED = -99;
@@ -112,6 +111,7 @@ public class Keyboard {
public final Map<Key, Drawable> mUnshiftedIcons;
public final KeyboardIconsSet mIconsSet;
+ private final Map<Integer, Key> mKeyCache = new HashMap<Integer, Key>();
private final KeyboardShiftState mShiftState = new KeyboardShiftState();
private final ProximityInfo mProximityInfo;
@@ -146,6 +146,22 @@ public class Keyboard {
return mProximityInfo;
}
+ public Key getKey(int code) {
+ final Integer keyCode = code;
+ if (mKeyCache.containsKey(keyCode)) {
+ return mKeyCache.get(keyCode);
+ }
+
+ for (final Key key : mKeys) {
+ if (key.mCode == code) {
+ mKeyCache.put(keyCode, key);
+ return key;
+ }
+ }
+ mKeyCache.put(keyCode, null);
+ return null;
+ }
+
public boolean hasShiftLockKey() {
return !mShiftLockKeys.isEmpty();
}
@@ -218,10 +234,6 @@ public class Keyboard {
return mId.isPhoneKeyboard();
}
- public boolean isNumberKeyboard() {
- return mId.isNumberKeyboard();
- }
-
public CharSequence adjustLabelCase(CharSequence label) {
if (isShiftedOrShiftLocked() && !TextUtils.isEmpty(label) && label.length() < 3
&& Character.isLowerCase(label.charAt(0))) {
diff --git a/java/src/com/android/inputmethod/keyboard/KeyboardId.java b/java/src/com/android/inputmethod/keyboard/KeyboardId.java
index 2e4988fb0..840857778 100644
--- a/java/src/com/android/inputmethod/keyboard/KeyboardId.java
+++ b/java/src/com/android/inputmethod/keyboard/KeyboardId.java
@@ -58,15 +58,15 @@ public class KeyboardId {
public final int mImeAction;
public final String mXmlName;
- public final EditorInfo mAttribute;
+ public final EditorInfo mEditorInfo;
private final int mHashCode;
public KeyboardId(String xmlName, int xmlId, Locale locale, int orientation, int width,
- int mode, EditorInfo attribute, boolean hasSettingsKey, int f2KeyMode,
+ int mode, EditorInfo editorInfo, boolean hasSettingsKey, int f2KeyMode,
boolean clobberSettingsKey, boolean shortcutKeyEnabled, boolean hasShortcutKey) {
- final int inputType = (attribute != null) ? attribute.inputType : 0;
- final int imeOptions = (attribute != null) ? attribute.imeOptions : 0;
+ final int inputType = (editorInfo != null) ? editorInfo.inputType : 0;
+ final int imeOptions = (editorInfo != null) ? editorInfo.imeOptions : 0;
this.mLocale = locale;
this.mOrientation = orientation;
this.mWidth = width;
@@ -89,7 +89,7 @@ public class KeyboardId {
EditorInfo.IME_MASK_ACTION | EditorInfo.IME_FLAG_NO_ENTER_ACTION);
this.mXmlName = xmlName;
- this.mAttribute = attribute;
+ this.mEditorInfo = editorInfo;
this.mHashCode = Arrays.hashCode(new Object[] {
locale,
@@ -109,7 +109,7 @@ public class KeyboardId {
}
public KeyboardId cloneWithNewXml(String xmlName, int xmlId) {
- return new KeyboardId(xmlName, xmlId, mLocale, mOrientation, mWidth, mMode, mAttribute,
+ return new KeyboardId(xmlName, xmlId, mLocale, mOrientation, mWidth, mMode, mEditorInfo,
false, F2KEY_MODE_NONE, false, false, false);
}
@@ -133,10 +133,6 @@ public class KeyboardId {
return mXmlId == R.xml.kbd_phone_shift;
}
- public boolean isNumberKeyboard() {
- return mMode == MODE_NUMBER;
- }
-
@Override
public boolean equals(Object other) {
return other instanceof KeyboardId && equals((KeyboardId) other);
diff --git a/java/src/com/android/inputmethod/keyboard/KeyboardSwitcher.java b/java/src/com/android/inputmethod/keyboard/KeyboardSwitcher.java
index ac718fc62..4d30077e3 100644
--- a/java/src/com/android/inputmethod/keyboard/KeyboardSwitcher.java
+++ b/java/src/com/android/inputmethod/keyboard/KeyboardSwitcher.java
@@ -666,11 +666,16 @@ public class KeyboardSwitcher implements SharedPreferences.OnSharedPreferenceCha
return mKeyboardView == null ? 0 : mKeyboardView.getPointerCount();
}
+ private boolean mPrevMainKeyboardWasShiftLocked;
+
private void toggleKeyboardMode() {
if (mCurrentId.equals(mMainKeyboardId)) {
+ mPrevMainKeyboardWasShiftLocked = isShiftLocked();
setKeyboard(getKeyboard(mSymbolsKeyboardId));
} else {
setKeyboard(getKeyboard(mMainKeyboardId));
+ setShiftLocked(mPrevMainKeyboardWasShiftLocked);
+ mPrevMainKeyboardWasShiftLocked = false;
}
}
diff --git a/java/src/com/android/inputmethod/keyboard/KeyboardView.java b/java/src/com/android/inputmethod/keyboard/KeyboardView.java
index 04e672590..d2741edec 100644
--- a/java/src/com/android/inputmethod/keyboard/KeyboardView.java
+++ b/java/src/com/android/inputmethod/keyboard/KeyboardView.java
@@ -148,7 +148,7 @@ public class KeyboardView extends View implements PointerTracker.DrawingProxy {
final PointerTracker tracker = (PointerTracker) msg.obj;
switch (msg.what) {
case MSG_SHOW_KEY_PREVIEW:
- keyboardView.showKey(msg.arg1, tracker);
+ keyboardView.showKey(tracker);
break;
case MSG_DISMISS_KEY_PREVIEW:
tracker.getKeyPreviewText().setVisibility(View.INVISIBLE);
@@ -156,16 +156,15 @@ public class KeyboardView extends View implements PointerTracker.DrawingProxy {
}
}
- public void showKeyPreview(long delay, int keyIndex, PointerTracker tracker) {
+ public void showKeyPreview(long delay, PointerTracker tracker) {
removeMessages(MSG_SHOW_KEY_PREVIEW);
final KeyboardView keyboardView = getOuterInstance();
if (keyboardView == null) return;
if (tracker.getKeyPreviewText().getVisibility() == VISIBLE || delay == 0) {
// Show right away, if it's already visible and finger is moving around
- keyboardView.showKey(keyIndex, tracker);
+ keyboardView.showKey(tracker);
} else {
- sendMessageDelayed(
- obtainMessage(MSG_SHOW_KEY_PREVIEW, keyIndex, 0, tracker), delay);
+ sendMessageDelayed(obtainMessage(MSG_SHOW_KEY_PREVIEW, tracker), delay);
}
}
@@ -830,9 +829,9 @@ public class KeyboardView extends View implements PointerTracker.DrawingProxy {
}
@Override
- public void showKeyPreview(int keyIndex, PointerTracker tracker) {
+ public void showKeyPreview(PointerTracker tracker) {
if (mShowKeyPreviewPopup) {
- mDrawingHandler.showKeyPreview(mDelayBeforePreview, keyIndex, tracker);
+ mDrawingHandler.showKeyPreview(mDelayBeforePreview, tracker);
}
}
@@ -858,7 +857,7 @@ public class KeyboardView extends View implements PointerTracker.DrawingProxy {
keyPreview, FrameLayoutCompatUtils.newLayoutParam(mPreviewPlacer, 0, 0));
}
- private void showKey(final int keyIndex, PointerTracker tracker) {
+ private void showKey(PointerTracker tracker) {
final TextView previewText = tracker.getKeyPreviewText();
// If the key preview has no parent view yet, add it to the ViewGroup which can place
// key preview absolutely in SoftInputWindow.
@@ -867,8 +866,8 @@ public class KeyboardView extends View implements PointerTracker.DrawingProxy {
}
mDrawingHandler.cancelDismissKeyPreview(tracker);
- final Key key = tracker.getKey(keyIndex);
- // If keyIndex is invalid or IME is already closed, we must not show key preview.
+ final Key key = tracker.getKey();
+ // If key is invalid or IME is already closed, we must not show key preview.
// Trying to show key preview while root window is closed causes
// WindowManager.BadTokenException.
if (key == null)
@@ -906,12 +905,16 @@ public class KeyboardView extends View implements PointerTracker.DrawingProxy {
int previewX = keyDrawX - (previewWidth - keyDrawWidth) / 2 + params.mCoordinates[0];
final int previewY = key.mY - previewHeight
+ params.mCoordinates[1] + params.mPreviewOffset;
- if (previewX < 0 && params.mPreviewLeftBackground != null) {
- previewText.setBackgroundDrawable(params.mPreviewLeftBackground);
+ if (previewX < 0) {
previewX = 0;
- } else if (previewX + previewWidth > getWidth() && params.mPreviewRightBackground != null) {
- previewText.setBackgroundDrawable(params.mPreviewRightBackground);
+ if (params.mPreviewLeftBackground != null) {
+ previewText.setBackgroundDrawable(params.mPreviewLeftBackground);
+ }
+ } else if (previewX > getWidth() - previewWidth) {
previewX = getWidth() - previewWidth;
+ if (params.mPreviewRightBackground != null) {
+ previewText.setBackgroundDrawable(params.mPreviewRightBackground);
+ }
}
// Set the preview background state
diff --git a/java/src/com/android/inputmethod/keyboard/LatinKeyboardView.java b/java/src/com/android/inputmethod/keyboard/LatinKeyboardView.java
index 6ce3876b6..9ddf40119 100644
--- a/java/src/com/android/inputmethod/keyboard/LatinKeyboardView.java
+++ b/java/src/com/android/inputmethod/keyboard/LatinKeyboardView.java
@@ -21,7 +21,6 @@ import android.content.pm.PackageManager;
import android.content.res.Resources;
import android.graphics.Canvas;
import android.os.Message;
-import android.os.SystemClock;
import android.util.AttributeSet;
import android.util.Log;
import android.view.GestureDetector;
@@ -75,7 +74,7 @@ public class LatinKeyboardView extends KeyboardView implements PointerTracker.Ke
private final boolean mHasDistinctMultitouch;
private int mOldPointerCount = 1;
- private int mOldKeyIndex;
+ private Key mOldKey;
private final boolean mConfigShowMiniKeyboardAtTouchedPoint;
protected KeyDetector mKeyDetector;
@@ -90,6 +89,7 @@ public class LatinKeyboardView extends KeyboardView implements PointerTracker.Ke
private static final int MSG_REPEAT_KEY = 1;
private static final int MSG_LONGPRESS_KEY = 2;
private static final int MSG_IGNORE_DOUBLE_TAP = 3;
+ private static final int MSG_KEY_TYPED = 4;
private boolean mInKeyRepeat;
@@ -103,19 +103,19 @@ public class LatinKeyboardView extends KeyboardView implements PointerTracker.Ke
final PointerTracker tracker = (PointerTracker) msg.obj;
switch (msg.what) {
case MSG_REPEAT_KEY:
- tracker.onRepeatKey(msg.arg1);
- startKeyRepeatTimer(keyboardView.mKeyRepeatInterval, msg.arg1, tracker);
+ tracker.onRepeatKey(tracker.getKey());
+ startKeyRepeatTimer(keyboardView.mKeyRepeatInterval, tracker);
break;
case MSG_LONGPRESS_KEY:
- keyboardView.openMiniKeyboardIfRequired(msg.arg1, tracker);
+ keyboardView.openMiniKeyboardIfRequired(tracker.getKey(), tracker);
break;
}
}
@Override
- public void startKeyRepeatTimer(long delay, int keyIndex, PointerTracker tracker) {
+ public void startKeyRepeatTimer(long delay, PointerTracker tracker) {
mInKeyRepeat = true;
- sendMessageDelayed(obtainMessage(MSG_REPEAT_KEY, keyIndex, 0, tracker), delay);
+ sendMessageDelayed(obtainMessage(MSG_REPEAT_KEY, tracker), delay);
}
public void cancelKeyRepeatTimer() {
@@ -128,9 +128,9 @@ public class LatinKeyboardView extends KeyboardView implements PointerTracker.Ke
}
@Override
- public void startLongPressTimer(long delay, int keyIndex, PointerTracker tracker) {
+ public void startLongPressTimer(long delay, PointerTracker tracker) {
cancelLongPressTimer();
- sendMessageDelayed(obtainMessage(MSG_LONGPRESS_KEY, keyIndex, 0, tracker), delay);
+ sendMessageDelayed(obtainMessage(MSG_LONGPRESS_KEY, tracker), delay);
}
@Override
@@ -139,6 +139,17 @@ public class LatinKeyboardView extends KeyboardView implements PointerTracker.Ke
}
@Override
+ public void startKeyTypedTimer(long delay) {
+ removeMessages(MSG_KEY_TYPED);
+ sendMessageDelayed(obtainMessage(MSG_KEY_TYPED), delay);
+ }
+
+ @Override
+ public boolean isTyping() {
+ return hasMessages(MSG_KEY_TYPED);
+ }
+
+ @Override
public void cancelKeyTimers() {
cancelKeyRepeatTimer();
cancelLongPressTimer();
@@ -170,8 +181,9 @@ public class LatinKeyboardView extends KeyboardView implements PointerTracker.Ke
final int pointerIndex = firstDown.getActionIndex();
final int id = firstDown.getPointerId(pointerIndex);
final PointerTracker tracker = getPointerTracker(id);
+ final Key key = tracker.getKeyOn((int)firstDown.getX(), (int)firstDown.getY());
// If the first down event is on shift key.
- if (tracker.isOnShiftKey((int) firstDown.getX(), (int) firstDown.getY())) {
+ if (key != null && key.isShift()) {
mProcessingShiftDoubleTapEvent = true;
return true;
}
@@ -188,8 +200,9 @@ public class LatinKeyboardView extends KeyboardView implements PointerTracker.Ke
final int pointerIndex = secondDown.getActionIndex();
final int id = secondDown.getPointerId(pointerIndex);
final PointerTracker tracker = getPointerTracker(id);
+ final Key key = tracker.getKeyOn((int)secondDown.getX(), (int)secondDown.getY());
// If the second down event is also on shift key.
- if (tracker.isOnShiftKey((int) secondDown.getX(), (int) secondDown.getY())) {
+ if (key != null && key.isShift()) {
// Detected a double tap on shift key. If we are in the ignoring double tap
// mode, it means we have already turned off caps lock in
// {@link KeyboardSwitcher#onReleaseShift} .
@@ -264,20 +277,6 @@ public class LatinKeyboardView extends KeyboardView implements PointerTracker.Ke
return mKeyTimerHandler;
}
- @Override
- public void setKeyPreviewPopupEnabled(boolean previewEnabled, int delay) {
- final Keyboard keyboard = getKeyboard();
- if (keyboard instanceof LatinKeyboard) {
- final LatinKeyboard latinKeyboard = (LatinKeyboard)keyboard;
- if (latinKeyboard.isPhoneKeyboard() || latinKeyboard.isNumberKeyboard()) {
- // Phone and number keyboard never shows popup preview.
- super.setKeyPreviewPopupEnabled(false, delay);
- return;
- }
- }
- super.setKeyPreviewPopupEnabled(previewEnabled, delay);
- }
-
/**
* Attaches a keyboard to this view. The keyboard can be switched at any time and the
* view will re-layout itself to accommodate the keyboard.
@@ -329,7 +328,7 @@ public class LatinKeyboardView extends KeyboardView implements PointerTracker.Ke
super.cancelAllMessages();
}
- private boolean openMiniKeyboardIfRequired(int keyIndex, PointerTracker tracker) {
+ private boolean openMiniKeyboardIfRequired(Key parentKey, PointerTracker tracker) {
// Check if we have a popup layout specified first.
if (mMoreKeysLayout == 0) {
return false;
@@ -338,7 +337,6 @@ public class LatinKeyboardView extends KeyboardView implements PointerTracker.Ke
// Check if we are already displaying popup panel.
if (mMoreKeysPanel != null)
return false;
- final Key parentKey = tracker.getKey(keyIndex);
if (parentKey == null)
return false;
return onLongPress(parentKey, tracker);
@@ -349,9 +347,11 @@ public class LatinKeyboardView extends KeyboardView implements PointerTracker.Ke
// When shift key is double tapped, the first tap is correctly processed as usual tap. And
// 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.
- final int primaryCode = ignore ? Keyboard.CODE_HAPTIC_AND_AUDIO_FEEDBACK_ONLY
- : Keyboard.CODE_CAPSLOCK;
- invokeCodeInput(primaryCode);
+ if (ignore) {
+ mKeyboardActionListener.onCustomRequest(LatinIME.CODE_HAPTIC_AND_AUDIO_FEEDBACK);
+ } else {
+ mKeyboardActionListener.onCodeInput(Keyboard.CODE_CAPSLOCK, null, 0, 0);
+ }
}
// This default implementation returns a more keys panel.
@@ -463,8 +463,7 @@ public class LatinKeyboardView extends KeyboardView implements PointerTracker.Ke
this, this, pointX, pointY, mMoreKeysWindow, getKeyboardActionListener());
final int translatedX = moreKeysPanel.translateX(tracker.getLastX());
final int translatedY = moreKeysPanel.translateY(tracker.getLastY());
- tracker.onShowMoreKeysPanel(
- translatedX, translatedY, SystemClock.uptimeMillis(), moreKeysPanel);
+ tracker.onShowMoreKeysPanel(translatedX, translatedY, moreKeysPanel);
dimEntireKeyboard(true);
return true;
}
@@ -548,8 +547,8 @@ public class LatinKeyboardView extends KeyboardView implements PointerTracker.Ke
// Multi-touch to single touch transition.
// Send a down event for the latest pointer if the key is different from the
// previous key.
- final int newKeyIndex = tracker.getKeyIndexOn(x, y);
- if (mOldKeyIndex != newKeyIndex) {
+ final Key newKey = tracker.getKeyOn(x, y);
+ if (mOldKey != newKey) {
tracker.onDownEvent(x, y, eventTime, this);
if (action == MotionEvent.ACTION_UP)
tracker.onUpEvent(x, y, eventTime);
@@ -559,7 +558,7 @@ public class LatinKeyboardView extends KeyboardView implements PointerTracker.Ke
// Send an up event for the last pointer.
final int lastX = tracker.getLastX();
final int lastY = tracker.getLastY();
- mOldKeyIndex = tracker.getKeyIndexOn(lastX, lastY);
+ mOldKey = tracker.getKeyOn(lastX, lastY);
tracker.onUpEvent(lastX, lastY, eventTime);
} else if (pointerCount == 1 && oldPointerCount == 1) {
tracker.processMotionEvent(action, x, y, eventTime, this);
diff --git a/java/src/com/android/inputmethod/keyboard/MoreKeysDetector.java b/java/src/com/android/inputmethod/keyboard/MoreKeysDetector.java
index d20204611..742ee98d7 100644
--- a/java/src/com/android/inputmethod/keyboard/MoreKeysDetector.java
+++ b/java/src/com/android/inputmethod/keyboard/MoreKeysDetector.java
@@ -16,8 +16,6 @@
package com.android.inputmethod.keyboard;
-import java.util.List;
-
public class MoreKeysDetector extends KeyDetector {
private final int mSlideAllowanceSquare;
private final int mSlideAllowanceSquareTop;
@@ -41,24 +39,23 @@ public class MoreKeysDetector extends KeyDetector {
}
@Override
- public int getKeyIndexAndNearbyCodes(int x, int y, final int[] allCodes) {
- final List<Key> keys = getKeyboard().mKeys;
+ public Key getKeyAndNearbyCodes(int x, int y, final int[] allCodes) {
final int touchX = getTouchX(x);
final int touchY = getTouchY(y);
- int nearestIndex = NOT_A_KEY;
+ Key nearestKey = null;
int nearestDist = (y < 0) ? mSlideAllowanceSquareTop : mSlideAllowanceSquare;
- final int keyCount = keys.size();
- for (int index = 0; index < keyCount; index++) {
- final int dist = keys.get(index).squaredDistanceToEdge(touchX, touchY);
+ for (final Key key : getKeyboard().mKeys) {
+ final int dist = key.squaredDistanceToEdge(touchX, touchY);
if (dist < nearestDist) {
- nearestIndex = index;
+ nearestKey = key;
nearestDist = dist;
}
}
- if (allCodes != null && nearestIndex != NOT_A_KEY)
- allCodes[0] = keys.get(nearestIndex).mCode;
- return nearestIndex;
+ if (allCodes != null && nearestKey != null) {
+ allCodes[0] = nearestKey.mCode;
+ }
+ return nearestKey;
}
-} \ No newline at end of file
+}
diff --git a/java/src/com/android/inputmethod/keyboard/PointerTracker.java b/java/src/com/android/inputmethod/keyboard/PointerTracker.java
index 198e06aab..9e0c5ce02 100644
--- a/java/src/com/android/inputmethod/keyboard/PointerTracker.java
+++ b/java/src/com/android/inputmethod/keyboard/PointerTracker.java
@@ -18,6 +18,7 @@ package com.android.inputmethod.keyboard;
import android.content.Context;
import android.content.res.Resources;
+import android.os.SystemClock;
import android.util.Log;
import android.view.MotionEvent;
import android.widget.TextView;
@@ -27,7 +28,6 @@ import com.android.inputmethod.latin.LatinImeLogger;
import com.android.inputmethod.latin.R;
import java.util.ArrayList;
-import java.util.Arrays;
import java.util.List;
public class PointerTracker {
@@ -67,22 +67,28 @@ public class PointerTracker {
public interface DrawingProxy extends MoreKeysPanel.Controller {
public void invalidateKey(Key key);
public TextView inflateKeyPreviewText();
- public void showKeyPreview(int keyIndex, PointerTracker tracker);
+ public void showKeyPreview(PointerTracker tracker);
public void cancelShowKeyPreview(PointerTracker tracker);
public void dismissKeyPreview(PointerTracker tracker);
}
public interface TimerProxy {
- public void startKeyRepeatTimer(long delay, int keyIndex, PointerTracker tracker);
- public void startLongPressTimer(long delay, int keyIndex, PointerTracker tracker);
+ public void startKeyTypedTimer(long delay);
+ public boolean isTyping();
+ public void startKeyRepeatTimer(long delay, PointerTracker tracker);
+ public void startLongPressTimer(long delay, PointerTracker tracker);
public void cancelLongPressTimer();
public void cancelKeyTimers();
public static class Adapter implements TimerProxy {
@Override
- public void startKeyRepeatTimer(long delay, int keyIndex, PointerTracker tracker) {}
+ public void startKeyTypedTimer(long delay) {}
@Override
- public void startLongPressTimer(long delay, int keyIndex, PointerTracker tracker) {}
+ public boolean isTyping() { return false; }
+ @Override
+ public void startKeyRepeatTimer(long delay, PointerTracker tracker) {}
+ @Override
+ public void startLongPressTimer(long delay, PointerTracker tracker) {}
@Override
public void cancelLongPressTimer() {}
@Override
@@ -97,6 +103,7 @@ public class PointerTracker {
private static int sLongPressKeyTimeout;
private static int sLongPressShiftKeyTimeout;
private static int sLongPressSpaceKeyTimeout;
+ private static int sIgnoreSpecialKeyTimeout;
private static int sTouchNoiseThresholdMillis;
private static int sTouchNoiseThresholdDistanceSquared;
@@ -119,9 +126,9 @@ public class PointerTracker {
private long mDownTime;
private long mUpTime;
- // The current key index where this pointer is.
- private int mKeyIndex = KeyDetector.NOT_A_KEY;
- // The position where mKeyIndex was recognized for the first time.
+ // The current key where this pointer is.
+ private Key mCurrentKey = null;
+ // The position where the current key was recognized for the first time.
private int mKeyX;
private int mKeyY;
@@ -167,7 +174,9 @@ public class PointerTracker {
sLongPressKeyTimeout = res.getInteger(R.integer.config_long_press_key_timeout);
sLongPressShiftKeyTimeout = res.getInteger(R.integer.config_long_press_shift_key_timeout);
sLongPressSpaceKeyTimeout = res.getInteger(R.integer.config_long_press_space_key_timeout);
+ sIgnoreSpecialKeyTimeout = res.getInteger(R.integer.config_ignore_special_key_timeout);
sTouchNoiseThresholdMillis = res.getInteger(R.integer.config_touch_noise_threshold_millis);
+
final float touchNoiseThresholdDistance = res.getDimension(
R.dimen.config_touch_noise_threshold_distance);
sTouchNoiseThresholdDistanceSquared = (int)(
@@ -207,7 +216,7 @@ public class PointerTracker {
public static void dismissAllKeyPreviews() {
for (final PointerTracker tracker : sTrackers) {
- tracker.setReleasedKeyGraphics(tracker.mKeyIndex);
+ tracker.setReleasedKeyGraphics(tracker.mCurrentKey);
}
}
@@ -228,12 +237,15 @@ public class PointerTracker {
// Returns true if keyboard has been changed by this callback.
private boolean callListenerOnPressAndCheckKeyboardLayoutChange(Key key, boolean withSliding) {
- final boolean ignoreModifierKey = mIgnoreModifierKey && isModifierCode(key.mCode);
- if (DEBUG_LISTENER)
- Log.d(TAG, "onPress : " + keyCodePrintable(key.mCode) + " sliding=" + withSliding
- + " ignoreModifier=" + ignoreModifierKey);
- if (ignoreModifierKey)
+ final boolean ignoreModifierKey = mIgnoreModifierKey && key.isModifier();
+ if (DEBUG_LISTENER) {
+ Log.d(TAG, "onPress : " + KeyDetector.printableCode(key.mCode)
+ + " sliding=" + withSliding + " ignoreModifier=" + ignoreModifierKey
+ + " enabled=" + key.isEnabled());
+ }
+ if (ignoreModifierKey) {
return false;
+ }
if (key.isEnabled()) {
mListener.onPress(key.mCode, withSliding);
final boolean keyboardLayoutHasBeenChanged = mKeyboardLayoutHasBeenChanged;
@@ -246,35 +258,47 @@ public class PointerTracker {
// Note that we need primaryCode argument because the keyboard may in shifted state and the
// primaryCode is different from {@link Key#mCode}.
private void callListenerOnCodeInput(Key key, int primaryCode, int[] keyCodes, int x, int y) {
- final boolean ignoreModifierKey = mIgnoreModifierKey && isModifierCode(key.mCode);
- if (DEBUG_LISTENER)
- Log.d(TAG, "onCodeInput: " + keyCodePrintable(primaryCode)
- + " codes="+ Arrays.toString(keyCodes) + " x=" + x + " y=" + y
- + " ignoreModifier=" + ignoreModifierKey);
- if (ignoreModifierKey)
+ final boolean ignoreModifierKey = mIgnoreModifierKey && key.isModifier();
+ final boolean alterCode = key.altCodeWhileTyping() && mTimerProxy.isTyping();
+ final int code = alterCode ? key.mAltCode : primaryCode;
+ // If code is CODE_DUMMY here, this key will be ignored or generate text.
+ final CharSequence text = (code != Keyboard.CODE_DUMMY) ? null : key.mOutputText;
+ if (DEBUG_LISTENER) {
+ Log.d(TAG, "onCodeInput: " + KeyDetector.printableCode(code) + " text=" + text
+ + " codes="+ KeyDetector.printableCodes(keyCodes) + " x=" + x + " y=" + y
+ + " ignoreModifier=" + ignoreModifierKey + " alterCode=" + alterCode
+ + " enabled=" + key.isEnabled());
+ }
+ if (ignoreModifierKey) {
return;
- if (key.isEnabled())
- mListener.onCodeInput(primaryCode, keyCodes, x, y);
- }
-
- private void callListenerOnTextInput(Key key) {
- if (DEBUG_LISTENER)
- Log.d(TAG, "onTextInput: text=" + key.mOutputText);
- if (key.isEnabled())
- mListener.onTextInput(key.mOutputText);
+ }
+ if (key.isEnabled()) {
+ if (code != Keyboard.CODE_DUMMY) {
+ mListener.onCodeInput(code, keyCodes, x, y);
+ } else if (text != null) {
+ mListener.onTextInput(text);
+ }
+ if (!key.altCodeWhileTyping() && !key.isModifier()) {
+ mTimerProxy.startKeyTypedTimer(sIgnoreSpecialKeyTimeout);
+ }
+ }
}
// Note that we need primaryCode argument because the keyboard may in shifted state and the
// primaryCode is different from {@link Key#mCode}.
private void callListenerOnRelease(Key key, int primaryCode, boolean withSliding) {
- final boolean ignoreModifierKey = mIgnoreModifierKey && isModifierCode(key.mCode);
- if (DEBUG_LISTENER)
- Log.d(TAG, "onRelease : " + keyCodePrintable(primaryCode) + " sliding="
- + withSliding + " ignoreModifier=" + ignoreModifierKey);
- if (ignoreModifierKey)
+ final boolean ignoreModifierKey = mIgnoreModifierKey && key.isModifier();
+ if (DEBUG_LISTENER) {
+ Log.d(TAG, "onRelease : " + KeyDetector.printableCode(primaryCode)
+ + " sliding=" + withSliding + " ignoreModifier=" + ignoreModifierKey
+ + " enabled="+ key.isEnabled());
+ }
+ if (ignoreModifierKey) {
return;
- if (key.isEnabled())
+ }
+ if (key.isEnabled()) {
mListener.onRelease(primaryCode, withSliding);
+ }
}
private void callListenerOnCancelInput() {
@@ -295,71 +319,69 @@ public class PointerTracker {
return mIsInSlidingKeyInput;
}
- private boolean isValidKeyIndex(int keyIndex) {
- return keyIndex >= 0 && keyIndex < mKeys.size();
- }
-
- public Key getKey(int keyIndex) {
- return isValidKeyIndex(keyIndex) ? mKeys.get(keyIndex) : null;
- }
-
- private static boolean isModifierCode(int primaryCode) {
- return primaryCode == Keyboard.CODE_SHIFT
- || primaryCode == Keyboard.CODE_SWITCH_ALPHA_SYMBOL;
- }
-
- private boolean isModifierInternal(int keyIndex) {
- final Key key = getKey(keyIndex);
- return key == null ? false : isModifierCode(key.mCode);
+ public Key getKey() {
+ return mCurrentKey;
}
public boolean isModifier() {
- return isModifierInternal(mKeyIndex);
- }
-
- private boolean isOnModifierKey(int x, int y) {
- return isModifierInternal(mKeyDetector.getKeyIndexAndNearbyCodes(x, y, null));
- }
-
- public boolean isOnShiftKey(int x, int y) {
- final Key key = getKey(mKeyDetector.getKeyIndexAndNearbyCodes(x, y, null));
- return key != null && key.mCode == Keyboard.CODE_SHIFT;
+ return mCurrentKey != null && mCurrentKey.isModifier();
}
- public int getKeyIndexOn(int x, int y) {
- return mKeyDetector.getKeyIndexAndNearbyCodes(x, y, null);
+ public Key getKeyOn(int x, int y) {
+ return mKeyDetector.getKeyAndNearbyCodes(x, y, null);
}
- private void setReleasedKeyGraphics(int keyIndex) {
+ private void setReleasedKeyGraphics(Key key) {
mDrawingProxy.dismissKeyPreview(this);
- final Key key = getKey(keyIndex);
if (key != null && key.isEnabled()) {
key.onReleased();
mDrawingProxy.invalidateKey(key);
+
+ if (key.isShift()) {
+ for (final Key shiftKey : mKeyboard.mShiftKeys) {
+ if (shiftKey != key) {
+ shiftKey.onReleased();
+ mDrawingProxy.invalidateKey(shiftKey);
+ }
+ }
+ }
+
+ if (key.altCodeWhileTyping()) {
+ final Key altKey = mKeyboard.getKey(key.mAltCode);
+ if (altKey != null) {
+ altKey.onReleased();
+ mDrawingProxy.invalidateKey(altKey);
+ }
+ }
}
}
- private void setPressedKeyGraphics(int keyIndex) {
- final Key key = getKey(keyIndex);
+ private void setPressedKeyGraphics(Key key) {
if (key != null && key.isEnabled()) {
- if (isKeyPreviewRequired(key)) {
- mDrawingProxy.showKeyPreview(keyIndex, this);
+ if (!key.noKeyPreview()) {
+ mDrawingProxy.showKeyPreview(this);
}
key.onPressed();
mDrawingProxy.invalidateKey(key);
- }
- }
- // The modifier key, such as shift key, should not show its key preview.
- private static boolean isKeyPreviewRequired(Key key) {
- final int code = key.mCode;
- // TODO: Stop hard-coding these key codes here, and add a new key attribute of a key.
- if (code == Keyboard.CODE_SPACE || code == Keyboard.CODE_ENTER
- || code == Keyboard.CODE_DELETE || isModifierCode(code)
- || code == Keyboard.CODE_SETTINGS || code == Keyboard.CODE_SHORTCUT) {
- return false;
+ if (key.isShift()) {
+ for (final Key shiftKey : mKeyboard.mShiftKeys) {
+ if (shiftKey != key) {
+ shiftKey.onPressed();
+ mDrawingProxy.invalidateKey(shiftKey);
+ }
+ }
+ }
+
+ if (key.altCodeWhileTyping() && mTimerProxy.isTyping()) {
+ final Key altKey = mKeyboard.getKey(key.mAltCode);
+ if (altKey != null) {
+ // TODO: Show altKey's preview.
+ altKey.onPressed();
+ mDrawingProxy.invalidateKey(altKey);
+ }
+ }
}
- return true;
}
public int getLastX() {
@@ -374,31 +396,31 @@ public class PointerTracker {
return mDownTime;
}
- private int onDownKey(int x, int y, long eventTime) {
+ private Key onDownKey(int x, int y, long eventTime) {
mDownTime = eventTime;
return onMoveToNewKey(onMoveKeyInternal(x, y), x, y);
}
- private int onMoveKeyInternal(int x, int y) {
+ private Key onMoveKeyInternal(int x, int y) {
mLastX = x;
mLastY = y;
- return mKeyDetector.getKeyIndexAndNearbyCodes(x, y, null);
+ return mKeyDetector.getKeyAndNearbyCodes(x, y, null);
}
- private int onMoveKey(int x, int y) {
+ private Key onMoveKey(int x, int y) {
return onMoveKeyInternal(x, y);
}
- private int onMoveToNewKey(int keyIndex, int x, int y) {
- mKeyIndex = keyIndex;
+ private Key onMoveToNewKey(Key newKey, int x, int y) {
+ mCurrentKey = newKey;
mKeyX = x;
mKeyY = y;
- return keyIndex;
+ return newKey;
}
- private int onUpKey(int x, int y, long eventTime) {
+ private Key onUpKey(int x, int y, long eventTime) {
mUpTime = eventTime;
- mKeyIndex = KeyDetector.NOT_A_KEY;
+ mCurrentKey = null;
return onMoveKeyInternal(x, y);
}
@@ -447,7 +469,8 @@ public class PointerTracker {
final PointerTrackerQueue queue = sPointerTrackerQueue;
if (queue != null) {
- if (isOnModifierKey(x, y)) {
+ final Key key = getKeyOn(x, y);
+ if (key != null && key.isModifier()) {
// Before processing a down event of modifier key, all pointers already being
// tracked should be released.
queue.releaseAllPointers(eventTime);
@@ -458,32 +481,35 @@ public class PointerTracker {
}
private void onDownEventInternal(int x, int y, long eventTime) {
- int keyIndex = onDownKey(x, y, eventTime);
+ Key key = onDownKey(x, y, eventTime);
// Sliding key is allowed when 1) enabled by configuration, 2) this pointer starts sliding
// from modifier key, or 3) this pointer's KeyDetector always allows sliding input.
- mIsAllowedSlidingKeyInput = sConfigSlidingKeyInputEnabled || isModifierInternal(keyIndex)
+ mIsAllowedSlidingKeyInput = sConfigSlidingKeyInputEnabled
+ || (key != null && key.isModifier())
|| mKeyDetector.alwaysAllowsSlidingInput();
mKeyboardLayoutHasBeenChanged = false;
mKeyAlreadyProcessed = false;
mIsRepeatableKey = false;
mIsInSlidingKeyInput = false;
mIgnoreModifierKey = false;
- if (isValidKeyIndex(keyIndex)) {
+ if (key != null) {
// This onPress call may have changed keyboard layout. Those cases are detected at
- // {@link #setKeyboard}. In those cases, we should update keyIndex according to the new
+ // {@link #setKeyboard}. In those cases, we should update key according to the new
// keyboard layout.
- if (callListenerOnPressAndCheckKeyboardLayoutChange(getKey(keyIndex), false))
- keyIndex = onDownKey(x, y, eventTime);
+ if (callListenerOnPressAndCheckKeyboardLayoutChange(key, false)) {
+ key = onDownKey(x, y, eventTime);
+ }
- startRepeatKey(keyIndex);
- startLongPressTimer(keyIndex);
- setPressedKeyGraphics(keyIndex);
+ startRepeatKey(key);
+ startLongPressTimer(key);
+ setPressedKeyGraphics(key);
}
}
private void startSlidingKeyInput(Key key) {
- if (!mIsInSlidingKeyInput)
- mIgnoreModifierKey = isModifierCode(key.mCode);
+ if (!mIsInSlidingKeyInput) {
+ mIgnoreModifierKey = key.isModifier();
+ }
mIsInSlidingKeyInput = true;
}
@@ -495,39 +521,40 @@ public class PointerTracker {
final int lastX = mLastX;
final int lastY = mLastY;
- final int oldKeyIndex = mKeyIndex;
- final Key oldKey = getKey(oldKeyIndex);
- int keyIndex = onMoveKey(x, y);
- if (isValidKeyIndex(keyIndex)) {
+ final Key oldKey = mCurrentKey;
+ Key key = onMoveKey(x, y);
+ if (key != null) {
if (oldKey == null) {
// The pointer has been slid in to the new key, but the finger was not on any keys.
// In this case, we must call onPress() to notify that the new key is being pressed.
// This onPress call may have changed keyboard layout. Those cases are detected at
- // {@link #setKeyboard}. In those cases, we should update keyIndex according to the
+ // {@link #setKeyboard}. In those cases, we should update key according to the
// new keyboard layout.
- if (callListenerOnPressAndCheckKeyboardLayoutChange(getKey(keyIndex), true))
- keyIndex = onMoveKey(x, y);
- onMoveToNewKey(keyIndex, x, y);
- startLongPressTimer(keyIndex);
- setPressedKeyGraphics(keyIndex);
- } else if (isMajorEnoughMoveToBeOnNewKey(x, y, keyIndex)) {
+ if (callListenerOnPressAndCheckKeyboardLayoutChange(key, true)) {
+ key = onMoveKey(x, y);
+ }
+ onMoveToNewKey(key, x, y);
+ startLongPressTimer(key);
+ setPressedKeyGraphics(key);
+ } else if (isMajorEnoughMoveToBeOnNewKey(x, y, key)) {
// The pointer has been slid in to the new key from the previous key, we must call
// onRelease() first to notify that the previous key has been released, then call
// onPress() to notify that the new key is being pressed.
- setReleasedKeyGraphics(oldKeyIndex);
+ setReleasedKeyGraphics(oldKey);
callListenerOnRelease(oldKey, oldKey.mCode, true);
startSlidingKeyInput(oldKey);
mTimerProxy.cancelKeyTimers();
- startRepeatKey(keyIndex);
+ startRepeatKey(key);
if (mIsAllowedSlidingKeyInput) {
// This onPress call may have changed keyboard layout. Those cases are detected
- // at {@link #setKeyboard}. In those cases, we should update keyIndex according
+ // at {@link #setKeyboard}. In those cases, we should update key according
// to the new keyboard layout.
- if (callListenerOnPressAndCheckKeyboardLayoutChange(getKey(keyIndex), true))
- keyIndex = onMoveKey(x, y);
- onMoveToNewKey(keyIndex, x, y);
- startLongPressTimer(keyIndex);
- setPressedKeyGraphics(keyIndex);
+ if (callListenerOnPressAndCheckKeyboardLayoutChange(key, true)) {
+ key = onMoveKey(x, y);
+ }
+ onMoveToNewKey(key, x, y);
+ startLongPressTimer(key);
+ setPressedKeyGraphics(key);
} else {
// HACK: On some devices, quick successive touches may be translated to sudden
// move by touch panel firmware. This hack detects the case and translates the
@@ -543,20 +570,20 @@ public class PointerTracker {
onDownEventInternal(x, y, eventTime);
} else {
mKeyAlreadyProcessed = true;
- setReleasedKeyGraphics(oldKeyIndex);
+ setReleasedKeyGraphics(oldKey);
}
}
}
} else {
- if (oldKey != null && isMajorEnoughMoveToBeOnNewKey(x, y, keyIndex)) {
+ if (oldKey != null && isMajorEnoughMoveToBeOnNewKey(x, y, key)) {
// The pointer has been slid out from the previous key, we must call onRelease() to
// notify that the previous key has been released.
- setReleasedKeyGraphics(oldKeyIndex);
+ setReleasedKeyGraphics(oldKey);
callListenerOnRelease(oldKey, oldKey.mCode, true);
startSlidingKeyInput(oldKey);
mTimerProxy.cancelLongPressTimer();
if (mIsAllowedSlidingKeyInput) {
- onMoveToNewKey(keyIndex, x, y);
+ onMoveToNewKey(key, x, y);
} else {
mKeyAlreadyProcessed = true;
}
@@ -570,7 +597,7 @@ public class PointerTracker {
final PointerTrackerQueue queue = sPointerTrackerQueue;
if (queue != null) {
- if (isModifier()) {
+ if (mCurrentKey != null && mCurrentKey.isModifier()) {
// Before processing an up event of modifier key, all pointers already being
// tracked should be released.
queue.releaseAllPointersExcept(this, eventTime);
@@ -605,8 +632,8 @@ public class PointerTracker {
keyX = mKeyX;
keyY = mKeyY;
}
- final int keyIndex = onUpKey(keyX, keyY, eventTime);
- setReleasedKeyGraphics(keyIndex);
+ final Key key = onUpKey(keyX, keyY, eventTime);
+ setReleasedKeyGraphics(key);
if (mIsShowingMoreKeysPanel) {
mDrawingProxy.dismissMoreKeysPanel();
mIsShowingMoreKeysPanel = false;
@@ -614,19 +641,19 @@ public class PointerTracker {
if (mKeyAlreadyProcessed)
return;
if (!mIsRepeatableKey) {
- detectAndSendKey(keyIndex, keyX, keyY);
+ detectAndSendKey(key, keyX, keyY);
}
}
- public void onShowMoreKeysPanel(int x, int y, long eventTime, KeyEventHandler handler) {
+ public void onShowMoreKeysPanel(int x, int y, KeyEventHandler handler) {
onLongPressed();
- onDownEvent(x, y, eventTime, handler);
+ onDownEvent(x, y, SystemClock.uptimeMillis(), handler);
mIsShowingMoreKeysPanel = true;
}
public void onLongPressed() {
mKeyAlreadyProcessed = true;
- setReleasedKeyGraphics(mKeyIndex);
+ setReleasedKeyGraphics(mCurrentKey);
final PointerTrackerQueue queue = sPointerTrackerQueue;
if (queue != null) {
queue.remove(this);
@@ -648,7 +675,7 @@ public class PointerTracker {
private void onCancelEventInternal() {
mTimerProxy.cancelKeyTimers();
mDrawingProxy.cancelShowKeyPreview(this);
- setReleasedKeyGraphics(mKeyIndex);
+ setReleasedKeyGraphics(mCurrentKey);
mIsInSlidingKeyInput = false;
if (mIsShowingMoreKeysPanel) {
mDrawingProxy.dismissMoreKeysPanel();
@@ -656,48 +683,45 @@ public class PointerTracker {
}
}
- private void startRepeatKey(int keyIndex) {
- final Key key = getKey(keyIndex);
- if (key != null && key.mRepeatable) {
- onRepeatKey(keyIndex);
- mTimerProxy.startKeyRepeatTimer(sDelayBeforeKeyRepeatStart, keyIndex, this);
+ private void startRepeatKey(Key key) {
+ if (key != null && key.isRepeatable()) {
+ onRepeatKey(key);
+ mTimerProxy.startKeyRepeatTimer(sDelayBeforeKeyRepeatStart, this);
mIsRepeatableKey = true;
} else {
mIsRepeatableKey = false;
}
}
- public void onRepeatKey(int keyIndex) {
- Key key = getKey(keyIndex);
+ public void onRepeatKey(Key key) {
if (key != null) {
- detectAndSendKey(keyIndex, key.mX, key.mY);
+ detectAndSendKey(key, key.mX, key.mY);
}
}
- private boolean isMajorEnoughMoveToBeOnNewKey(int x, int y, int newKey) {
+ private boolean isMajorEnoughMoveToBeOnNewKey(int x, int y, Key newKey) {
if (mKeys == null || mKeyDetector == null)
throw new NullPointerException("keyboard and/or key detector not set");
- int curKey = mKeyIndex;
+ Key curKey = mCurrentKey;
if (newKey == curKey) {
return false;
- } else if (isValidKeyIndex(curKey)) {
- return mKeys.get(curKey).squaredDistanceToEdge(x, y)
+ } else if (curKey != null) {
+ return curKey.squaredDistanceToEdge(x, y)
>= mKeyDetector.getKeyHysteresisDistanceSquared();
} else {
return true;
}
}
- private void startLongPressTimer(int keyIndex) {
- Key key = getKey(keyIndex);
+ private void startLongPressTimer(Key key) {
if (key == null) return;
if (key.mCode == Keyboard.CODE_SHIFT) {
if (sLongPressShiftKeyTimeout > 0) {
- mTimerProxy.startLongPressTimer(sLongPressShiftKeyTimeout, keyIndex, this);
+ mTimerProxy.startLongPressTimer(sLongPressShiftKeyTimeout, this);
}
} else if (key.mCode == Keyboard.CODE_SPACE) {
if (sLongPressSpaceKeyTimeout > 0) {
- mTimerProxy.startLongPressTimer(sLongPressSpaceKeyTimeout, keyIndex, this);
+ mTimerProxy.startLongPressTimer(sLongPressSpaceKeyTimeout, this);
}
} else if (key.hasUppercaseLetter() && mKeyboard.isManualTemporaryUpperCase()) {
// We need not start long press timer on the key which has manual temporary upper case
@@ -705,59 +729,48 @@ public class PointerTracker {
return;
} else if (sKeyboardSwitcher.isInMomentarySwitchState()) {
// We use longer timeout for sliding finger input started from the symbols mode key.
- mTimerProxy.startLongPressTimer(sLongPressKeyTimeout * 3, keyIndex, this);
+ mTimerProxy.startLongPressTimer(sLongPressKeyTimeout * 3, this);
} else {
- mTimerProxy.startLongPressTimer(sLongPressKeyTimeout, keyIndex, this);
+ mTimerProxy.startLongPressTimer(sLongPressKeyTimeout, this);
}
}
- private void detectAndSendKey(int index, int x, int y) {
- final Key key = getKey(index);
+ private void detectAndSendKey(Key key, int x, int y) {
if (key == null) {
callListenerOnCancelInput();
return;
}
- if (key.mOutputText != null) {
- callListenerOnTextInput(key);
- callListenerOnRelease(key, key.mCode, false);
- } else {
- int code = key.mCode;
- final int[] codes = mKeyDetector.newCodeArray();
- mKeyDetector.getKeyIndexAndNearbyCodes(x, y, codes);
-
- // If keyboard is in manual temporary upper case state and key has manual temporary
- // uppercase letter as key hint letter, alternate character code should be sent.
- if (mKeyboard.isManualTemporaryUpperCase() && key.hasUppercaseLetter()) {
- code = key.mHintLabel.charAt(0);
- codes[0] = code;
- }
- // Swap the first and second values in the codes array if the primary code is not the
- // first value but the second value in the array. This happens when key debouncing is
- // in effect.
- if (codes.length >= 2 && codes[0] != code && codes[1] == code) {
- codes[1] = codes[0];
- codes[0] = code;
- }
- callListenerOnCodeInput(key, code, codes, x, y);
- callListenerOnRelease(key, code, false);
+ int code = key.mCode;
+ final int[] codes = mKeyDetector.newCodeArray();
+ mKeyDetector.getKeyAndNearbyCodes(x, y, codes);
+
+ // If keyboard is in manual temporary upper case state and key has manual temporary
+ // uppercase letter as key hint letter, alternate character code should be sent.
+ if (mKeyboard.isManualTemporaryUpperCase() && key.hasUppercaseLetter()) {
+ code = key.mHintLabel.charAt(0);
+ codes[0] = code;
}
+
+ // Swap the first and second values in the codes array if the primary code is not the
+ // first value but the second value in the array. This happens when key debouncing is
+ // in effect.
+ if (codes.length >= 2 && codes[0] != code && codes[1] == code) {
+ codes[1] = codes[0];
+ codes[0] = code;
+ }
+ callListenerOnCodeInput(key, code, codes, x, y);
+ callListenerOnRelease(key, code, false);
}
private long mPreviousEventTime;
private void printTouchEvent(String title, int x, int y, long eventTime) {
- final int keyIndex = mKeyDetector.getKeyIndexAndNearbyCodes(x, y, null);
- final Key key = getKey(keyIndex);
- final String code = (key == null) ? "----" : keyCodePrintable(key.mCode);
+ final Key key = mKeyDetector.getKeyAndNearbyCodes(x, y, null);
+ final String code = KeyDetector.printableCode(key);
final long delta = eventTime - mPreviousEventTime;
- Log.d(TAG, String.format("%s%s[%d] %4d %4d %5d %3d(%s)", title,
- (mKeyAlreadyProcessed ? "-" : " "), mPointerId, x, y, delta, keyIndex, code));
+ Log.d(TAG, String.format("%s%s[%d] %4d %4d %5d %s", title,
+ (mKeyAlreadyProcessed ? "-" : " "), mPointerId, x, y, delta, code));
mPreviousEventTime = eventTime;
}
-
- private static String keyCodePrintable(int primaryCode) {
- final String modifier = isModifierCode(primaryCode) ? " modifier" : "";
- return String.format((primaryCode < 0) ? "%4d" : "0x%02x", primaryCode) + modifier;
- }
}
diff --git a/java/src/com/android/inputmethod/keyboard/ProximityInfo.java b/java/src/com/android/inputmethod/keyboard/ProximityInfo.java
index 2a25d0ca7..6c5c3e7be 100644
--- a/java/src/com/android/inputmethod/keyboard/ProximityInfo.java
+++ b/java/src/com/android/inputmethod/keyboard/ProximityInfo.java
@@ -75,16 +75,16 @@ public class ProximityInfo {
return spellCheckerProximityInfo;
}
- private int mNativeProximityInfo;
+ private long mNativeProximityInfo;
static {
Utils.loadNativeLibrary();
}
- private native int setProximityInfoNative(int maxProximityCharsSize, int displayWidth,
+ private native long setProximityInfoNative(int maxProximityCharsSize, int displayWidth,
int displayHeight, int gridWidth, int gridHeight, int[] proximityCharsArray,
int keyCount, int[] keyXCoordinates, int[] keyYCoordinates,
int[] keyWidths, int[] keyHeights, int[] keyCharCodes,
float[] sweetSpotCenterX, float[] sweetSpotCenterY, float[] sweetSpotRadii);
- private native void releaseProximityInfoNative(int nativeProximityInfo);
+ private native void releaseProximityInfoNative(long nativeProximityInfo);
private final void setProximityInfo(int[][] gridNeighborKeyIndexes, int keyboardWidth,
int keyboardHeight, List<Key> keys,
@@ -157,7 +157,7 @@ public class ProximityInfo {
}
}
- public int getNativeProximityInfo() {
+ public long getNativeProximityInfo() {
return mNativeProximityInfo;
}
diff --git a/java/src/com/android/inputmethod/keyboard/internal/KeyStyles.java b/java/src/com/android/inputmethod/keyboard/internal/KeyStyles.java
index b385b7a04..218793500 100644
--- a/java/src/com/android/inputmethod/keyboard/internal/KeyStyles.java
+++ b/java/src/com/android/inputmethod/keyboard/internal/KeyStyles.java
@@ -40,7 +40,6 @@ public class KeyStyles {
public CharSequence getText(TypedArray a, int index);
public int getInt(TypedArray a, int index, int defaultValue);
public int getFlag(TypedArray a, int index, int defaultValue);
- public boolean getBoolean(TypedArray a, int index, boolean defaultValue);
}
/* package */ static class EmptyKeyStyle implements KeyStyle {
@@ -68,11 +67,6 @@ public class KeyStyles {
return a.getInt(index, defaultValue);
}
- @Override
- public boolean getBoolean(TypedArray a, int index, boolean defaultValue) {
- return a.getBoolean(index, defaultValue);
- }
-
protected static CharSequence[] parseTextArray(TypedArray a, int index) {
if (!a.hasValue(index))
return null;
@@ -151,12 +145,6 @@ public class KeyStyles {
return super.getFlag(a, index, defaultValue) | (value != null ? value : 0);
}
- @Override
- public boolean getBoolean(TypedArray a, int index, boolean defaultValue) {
- final Boolean value = (Boolean)mAttributes.get(index);
- return super.getBoolean(a, index, (value != null) ? value : defaultValue);
- }
-
private DeclaredKeyStyle() {
super();
}
@@ -164,18 +152,18 @@ public class KeyStyles {
private void parseKeyStyleAttributes(TypedArray keyAttr) {
// TODO: Currently not all Key attributes can be declared as style.
readInt(keyAttr, R.styleable.Keyboard_Key_code);
+ readInt(keyAttr, R.styleable.Keyboard_Key_altCode);
readText(keyAttr, R.styleable.Keyboard_Key_keyLabel);
readText(keyAttr, R.styleable.Keyboard_Key_keyOutputText);
readText(keyAttr, R.styleable.Keyboard_Key_keyHintLabel);
readTextArray(keyAttr, R.styleable.Keyboard_Key_moreKeys);
- readFlag(keyAttr, R.styleable.Keyboard_Key_keyLabelOption);
+ readFlag(keyAttr, R.styleable.Keyboard_Key_keyLabelFlags);
readInt(keyAttr, R.styleable.Keyboard_Key_keyIcon);
readInt(keyAttr, R.styleable.Keyboard_Key_keyIconPreview);
readInt(keyAttr, R.styleable.Keyboard_Key_keyIconShifted);
readInt(keyAttr, R.styleable.Keyboard_Key_maxMoreKeysColumn);
readInt(keyAttr, R.styleable.Keyboard_Key_backgroundType);
- readBoolean(keyAttr, R.styleable.Keyboard_Key_isRepeatable);
- readBoolean(keyAttr, R.styleable.Keyboard_Key_enabled);
+ readFlag(keyAttr, R.styleable.Keyboard_Key_keyActionFlags);
}
private void readText(TypedArray a, int index) {
@@ -194,11 +182,6 @@ public class KeyStyles {
mAttributes.put(index, a.getInt(index, 0) | (value != null ? value : 0));
}
- private void readBoolean(TypedArray a, int index) {
- if (a.hasValue(index))
- mAttributes.put(index, a.getBoolean(index, false));
- }
-
private void readTextArray(TypedArray a, int index) {
final CharSequence[] value = parseTextArray(a, index);
if (value != null)
@@ -235,7 +218,7 @@ public class KeyStyles {
return mStyles.get(styleName);
}
- public KeyStyle getEmptyKeyStyle() {
+ public static KeyStyle getEmptyKeyStyle() {
return EMPTY_KEY_STYLE;
}
}
diff --git a/java/src/com/android/inputmethod/keyboard/internal/PointerTrackerQueue.java b/java/src/com/android/inputmethod/keyboard/internal/PointerTrackerQueue.java
index 08e7a7a4e..d9181f786 100644
--- a/java/src/com/android/inputmethod/keyboard/internal/PointerTrackerQueue.java
+++ b/java/src/com/android/inputmethod/keyboard/internal/PointerTrackerQueue.java
@@ -18,6 +18,7 @@ package com.android.inputmethod.keyboard.internal;
import com.android.inputmethod.keyboard.PointerTracker;
+import java.util.Iterator;
import java.util.LinkedList;
public class PointerTrackerQueue {
@@ -27,18 +28,23 @@ public class PointerTrackerQueue {
mQueue.add(tracker);
}
+ public synchronized void remove(PointerTracker tracker) {
+ mQueue.remove(tracker);
+ }
+
public synchronized void releaseAllPointersOlderThan(PointerTracker tracker, long eventTime) {
- if (mQueue.lastIndexOf(tracker) < 0) {
+ if (!mQueue.contains(tracker)) {
return;
}
- final LinkedList<PointerTracker> queue = mQueue;
- int oldestPos = 0;
- for (PointerTracker t = queue.get(oldestPos); t != tracker; t = queue.get(oldestPos)) {
- if (t.isModifier()) {
- oldestPos++;
- } else {
+ final Iterator<PointerTracker> it = mQueue.iterator();
+ while (it.hasNext()) {
+ final PointerTracker t = it.next();
+ if (t == tracker) {
+ break;
+ }
+ if (!t.isModifier()) {
t.onPhantomUpEvent(t.getLastX(), t.getLastY(), eventTime);
- queue.remove(oldestPos);
+ it.remove();
}
}
}
@@ -48,22 +54,16 @@ public class PointerTrackerQueue {
}
public synchronized void releaseAllPointersExcept(PointerTracker tracker, long eventTime) {
- for (PointerTracker t : mQueue) {
- if (t == tracker) {
- continue;
+ final Iterator<PointerTracker> it = mQueue.iterator();
+ while (it.hasNext()) {
+ final PointerTracker t = it.next();
+ if (t != tracker) {
+ t.onPhantomUpEvent(t.getLastX(), t.getLastY(), eventTime);
+ it.remove();
}
- t.onPhantomUpEvent(t.getLastX(), t.getLastY(), eventTime);
- }
- mQueue.clear();
- if (tracker != null) {
- mQueue.add(tracker);
}
}
- public synchronized void remove(PointerTracker tracker) {
- mQueue.remove(tracker);
- }
-
public synchronized boolean isAnyInSlidingKeyInput() {
for (final PointerTracker tracker : mQueue) {
if (tracker.isInSlidingKeyInput()) {
@@ -75,13 +75,12 @@ public class PointerTrackerQueue {
@Override
public String toString() {
- StringBuilder sb = new StringBuilder("[");
- for (PointerTracker tracker : mQueue) {
- if (sb.length() > 1)
+ final StringBuilder sb = new StringBuilder();
+ for (final PointerTracker tracker : mQueue) {
+ if (sb.length() > 0)
sb.append(" ");
- sb.append(String.format("%d", tracker.mPointerId));
+ sb.append(tracker.mPointerId);
}
- sb.append("]");
- return sb.toString();
+ return "[" + sb + "]";
}
}
diff --git a/java/src/com/android/inputmethod/latin/AutoCorrection.java b/java/src/com/android/inputmethod/latin/AutoCorrection.java
index 485ec511f..cd066a3d1 100644
--- a/java/src/com/android/inputmethod/latin/AutoCorrection.java
+++ b/java/src/com/android/inputmethod/latin/AutoCorrection.java
@@ -98,7 +98,7 @@ public class AutoCorrection {
return whiteListedWord != null;
}
- private boolean hasAutoCorrectionForTypedWord(Map<String, Dictionary> dictionaries,
+ private static boolean hasAutoCorrectionForTypedWord(Map<String, Dictionary> dictionaries,
WordComposer wordComposer, ArrayList<CharSequence> suggestions, CharSequence typedWord,
int correctionMode) {
if (TextUtils.isEmpty(typedWord)) return false;
diff --git a/java/src/com/android/inputmethod/latin/BinaryDictionary.java b/java/src/com/android/inputmethod/latin/BinaryDictionary.java
index b9fd57434..f0e56d346 100644
--- a/java/src/com/android/inputmethod/latin/BinaryDictionary.java
+++ b/java/src/com/android/inputmethod/latin/BinaryDictionary.java
@@ -46,7 +46,7 @@ public class BinaryDictionary extends Dictionary {
private static final int TYPED_LETTER_MULTIPLIER = 2;
private int mDicTypeId;
- private int mNativeDict;
+ private long mNativeDict;
private final int[] mInputCodes = new int[MAX_WORD_LENGTH * MAX_PROXIMITY_CHARS_SIZE];
private final char[] mOutputChars = new char[MAX_WORD_LENGTH * MAX_WORDS];
private final char[] mOutputChars_bigrams = new char[MAX_WORD_LENGTH * MAX_BIGRAMS];
@@ -107,15 +107,15 @@ public class BinaryDictionary extends Dictionary {
Utils.loadNativeLibrary();
}
- private native int openNative(String sourceDir, long dictOffset, long dictSize,
+ private native long openNative(String sourceDir, long dictOffset, long dictSize,
int typedLetterMultiplier, int fullWordMultiplier, int maxWordLength,
int maxWords, int maxAlternatives);
- private native void closeNative(int dict);
- private native boolean isValidWordNative(int nativeData, char[] word, int wordLength);
- private native int getSuggestionsNative(int dict, int proximityInfo, int[] xCoordinates,
+ private native void closeNative(long dict);
+ private native boolean isValidWordNative(long dict, char[] word, int wordLength);
+ private native int getSuggestionsNative(long dict, long proximityInfo, int[] xCoordinates,
int[] yCoordinates, int[] inputCodes, int codesSize, int flags, char[] outputChars,
int[] scores);
- private native int getBigramsNative(int dict, char[] prevWord, int prevWordLength,
+ private native int getBigramsNative(long dict, char[] prevWord, int prevWordLength,
int[] inputCodes, int inputCodesLength, char[] outputChars, int[] scores,
int maxWordLength, int maxBigrams, int maxAlternatives);
diff --git a/java/src/com/android/inputmethod/latin/DictionaryCollection.java b/java/src/com/android/inputmethod/latin/DictionaryCollection.java
index 739153044..c19a5a718 100644
--- a/java/src/com/android/inputmethod/latin/DictionaryCollection.java
+++ b/java/src/com/android/inputmethod/latin/DictionaryCollection.java
@@ -18,6 +18,8 @@ package com.android.inputmethod.latin;
import com.android.inputmethod.keyboard.ProximityInfo;
+import android.util.Log;
+
import java.util.Collection;
import java.util.Collections;
import java.util.List;
@@ -27,7 +29,7 @@ import java.util.concurrent.CopyOnWriteArrayList;
* Class for a collection of dictionaries that behave like one dictionary.
*/
public class DictionaryCollection extends Dictionary {
-
+ private final String TAG = DictionaryCollection.class.getSimpleName();
protected final List<Dictionary> mDictionaries;
public DictionaryCollection() {
@@ -75,7 +77,21 @@ public class DictionaryCollection extends Dictionary {
dict.close();
}
- public void addDictionary(Dictionary newDict) {
- if (null != newDict) mDictionaries.add(newDict);
+ // Warning: this is not thread-safe. Take necessary precaution when calling.
+ public void addDictionary(final Dictionary newDict) {
+ if (null == newDict) return;
+ if (mDictionaries.contains(newDict)) {
+ Log.w(TAG, "This collection already contains this dictionary: " + newDict);
+ }
+ mDictionaries.add(newDict);
+ }
+
+ // Warning: this is not thread-safe. Take necessary precaution when calling.
+ public void removeDictionary(final Dictionary dict) {
+ if (mDictionaries.contains(dict)) {
+ mDictionaries.remove(dict);
+ } else {
+ Log.w(TAG, "This collection does not contain this dictionary: " + dict);
+ }
}
}
diff --git a/java/src/com/android/inputmethod/latin/LatinIME.java b/java/src/com/android/inputmethod/latin/LatinIME.java
index 7ba7f7d27..60b436f69 100644
--- a/java/src/com/android/inputmethod/latin/LatinIME.java
+++ b/java/src/com/android/inputmethod/latin/LatinIME.java
@@ -157,6 +157,22 @@ public class LatinIME extends InputMethodServiceCompatWrapper implements Keyboar
SUGGESTION_VISIBILILTY_HIDE_VALUE
};
+ // Magic space: a space that should disappear on space/apostrophe insertion, move after the
+ // punctuation on punctuation insertion, and become a real space on alpha char insertion.
+ // Weak space: a space that should be swapped only by suggestion strip punctuation.
+ // Double space: the state where the user pressed space twice quickly, which LatinIME
+ // resolved as period-space. Undoing this converts the period to a space.
+ // Swap punctuation: the state where a (weak or magic) space and a punctuation from the
+ // suggestion strip have just been swapped. Undoing this swaps them back.
+ private static final int SPACE_STATE_NONE = 0;
+ private static final int SPACE_STATE_DOUBLE = 1;
+ private static final int SPACE_STATE_SWAP_PUNCTUATION = 2;
+ private static final int SPACE_STATE_MAGIC = 3;
+ private static final int SPACE_STATE_WEAK = 4;
+
+ // Current space state of the input method. This can be any of the above constants.
+ private int mSpaceState;
+
private Settings.Values mSettingsValues;
private View mExtractArea;
@@ -177,7 +193,7 @@ public class LatinIME extends InputMethodServiceCompatWrapper implements Keyboar
private UserDictionary mUserDictionary;
private UserBigramDictionary mUserBigramDictionary;
private UserUnigramDictionary mUserUnigramDictionary;
- private boolean mIsUserDictionaryAvaliable;
+ private boolean mIsUserDictionaryAvailable;
// TODO: Create an inner class to group options and pseudo-options to improve readability.
// These variables are initialized according to the {@link EditorInfo#inputType}.
@@ -190,12 +206,6 @@ public class LatinIME extends InputMethodServiceCompatWrapper implements Keyboar
private WordComposer mWordComposer = new WordComposer();
private CharSequence mBestWord;
private boolean mHasUncommittedTypedChars;
- // Magic space: a space that should disappear on space/apostrophe insertion, move after the
- // punctuation on punctuation insertion, and become a real space on alpha char insertion.
- private boolean mJustAddedMagicSpace; // This indicates whether the last char is a magic space.
- // This indicates whether the last keypress resulted in processing of double space replacement
- // with period-space.
- private boolean mJustReplacedDoubleSpace;
private int mCorrectionMode;
private int mCommittedLength;
@@ -241,9 +251,8 @@ public class LatinIME extends InputMethodServiceCompatWrapper implements Keyboar
private static final int MSG_FADEOUT_LANGUAGE_ON_SPACEBAR = 3;
private static final int MSG_DISMISS_LANGUAGE_ON_SPACEBAR = 4;
private static final int MSG_SPACE_TYPED = 5;
- private static final int MSG_KEY_TYPED = 6;
- private static final int MSG_SET_BIGRAM_PREDICTIONS = 7;
- private static final int MSG_PENDING_IMS_CALLBACK = 8;
+ private static final int MSG_SET_BIGRAM_PREDICTIONS = 6;
+ private static final int MSG_PENDING_IMS_CALLBACK = 7;
private int mDelayBeforeFadeoutLanguageOnSpacebar;
private int mDelayUpdateSuggestions;
@@ -251,7 +260,6 @@ public class LatinIME extends InputMethodServiceCompatWrapper implements Keyboar
private int mDurationOfFadeoutLanguageOnSpacebar;
private float mFinalFadeoutFactorOfLanguageOnSpacebar;
private long mDoubleSpacesTurnIntoPeriodTimeout;
- private long mIgnoreSpecialKeyTimeout;
public UIHandler(LatinIME outerInstance) {
super(outerInstance);
@@ -271,8 +279,6 @@ public class LatinIME extends InputMethodServiceCompatWrapper implements Keyboar
R.integer.config_final_fadeout_percentage_of_language_on_spacebar) / 100.0f;
mDoubleSpacesTurnIntoPeriodTimeout = res.getInteger(
R.integer.config_double_spaces_turn_into_period_timeout);
- mIgnoreSpecialKeyTimeout = res.getInteger(
- R.integer.config_ignore_special_key_timeout);
}
@Override
@@ -384,28 +390,22 @@ public class LatinIME extends InputMethodServiceCompatWrapper implements Keyboar
return hasMessages(MSG_SPACE_TYPED);
}
- public void startKeyTypedTimer() {
- removeMessages(MSG_KEY_TYPED);
- sendMessageDelayed(obtainMessage(MSG_KEY_TYPED), mIgnoreSpecialKeyTimeout);
- }
-
- public boolean isIgnoringSpecialKey() {
- return hasMessages(MSG_KEY_TYPED);
- }
-
// Working variables for the following methods.
private boolean mIsOrientationChanging;
private boolean mPendingSuccesiveImsCallback;
private boolean mHasPendingStartInput;
private boolean mHasPendingFinishInputView;
private boolean mHasPendingFinishInput;
+ private EditorInfo mAppliedEditorInfo;
public void startOrientationChanging() {
removeMessages(MSG_PENDING_IMS_CALLBACK);
resetPendingImsCallback();
mIsOrientationChanging = true;
final LatinIME latinIme = getOuterInstance();
- latinIme.mKeyboardSwitcher.saveKeyboardState();
+ if (latinIme.isInputViewShown()) {
+ latinIme.mKeyboardSwitcher.saveKeyboardState();
+ }
}
private void resetPendingImsCallback() {
@@ -414,18 +414,18 @@ public class LatinIME extends InputMethodServiceCompatWrapper implements Keyboar
mHasPendingStartInput = false;
}
- private void executePendingImsCallback(LatinIME latinIme, EditorInfo attribute,
+ private void executePendingImsCallback(LatinIME latinIme, EditorInfo editorInfo,
boolean restarting) {
if (mHasPendingFinishInputView)
latinIme.onFinishInputViewInternal(mHasPendingFinishInput);
if (mHasPendingFinishInput)
latinIme.onFinishInputInternal();
if (mHasPendingStartInput)
- latinIme.onStartInputInternal(attribute, restarting);
+ latinIme.onStartInputInternal(editorInfo, restarting);
resetPendingImsCallback();
}
- public void onStartInput(EditorInfo attribute, boolean restarting) {
+ public void onStartInput(EditorInfo editorInfo, boolean restarting) {
if (hasMessages(MSG_PENDING_IMS_CALLBACK)) {
// Typically this is the second onStartInput after orientation changed.
mHasPendingStartInput = true;
@@ -436,27 +436,28 @@ public class LatinIME extends InputMethodServiceCompatWrapper implements Keyboar
mPendingSuccesiveImsCallback = true;
}
final LatinIME latinIme = getOuterInstance();
- executePendingImsCallback(latinIme, attribute, restarting);
- latinIme.onStartInputInternal(attribute, restarting);
+ executePendingImsCallback(latinIme, editorInfo, restarting);
+ latinIme.onStartInputInternal(editorInfo, restarting);
}
}
- public void onStartInputView(EditorInfo attribute, boolean restarting) {
- if (hasMessages(MSG_PENDING_IMS_CALLBACK)) {
- // Typically this is the second onStartInputView after orientation changed.
- resetPendingImsCallback();
- } else {
- if (mPendingSuccesiveImsCallback) {
- // This is the first onStartInputView after orientation changed.
- mPendingSuccesiveImsCallback = false;
- resetPendingImsCallback();
- sendMessageDelayed(obtainMessage(MSG_PENDING_IMS_CALLBACK),
- PENDING_IMS_CALLBACK_DURATION);
- }
- final LatinIME latinIme = getOuterInstance();
- executePendingImsCallback(latinIme, attribute, restarting);
- latinIme.onStartInputViewInternal(attribute, restarting);
- }
+ public void onStartInputView(EditorInfo editorInfo, boolean restarting) {
+ if (hasMessages(MSG_PENDING_IMS_CALLBACK) && editorInfo == mAppliedEditorInfo) {
+ // Typically this is the second onStartInputView after orientation changed.
+ resetPendingImsCallback();
+ } else {
+ if (mPendingSuccesiveImsCallback) {
+ // This is the first onStartInputView after orientation changed.
+ mPendingSuccesiveImsCallback = false;
+ resetPendingImsCallback();
+ sendMessageDelayed(obtainMessage(MSG_PENDING_IMS_CALLBACK),
+ PENDING_IMS_CALLBACK_DURATION);
+ }
+ final LatinIME latinIme = getOuterInstance();
+ executePendingImsCallback(latinIme, editorInfo, restarting);
+ latinIme.onStartInputViewInternal(editorInfo, restarting);
+ mAppliedEditorInfo = editorInfo;
+ }
}
public void onFinishInputView(boolean finishingInput) {
@@ -466,6 +467,7 @@ public class LatinIME extends InputMethodServiceCompatWrapper implements Keyboar
} else {
final LatinIME latinIme = getOuterInstance();
latinIme.onFinishInputViewInternal(finishingInput);
+ mAppliedEditorInfo = null;
}
}
@@ -572,7 +574,7 @@ public class LatinIME extends InputMethodServiceCompatWrapper implements Keyboar
mUserDictionary = new UserDictionary(this, localeStr);
mSuggest.setUserDictionary(mUserDictionary);
- mIsUserDictionaryAvaliable = mUserDictionary.isEnabled();
+ mIsUserDictionaryAvailable = mUserDictionary.isEnabled();
resetContactsDictionary(oldContactsDictionary);
@@ -693,13 +695,13 @@ public class LatinIME extends InputMethodServiceCompatWrapper implements Keyboar
}
@Override
- public void onStartInput(EditorInfo attribute, boolean restarting) {
- mHandler.onStartInput(attribute, restarting);
+ public void onStartInput(EditorInfo editorInfo, boolean restarting) {
+ mHandler.onStartInput(editorInfo, restarting);
}
@Override
- public void onStartInputView(EditorInfo attribute, boolean restarting) {
- mHandler.onStartInputView(attribute, restarting);
+ public void onStartInputView(EditorInfo editorInfo, boolean restarting) {
+ mHandler.onStartInputView(editorInfo, restarting);
}
@Override
@@ -712,19 +714,19 @@ public class LatinIME extends InputMethodServiceCompatWrapper implements Keyboar
mHandler.onFinishInput();
}
- private void onStartInputInternal(EditorInfo attribute, boolean restarting) {
- super.onStartInput(attribute, restarting);
+ private void onStartInputInternal(EditorInfo editorInfo, boolean restarting) {
+ super.onStartInput(editorInfo, restarting);
}
- private void onStartInputViewInternal(EditorInfo attribute, boolean restarting) {
- super.onStartInputView(attribute, restarting);
+ private void onStartInputViewInternal(EditorInfo editorInfo, boolean restarting) {
+ super.onStartInputView(editorInfo, restarting);
final KeyboardSwitcher switcher = mKeyboardSwitcher;
LatinKeyboardView inputView = switcher.getKeyboardView();
if (DEBUG) {
- Log.d(TAG, "onStartInputView: attribute:" + ((attribute == null) ? "none"
+ Log.d(TAG, "onStartInputView: editorInfo:" + ((editorInfo == null) ? "none"
: String.format("inputType=0x%08x imeOptions=0x%08x",
- attribute.inputType, attribute.imeOptions)));
+ editorInfo.inputType, editorInfo.imeOptions)));
}
// In landscape mode, this method gets called without the input view being created.
if (inputView == null) {
@@ -734,7 +736,7 @@ public class LatinIME extends InputMethodServiceCompatWrapper implements Keyboar
// Forward this event to the accessibility utilities, if enabled.
final AccessibilityUtils accessUtils = AccessibilityUtils.getInstance();
if (accessUtils.isTouchExplorationEnabled()) {
- accessUtils.onStartInputViewInternal(attribute, restarting);
+ accessUtils.onStartInputViewInternal(editorInfo, restarting);
}
mSubtypeSwitcher.updateParametersOnStartInputView();
@@ -745,22 +747,21 @@ public class LatinIME extends InputMethodServiceCompatWrapper implements Keyboar
// know now whether this is a password text field, because we need to know now whether we
// want to enable the voice button.
final VoiceProxy voiceIme = mVoiceProxy;
- final int inputType = (attribute != null) ? attribute.inputType : 0;
+ final int inputType = (editorInfo != null) ? editorInfo.inputType : 0;
voiceIme.resetVoiceStates(InputTypeCompatUtils.isPasswordInputType(inputType)
|| InputTypeCompatUtils.isVisiblePasswordInputType(inputType));
// The EditorInfo might have a flag that affects fullscreen mode.
// Note: This call should be done by InputMethodService?
updateFullscreenMode();
- initializeInputAttributes(attribute);
+ initializeInputAttributes(editorInfo);
inputView.closing();
mEnteredText = null;
mComposingStringBuilder.setLength(0);
mHasUncommittedTypedChars = false;
mDeleteCount = 0;
- mJustAddedMagicSpace = false;
- mJustReplacedDoubleSpace = false;
+ mSpaceState = SPACE_STATE_NONE;
loadSettings();
updateCorrectionMode();
@@ -769,12 +770,12 @@ public class LatinIME extends InputMethodServiceCompatWrapper implements Keyboar
if (mSuggest != null && mSettingsValues.mAutoCorrectEnabled) {
mSuggest.setAutoCorrectionThreshold(mSettingsValues.mAutoCorrectionThreshold);
}
- mVoiceProxy.loadSettings(attribute, mPrefs);
+ mVoiceProxy.loadSettings(editorInfo, mPrefs);
// This will work only when the subtype is not supported.
LanguageSwitcherProxy.loadSettings();
if (mSubtypeSwitcher.isKeyboardMode()) {
- switcher.loadKeyboard(attribute, mSettingsValues);
+ switcher.loadKeyboard(editorInfo, mSettingsValues);
}
if (mSuggestionsView != null)
@@ -783,6 +784,7 @@ public class LatinIME extends InputMethodServiceCompatWrapper implements Keyboar
isSuggestionsStripVisible(), /* needsInputViewShown */ false);
// Delay updating suggestions because keyboard input view may not be shown at this point.
mHandler.postUpdateSuggestions();
+ mHandler.cancelDoubleSpacesTimer();
inputView.setKeyPreviewPopupEnabled(mSettingsValues.mKeyPreviewPopupOn,
mSettingsValues.mKeyPreviewPopupDismissDelay);
@@ -793,10 +795,10 @@ public class LatinIME extends InputMethodServiceCompatWrapper implements Keyboar
if (TRACE) Debug.startMethodTracing("/data/trace/latinime");
}
- private void initializeInputAttributes(EditorInfo attribute) {
- if (attribute == null)
+ private void initializeInputAttributes(EditorInfo editorInfo) {
+ if (editorInfo == null)
return;
- final int inputType = attribute.inputType;
+ final int inputType = editorInfo.inputType;
if (inputType == InputType.TYPE_NULL) {
// TODO: We should honor TYPE_NULL specification.
Log.i(TAG, "InputType.TYPE_NULL is specified");
@@ -805,7 +807,7 @@ public class LatinIME extends InputMethodServiceCompatWrapper implements Keyboar
final int variation = inputType & InputType.TYPE_MASK_VARIATION;
if (inputClass == 0) {
Log.w(TAG, String.format("Unexpected input class: inputType=0x%08x imeOptions=0x%08x",
- inputType, attribute.imeOptions));
+ inputType, editorInfo.imeOptions));
}
mInsertSpaceOnPickSuggestionManually = false;
@@ -921,6 +923,13 @@ public class LatinIME extends InputMethodServiceCompatWrapper implements Keyboar
|| newSelEnd != candidatesEnd) && mLastSelectionStart != newSelStart;
final boolean candidatesCleared = candidatesStart == -1 && candidatesEnd == -1;
if (!mExpectingUpdateSelection) {
+ if (SPACE_STATE_WEAK == mSpaceState) {
+ // Test for no WEAK_SPACE action because there is a race condition that may end up
+ // in coming here on a normal key press. We set this to NONE because after
+ // a cursor move, we don't want the suggestion strip to swap the space with the
+ // newly inserted punctuation.
+ mSpaceState = SPACE_STATE_NONE;
+ }
if (((mComposingStringBuilder.length() > 0 && mHasUncommittedTypedChars)
|| mVoiceProxy.isVoiceInputHighlighted())
&& (selectionChanged || candidatesCleared)) {
@@ -938,22 +947,19 @@ public class LatinIME extends InputMethodServiceCompatWrapper implements Keyboar
TextEntryState.reset();
updateSuggestions();
}
- mJustAddedMagicSpace = false; // The user moved the cursor.
- mJustReplacedDoubleSpace = false;
}
mExpectingUpdateSelection = false;
mHandler.postUpdateShiftKeyState();
+ // TODO: Decide to call restartSuggestionsOnWordBeforeCursorIfAtEndOfWord() or not
+ // here. It would probably be too expensive to call directly here but we may want to post a
+ // message to delay it. The point would be to unify behavior between backspace to the
+ // end of a word and manually put the pointer at the end of the word.
// Make a note of the cursor position
mLastSelectionStart = newSelStart;
mLastSelectionEnd = newSelEnd;
}
- public void setLastSelection(int start, int end) {
- mLastSelectionStart = start;
- mLastSelectionEnd = end;
- }
-
/**
* This is called when the user has clicked on the extracted text view,
* when running in fullscreen mode. The default implementation hides
@@ -1164,25 +1170,22 @@ public class LatinIME extends InputMethodServiceCompatWrapper implements Keyboar
return false;
}
- private void swapSwapperAndSpace() {
- final InputConnection ic = getCurrentInputConnection();
- if (ic == null) return;
+ // "ic" may be null
+ private void swapSwapperAndSpaceWhileInBatchEdit(final InputConnection ic) {
+ if (null == ic) return;
CharSequence lastTwo = ic.getTextBeforeCursor(2, 0);
// It is guaranteed lastTwo.charAt(1) is a swapper - else this method is not called.
if (lastTwo != null && lastTwo.length() == 2
&& lastTwo.charAt(0) == Keyboard.CODE_SPACE) {
- ic.beginBatchEdit();
ic.deleteSurroundingText(2, 0);
ic.commitText(lastTwo.charAt(1) + " ", 1);
- ic.endBatchEdit();
mKeyboardSwitcher.updateShiftState();
}
}
- private void maybeDoubleSpace() {
- if (mCorrectionMode == Suggest.CORRECTION_NONE) return;
- final InputConnection ic = getCurrentInputConnection();
- if (ic == null) return;
+ private boolean maybeDoubleSpaceWhileInBatchEdit(final InputConnection ic) {
+ if (mCorrectionMode == Suggest.CORRECTION_NONE) return false;
+ if (ic == null) return false;
final CharSequence lastThree = ic.getTextBeforeCursor(3, 0);
if (lastThree != null && lastThree.length() == 3
&& Utils.canBeFollowedByPeriod(lastThree.charAt(0))
@@ -1190,22 +1193,19 @@ public class LatinIME extends InputMethodServiceCompatWrapper implements Keyboar
&& lastThree.charAt(2) == Keyboard.CODE_SPACE
&& mHandler.isAcceptingDoubleSpaces()) {
mHandler.cancelDoubleSpacesTimer();
- ic.beginBatchEdit();
ic.deleteSurroundingText(2, 0);
ic.commitText(". ", 1);
- ic.endBatchEdit();
mKeyboardSwitcher.updateShiftState();
- mJustReplacedDoubleSpace = true;
- } else {
- mHandler.startDoubleSpacesTimer();
+ return true;
}
+ return false;
}
- // "ic" must not null
- private void maybeRemovePreviousPeriod(final InputConnection ic, CharSequence text) {
+ // "ic" must not be null
+ private static void maybeRemovePreviousPeriod(final InputConnection ic, CharSequence text) {
// When the text's first character is '.', remove the previous period
// if there is one.
- CharSequence lastOne = ic.getTextBeforeCursor(1, 0);
+ final CharSequence lastOne = ic.getTextBeforeCursor(1, 0);
if (lastOne != null && lastOne.length() == 1
&& lastOne.charAt(0) == Keyboard.CODE_PERIOD
&& text.charAt(0) == Keyboard.CODE_PERIOD) {
@@ -1213,11 +1213,10 @@ public class LatinIME extends InputMethodServiceCompatWrapper implements Keyboar
}
}
- private void removeTrailingSpace() {
- final InputConnection ic = getCurrentInputConnection();
+ // "ic" may be null
+ private static void removeTrailingSpaceWhileInBatchEdit(final InputConnection ic) {
if (ic == null) return;
-
- CharSequence lastOne = ic.getTextBeforeCursor(1, 0);
+ final CharSequence lastOne = ic.getTextBeforeCursor(1, 0);
if (lastOne != null && lastOne.length() == 1
&& lastOne.charAt(0) == Keyboard.CODE_SPACE) {
ic.deleteSurroundingText(1, 0);
@@ -1233,12 +1232,8 @@ public class LatinIME extends InputMethodServiceCompatWrapper implements Keyboar
return true;
}
- private boolean isAlphabet(int code) {
- if (Character.isLetter(code)) {
- return true;
- } else {
- return false;
- }
+ private static boolean isAlphabet(int code) {
+ return Character.isLetter(code);
}
private void onSettingsKeyPressed() {
@@ -1254,6 +1249,7 @@ public class LatinIME extends InputMethodServiceCompatWrapper implements Keyboar
// Virtual codes representing custom requests. These are used in onCustomRequest() below.
public static final int CODE_SHOW_INPUT_METHOD_PICKER = 1;
+ public static final int CODE_HAPTIC_AND_AUDIO_FEEDBACK = 2;
@Override
public boolean onCustomRequest(int requestCode) {
@@ -1265,6 +1261,9 @@ public class LatinIME extends InputMethodServiceCompatWrapper implements Keyboar
return true;
}
return false;
+ case CODE_HAPTIC_AND_AUDIO_FEEDBACK:
+ hapticAndAudioFeedback(Keyboard.CODE_UNSPECIFIED);
+ return true;
}
return false;
}
@@ -1273,6 +1272,28 @@ public class LatinIME extends InputMethodServiceCompatWrapper implements Keyboar
return mOptionsDialog != null && mOptionsDialog.isShowing();
}
+ private void insertPunctuationFromSuggestionStrip(final InputConnection ic, final int code) {
+ final CharSequence beforeText = ic != null ? ic.getTextBeforeCursor(1, 0) : null;
+ final int toLeft = TextUtils.isEmpty(beforeText) ? 0 : beforeText.charAt(0);
+ final boolean shouldRegisterSwapPunctuation;
+ // If we have a space left of the cursor and it's a weak or a magic space, then we should
+ // swap it, and override the space state with SPACESTATE_SWAP_PUNCTUATION.
+ // To swap it, we fool handleSeparator to think the previous space state was a
+ // magic space.
+ if (Keyboard.CODE_SPACE == toLeft && mSpaceState == SPACE_STATE_WEAK) {
+ mSpaceState = SPACE_STATE_MAGIC;
+ shouldRegisterSwapPunctuation = true;
+ } else {
+ shouldRegisterSwapPunctuation = false;
+ }
+ onCodeInput(code, new int[] { code },
+ KeyboardActionListener.NOT_A_TOUCH_COORDINATE,
+ KeyboardActionListener.NOT_A_TOUCH_COORDINATE);
+ if (shouldRegisterSwapPunctuation) {
+ mSpaceState = SPACE_STATE_SWAP_PUNCTUATION;
+ }
+ }
+
// Implementation of {@link KeyboardActionListener}.
@Override
public void onCodeInput(int primaryCode, int[] keyCodes, int x, int y) {
@@ -1283,12 +1304,22 @@ public class LatinIME extends InputMethodServiceCompatWrapper implements Keyboar
mLastKeyTime = when;
final KeyboardSwitcher switcher = mKeyboardSwitcher;
final boolean distinctMultiTouch = switcher.hasDistinctMultitouch();
- final boolean lastStateOfJustReplacedDoubleSpace = mJustReplacedDoubleSpace;
- mJustReplacedDoubleSpace = false;
- boolean shouldStartKeyTypedTimer = true;
+ // 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
+ // all inputs that do not result in a special state. Each character handling is then
+ // free to override the state as they see fit.
+ final int spaceState = mSpaceState;
+
+ // TODO: Consolidate the double space timer, mLastKeyTime, and the space state.
+ if (primaryCode != Keyboard.CODE_SPACE) {
+ mHandler.cancelDoubleSpacesTimer();
+ }
+
switch (primaryCode) {
case Keyboard.CODE_DELETE:
- handleBackspace(lastStateOfJustReplacedDoubleSpace);
+ mSpaceState = SPACE_STATE_NONE;
+ handleBackspace(spaceState);
mDeleteCount++;
mExpectingUpdateSelection = true;
LatinImeLogger.logOnDelete();
@@ -1298,14 +1329,12 @@ public class LatinIME extends InputMethodServiceCompatWrapper implements Keyboar
if (!distinctMultiTouch) {
switcher.toggleShift();
}
- shouldStartKeyTypedTimer = false;
break;
case Keyboard.CODE_SWITCH_ALPHA_SYMBOL:
// Symbol key is handled in onPress() when device has distinct multi-touch panel.
if (!distinctMultiTouch) {
switcher.changeKeyboardMode();
}
- shouldStartKeyTypedTimer = false;
break;
case Keyboard.CODE_CANCEL:
if (!isShowingOptionDialog()) {
@@ -1313,24 +1342,14 @@ public class LatinIME extends InputMethodServiceCompatWrapper implements Keyboar
}
break;
case Keyboard.CODE_SETTINGS:
- if (!mHandler.isIgnoringSpecialKey()) {
- onSettingsKeyPressed();
- }
- shouldStartKeyTypedTimer = false;
+ onSettingsKeyPressed();
break;
case Keyboard.CODE_CAPSLOCK:
switcher.toggleCapsLock();
- //$FALL-THROUGH$
- case Keyboard.CODE_HAPTIC_AND_AUDIO_FEEDBACK_ONLY:
- // Dummy code for haptic and audio feedbacks.
- vibrate();
- playKeyClick(primaryCode);
+ hapticAndAudioFeedback(primaryCode);
break;
case Keyboard.CODE_SHORTCUT:
- if (!mHandler.isIgnoringSpecialKey()) {
- mSubtypeSwitcher.switchToShortcutIME();
- }
- shouldStartKeyTypedTimer = false;
+ mSubtypeSwitcher.switchToShortcutIME();
break;
case Keyboard.CODE_TAB:
handleTab();
@@ -1344,10 +1363,11 @@ public class LatinIME extends InputMethodServiceCompatWrapper implements Keyboar
// To sum it up: do not update mExpectingUpdateSelection here.
break;
default:
+ mSpaceState = SPACE_STATE_NONE;
if (mSettingsValues.isWordSeparator(primaryCode)) {
- handleSeparator(primaryCode, x, y);
+ handleSeparator(primaryCode, x, y, spaceState);
} else {
- handleCharacter(primaryCode, keyCodes, x, y);
+ handleCharacter(primaryCode, keyCodes, x, y, spaceState);
}
mExpectingUpdateSelection = true;
break;
@@ -1355,9 +1375,6 @@ public class LatinIME extends InputMethodServiceCompatWrapper implements Keyboar
switcher.onKey(primaryCode);
// Reset after any single keystroke
mEnteredText = null;
- if (shouldStartKeyTypedTimer) {
- mHandler.startKeyTypedTimer();
- }
}
@Override
@@ -1372,9 +1389,8 @@ public class LatinIME extends InputMethodServiceCompatWrapper implements Keyboar
ic.endBatchEdit();
mKeyboardSwitcher.updateShiftState();
mKeyboardSwitcher.onKey(Keyboard.CODE_DUMMY);
- mJustAddedMagicSpace = false;
+ mSpaceState = SPACE_STATE_NONE;
mEnteredText = text;
- mHandler.startKeyTypedTimer();
}
@Override
@@ -1383,7 +1399,7 @@ public class LatinIME extends InputMethodServiceCompatWrapper implements Keyboar
mKeyboardSwitcher.onCancelInput();
}
- private void handleBackspace(boolean justReplacedDoubleSpace) {
+ private void handleBackspace(final int spaceState) {
if (mVoiceProxy.logAndRevertVoiceInput()) return;
final InputConnection ic = getCurrentInputConnection();
@@ -1421,15 +1437,24 @@ public class LatinIME extends InputMethodServiceCompatWrapper implements Keyboar
}
mHandler.postUpdateShiftKeyState();
+ // TODO: Merge space state with TextEntryState
TextEntryState.backspace();
if (TextEntryState.isUndoCommit()) {
revertLastWord(ic);
ic.endBatchEdit();
return;
}
- if (justReplacedDoubleSpace) {
+ if (SPACE_STATE_DOUBLE == spaceState) {
if (revertDoubleSpace(ic)) {
ic.endBatchEdit();
+ // No need to reset mSpaceState, it has already be done (that's why we
+ // receive it as a parameter)
+ return;
+ }
+ } else if (SPACE_STATE_SWAP_PUNCTUATION == spaceState) {
+ if (revertSwapPunctuation(ic)) {
+ ic.endBatchEdit();
+ // Likewise
return;
}
}
@@ -1447,10 +1472,11 @@ public class LatinIME extends InputMethodServiceCompatWrapper implements Keyboar
// inconsistent with backspacing after selecting other suggestions.
revertLastWord(ic);
} else {
- sendDownUpKeyEvents(KeyEvent.KEYCODE_DEL);
+ ic.deleteSurroundingText(1, 0);
if (mDeleteCount > DELETE_ACCELERATE_AT) {
- sendDownUpKeyEvents(KeyEvent.KEYCODE_DEL);
+ ic.deleteSurroundingText(1, 0);
}
+ restartSuggestionsOnWordBeforeCursorIfAtEndOfWord(ic);
}
}
ic.endBatchEdit();
@@ -1479,18 +1505,24 @@ public class LatinIME extends InputMethodServiceCompatWrapper implements Keyboar
}
}
- private void handleCharacter(int primaryCode, int[] keyCodes, int x, int y) {
+ private void handleCharacter(final int primaryCode, final int[] keyCodes, final int x,
+ final int y, final int spaceState) {
mVoiceProxy.handleCharacter();
- if (mJustAddedMagicSpace && mSettingsValues.isMagicSpaceStripper(primaryCode)) {
- removeTrailingSpace();
+ final InputConnection ic = getCurrentInputConnection();
+ if (ic != null) ic.beginBatchEdit();
+ if (SPACE_STATE_MAGIC == spaceState
+ && mSettingsValues.isMagicSpaceStripper(primaryCode)) {
+ removeTrailingSpaceWhileInBatchEdit(ic);
}
int code = primaryCode;
if ((isAlphabet(code) || mSettingsValues.isSymbolExcludedFromWordSeparators(code))
&& isSuggestionsRequested() && !isCursorTouchingWord()) {
if (!mHasUncommittedTypedChars) {
- mHasUncommittedTypedChars = true;
+ // Reset entirely the composing state anyway, then start composing a new word unless
+ // the character is a single quote.
+ mHasUncommittedTypedChars = (Keyboard.CODE_SINGLE_QUOTE != code);
mComposingStringBuilder.setLength(0);
mWordComposer.reset();
clearSuggestions();
@@ -1501,6 +1533,7 @@ public class LatinIME extends InputMethodServiceCompatWrapper implements Keyboar
if (switcher.isShiftedOrShiftLocked()) {
if (keyCodes == null || keyCodes[0] < Character.MIN_CODE_POINT
|| keyCodes[0] > Character.MAX_CODE_POINT) {
+ if (null != ic) ic.endBatchEdit();
return;
}
code = keyCodes[0];
@@ -1514,6 +1547,7 @@ public class LatinIME extends InputMethodServiceCompatWrapper implements Keyboar
} else {
// Some keys, such as [eszett], have upper case as multi-characters.
onTextInput(upperCaseString);
+ if (null != ic) ic.endBatchEdit();
return;
}
}
@@ -1521,7 +1555,6 @@ public class LatinIME extends InputMethodServiceCompatWrapper implements Keyboar
if (mHasUncommittedTypedChars) {
mComposingStringBuilder.append((char) code);
mWordComposer.add(code, keyCodes, x, y);
- final InputConnection ic = getCurrentInputConnection();
if (ic != null) {
// If it's the first letter, make note of auto-caps state
if (mWordComposer.size() == 1) {
@@ -1539,18 +1572,19 @@ public class LatinIME extends InputMethodServiceCompatWrapper implements Keyboar
} else {
sendKeyChar((char)code);
}
- if (mJustAddedMagicSpace && mSettingsValues.isMagicSpaceSwapper(primaryCode)) {
- swapSwapperAndSpace();
- } else {
- mJustAddedMagicSpace = false;
+ if (SPACE_STATE_MAGIC == spaceState
+ && mSettingsValues.isMagicSpaceSwapper(primaryCode)) {
+ if (null != ic) swapSwapperAndSpaceWhileInBatchEdit(ic);
}
switcher.updateShiftState();
if (LatinIME.PERF_DEBUG) measureCps();
TextEntryState.typedCharacter((char) code, mSettingsValues.isWordSeparator(code), x, y);
+ if (null != ic) ic.endBatchEdit();
}
- private void handleSeparator(int primaryCode, int x, int y) {
+ private void handleSeparator(final int primaryCode, final int x, final int y,
+ final int spaceState) {
mVoiceProxy.handleSeparator();
mComposingStateManager.onFinishComposingText();
@@ -1580,21 +1614,49 @@ public class LatinIME extends InputMethodServiceCompatWrapper implements Keyboar
}
}
- if (mJustAddedMagicSpace) {
+ final boolean swapMagicSpace;
+ if (Keyboard.CODE_ENTER == primaryCode && (SPACE_STATE_MAGIC == spaceState
+ || SPACE_STATE_SWAP_PUNCTUATION == spaceState)) {
+ removeTrailingSpaceWhileInBatchEdit(ic);
+ swapMagicSpace = false;
+ } else if (SPACE_STATE_MAGIC == spaceState) {
if (mSettingsValues.isMagicSpaceSwapper(primaryCode)) {
- sendKeyChar((char)primaryCode);
- swapSwapperAndSpace();
+ swapMagicSpace = true;
} else {
- if (mSettingsValues.isMagicSpaceStripper(primaryCode)) removeTrailingSpace();
- sendKeyChar((char)primaryCode);
- mJustAddedMagicSpace = false;
+ swapMagicSpace = false;
+ if (mSettingsValues.isMagicSpaceStripper(primaryCode)) {
+ removeTrailingSpaceWhileInBatchEdit(ic);
+ }
}
} else {
- sendKeyChar((char)primaryCode);
+ swapMagicSpace = false;
}
- if (isSuggestionsRequested() && primaryCode == Keyboard.CODE_SPACE) {
- maybeDoubleSpace();
+ sendKeyChar((char)primaryCode);
+
+ if (Keyboard.CODE_SPACE == primaryCode) {
+ if (isSuggestionsRequested()) {
+ if (maybeDoubleSpaceWhileInBatchEdit(ic)) {
+ mSpaceState = SPACE_STATE_DOUBLE;
+ } else if (!isShowingPunctuationList()) {
+ mSpaceState = SPACE_STATE_WEAK;
+ }
+ }
+
+ mHandler.startDoubleSpacesTimer();
+ if (!isCursorTouchingWord()) {
+ mHandler.cancelUpdateSuggestions();
+ mHandler.postUpdateBigramPredictions();
+ }
+ } else {
+ if (swapMagicSpace) {
+ swapSwapperAndSpaceWhileInBatchEdit(ic);
+ mSpaceState = SPACE_STATE_MAGIC;
+ }
+
+ // Set punctuation right away. onUpdateSelection will fire but tests whether it is
+ // already displayed or not, so it's okay.
+ setPunctuationSuggestions();
}
TextEntryState.typedCharacter((char) primaryCode, true, x, y);
@@ -1607,16 +1669,6 @@ public class LatinIME extends InputMethodServiceCompatWrapper implements Keyboar
ic, mLastSelectionEnd - typedWord.length(), typedWord, mBestWord);
}
}
- if (Keyboard.CODE_SPACE == primaryCode) {
- if (!isCursorTouchingWord()) {
- mHandler.cancelUpdateSuggestions();
- mHandler.postUpdateBigramPredictions();
- }
- } else {
- // Set punctuation right away. onUpdateSelection will fire but tests whether it is
- // already displayed or not, so it's okay.
- setPunctuationSuggestions();
- }
mKeyboardSwitcher.updateShiftState();
if (ic != null) {
ic.endBatchEdit();
@@ -1651,7 +1703,7 @@ public class LatinIME extends InputMethodServiceCompatWrapper implements Keyboar
public boolean isSuggestionsStripVisible() {
if (mSuggestionsView == null)
return false;
- if (mSuggestionsView.isShowingAddToDictionaryHint() || TextEntryState.isRecorrecting())
+ if (mSuggestionsView.isShowingAddToDictionaryHint())
return true;
if (!isShowingSuggestionsStrip())
return false;
@@ -1748,15 +1800,23 @@ public class LatinIME extends InputMethodServiceCompatWrapper implements Keyboar
// The whitelist should be case-insensitive, so it's not possible to be consistent with
// a boolean flag. Right now this is handled with a slight hack in
// WhitelistDictionary#shouldForciblyAutoCorrectFrom.
+ final int quotesCount = wordComposer.trailingSingleQuotesCount();
final boolean allowsToBeAutoCorrected = AutoCorrection.allowsToBeAutoCorrected(
- mSuggest.getUnigramDictionaries(), typedWord, preferCapitalization());
+ mSuggest.getUnigramDictionaries(),
+ // If the typed string ends with a single quote, for dictionary lookup purposes
+ // we behave as if the single quote was not here. Here, we are looking up the
+ // typed string in the dictionary (to avoid autocorrecting from an existing
+ // word, so for consistency this lookup should be made WITHOUT the trailing
+ // single quote.
+ quotesCount > 0
+ ? typedWord.subSequence(0, typedWord.length() - quotesCount) : typedWord,
+ preferCapitalization());
if (mCorrectionMode == Suggest.CORRECTION_FULL
|| mCorrectionMode == Suggest.CORRECTION_FULL_BIGRAM) {
autoCorrectionAvailable |= (!allowsToBeAutoCorrected);
}
// Don't auto-correct words with multiple capital letter
autoCorrectionAvailable &= !wordComposer.isMostlyCaps();
- autoCorrectionAvailable &= !TextEntryState.isRecorrecting();
// Basically, we update the suggestion strip only when suggestion count > 1. However,
// there is an exception: We update the suggestion strip whenever typed word's length
@@ -1829,7 +1889,6 @@ public class LatinIME extends InputMethodServiceCompatWrapper implements Keyboar
mVoiceProxy.flushAndLogAllTextModificationCounters(index, suggestion,
mSettingsValues.mWordSeparators);
- final boolean recorrecting = TextEntryState.isRecorrecting();
final InputConnection ic = getCurrentInputConnection();
if (ic != null) {
ic.beginBatchEdit();
@@ -1859,8 +1918,8 @@ public class LatinIME extends InputMethodServiceCompatWrapper implements Keyboar
LatinImeLogger.logOnManualSuggestion(
"", suggestion.toString(), index, suggestions.mWords);
// Find out whether the previous character is a space. If it is, as a special case
- // for punctuation entered through the suggestion strip, it should be considered
- // a magic space even if it was a normal space. This is meant to help in case the user
+ // for punctuation entered through the suggestion strip, it should be swapped
+ // if it was a magic or a weak space. This is meant to help in case the user
// pressed space on purpose of displaying the suggestion strip punctuation.
final int rawPrimaryCode = suggestion.charAt(0);
// Maybe apply the "bidi mirrored" conversions for parentheses
@@ -1868,15 +1927,8 @@ public class LatinIME extends InputMethodServiceCompatWrapper implements Keyboar
final boolean isRtl = keyboard != null && keyboard.mIsRtlKeyboard;
final int primaryCode = Key.getRtlParenthesisCode(rawPrimaryCode, isRtl);
- final CharSequence beforeText = ic != null ? ic.getTextBeforeCursor(1, 0) : "";
- final int toLeft = (ic == null || TextUtils.isEmpty(beforeText))
- ? 0 : beforeText.charAt(0);
- final boolean oldMagicSpace = mJustAddedMagicSpace;
- if (Keyboard.CODE_SPACE == toLeft) mJustAddedMagicSpace = true;
- onCodeInput(primaryCode, new int[] { primaryCode },
- KeyboardActionListener.NOT_A_TOUCH_COORDINATE,
- KeyboardActionListener.NOT_A_TOUCH_COORDINATE);
- mJustAddedMagicSpace = oldMagicSpace;
+ insertPunctuationFromSuggestionStrip(ic, primaryCode);
+ // TODO: the following endBatchEdit seems useless, check
if (ic != null) {
ic.endBatchEdit();
}
@@ -1900,7 +1952,7 @@ public class LatinIME extends InputMethodServiceCompatWrapper implements Keyboar
suggestion.toString(), index, suggestions.mWords);
TextEntryState.acceptedSuggestion(mComposingStringBuilder.toString(), suggestion);
// Follow it with a space
- if (mInsertSpaceOnPickSuggestionManually && !recorrecting) {
+ if (mInsertSpaceOnPickSuggestionManually) {
sendMagicSpace();
}
@@ -1920,13 +1972,11 @@ public class LatinIME extends InputMethodServiceCompatWrapper implements Keyboar
|| !AutoCorrection.isValidWord(
mSuggest.getUnigramDictionaries(), suggestion, true));
- if (!recorrecting) {
- // Fool the state watcher so that a subsequent backspace will not do a revert, unless
- // we just did a correction, in which case we need to stay in
- // TextEntryState.State.PICKED_SUGGESTION state.
- TextEntryState.typedCharacter((char) Keyboard.CODE_SPACE, true,
- WordComposer.NOT_A_COORDINATE, WordComposer.NOT_A_COORDINATE);
- }
+ // Fool the state watcher so that a subsequent backspace will not do a revert, unless
+ // we just did a correction, in which case we need to stay in
+ // TextEntryState.State.PICKED_SUGGESTION state.
+ TextEntryState.typedCharacter((char) Keyboard.CODE_SPACE, true,
+ WordComposer.NOT_A_COORDINATE, WordComposer.NOT_A_COORDINATE);
if (!showingAddToDictionaryHint) {
// If we're not showing the "Touch again to save", then show corrections again.
// In case the cursor position doesn't change, make sure we show the suggestions again.
@@ -1936,8 +1986,9 @@ public class LatinIME extends InputMethodServiceCompatWrapper implements Keyboar
// take a noticeable delay to update them which may feel uneasy.
}
if (showingAddToDictionaryHint) {
- if (mIsUserDictionaryAvaliable) {
- mSuggestionsView.showAddToDictionaryHint(suggestion);
+ if (mIsUserDictionaryAvailable) {
+ mSuggestionsView.showAddToDictionaryHint(
+ suggestion, mSettingsValues.mHintToSaveText);
} else {
mHandler.postUpdateSuggestions();
}
@@ -2068,13 +2119,60 @@ public class LatinIME extends InputMethodServiceCompatWrapper implements Keyboar
return false;
}
- // "ic" must not null
- private boolean sameAsTextBeforeCursor(final InputConnection ic, CharSequence text) {
+ // "ic" must not be null
+ private static boolean sameAsTextBeforeCursor(final InputConnection ic, CharSequence text) {
CharSequence beforeText = ic.getTextBeforeCursor(text.length(), 0);
return TextUtils.equals(text, beforeText);
}
- // "ic" must not null
+ // "ic" must not be null
+ /**
+ * Check if the cursor is actually at the end of a word. If so, restart suggestions on this
+ * word, else do nothing.
+ */
+ private void restartSuggestionsOnWordBeforeCursorIfAtEndOfWord(
+ final InputConnection ic) {
+ // Bail out if the cursor is not at the end of a word (cursor must be preceded by
+ // non-whitespace, non-separator, non-start-of-text)
+ // Example ("|" is the cursor here) : <SOL>"|a" " |a" " | " all get rejected here.
+ final CharSequence textBeforeCursor = ic.getTextBeforeCursor(1, 0);
+ if (TextUtils.isEmpty(textBeforeCursor)
+ || mSettingsValues.isWordSeparator(textBeforeCursor.charAt(0))) return;
+
+ // Bail out if the cursor is in the middle of a word (cursor must be followed by whitespace,
+ // separator or end of line/text)
+ // Example: "test|"<EOL> "te|st" get rejected here
+ final CharSequence textAfterCursor = ic.getTextAfterCursor(1, 0);
+ if (!TextUtils.isEmpty(textAfterCursor)
+ && !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);
+ if (TextUtils.isEmpty(word)) return;
+ if (word.length() == 1 && !Character.isLetter(word.charAt(0))) return;
+
+ // Okay, we are at the end of a word. Restart suggestions.
+ restartSuggestionsOnWordBeforeCursor(ic, word);
+ }
+
+ // "ic" must not be null
+ private void restartSuggestionsOnWordBeforeCursor(final InputConnection ic,
+ final CharSequence word) {
+ mWordComposer.setComposingWord(word, mKeyboardSwitcher.getLatinKeyboard());
+ mComposingStringBuilder.setLength(0);
+ mComposingStringBuilder.append(word);
+ // mBestWord will be set appropriately by updateSuggestions() called by the handler
+ mBestWord = null;
+ mHasUncommittedTypedChars = true;
+ mComposingStateManager.onStartComposingText();
+ TextEntryState.restartSuggestionsOnWordBeforeCursor();
+ ic.deleteSurroundingText(word.length(), 0);
+ ic.setComposingText(word, 1);
+ mHandler.postUpdateSuggestions();
+ }
+
+ // "ic" must not be null
private void revertLastWord(final InputConnection ic) {
if (mHasUncommittedTypedChars || mComposingStringBuilder.length() <= 0) {
sendDownUpKeyEvents(KeyEvent.KEYCODE_DEL);
@@ -2100,6 +2198,10 @@ public class LatinIME extends InputMethodServiceCompatWrapper implements Keyboar
// Clear composing text
mComposingStringBuilder.setLength(0);
} else {
+ // Note: this relies on the last word still being held in the WordComposer
+ // Note: in the interest of code simplicity, we may want to just call
+ // restartSuggestionsOnWordBeforeCursorIfAtEndOfWord instead, but retrieving
+ // the old WordComposer allows to reuse the actual typed coordinates.
mHasUncommittedTypedChars = true;
ic.setComposingText(mComposingStringBuilder, 1);
TextEntryState.backspace();
@@ -2108,7 +2210,7 @@ public class LatinIME extends InputMethodServiceCompatWrapper implements Keyboar
mHandler.postUpdateSuggestions();
}
- // "ic" must not null
+ // "ic" must not be null
private boolean revertDoubleSpace(final InputConnection ic) {
mHandler.cancelDoubleSpacesTimer();
// Here we test whether we indeed have a period and a space before us. This should not
@@ -2123,13 +2225,28 @@ public class LatinIME extends InputMethodServiceCompatWrapper implements Keyboar
return true;
}
+ private static boolean revertSwapPunctuation(final InputConnection ic) {
+ // Here we test whether we indeed have a space and something else before us. This should not
+ // be needed, but it's there just in case something went wrong.
+ final CharSequence textBeforeCursor = ic.getTextBeforeCursor(2, 0);
+ // NOTE: This does not work with surrogate pairs. Hopefully when the keyboard is able to
+ // enter surrogate pairs this code will have been removed.
+ if (Keyboard.CODE_SPACE != textBeforeCursor.charAt(1))
+ return false;
+ ic.beginBatchEdit();
+ ic.deleteSurroundingText(2, 0);
+ ic.commitText(" " + textBeforeCursor.subSequence(0, 1), 1);
+ ic.endBatchEdit();
+ return true;
+ }
+
public boolean isWordSeparator(int code) {
return mSettingsValues.isWordSeparator(code);
}
private void sendMagicSpace() {
sendKeyChar((char)Keyboard.CODE_SPACE);
- mJustAddedMagicSpace = true;
+ mSpaceState = SPACE_STATE_MAGIC;
mKeyboardSwitcher.updateShiftState();
}
@@ -2155,12 +2272,16 @@ public class LatinIME extends InputMethodServiceCompatWrapper implements Keyboar
loadSettings();
}
+ private void hapticAndAudioFeedback(int primaryCode) {
+ vibrate();
+ playKeyClick(primaryCode);
+ }
+
@Override
public void onPress(int primaryCode, boolean withSliding) {
final KeyboardSwitcher switcher = mKeyboardSwitcher;
if (switcher.isVibrateAndSoundFeedbackRequired()) {
- vibrate();
- playKeyClick(primaryCode);
+ hapticAndAudioFeedback(primaryCode);
}
final boolean distinctMultiTouch = switcher.hasDistinctMultitouch();
if (distinctMultiTouch && primaryCode == Keyboard.CODE_SHIFT) {
diff --git a/java/src/com/android/inputmethod/latin/Settings.java b/java/src/com/android/inputmethod/latin/Settings.java
index 773efe709..7d6efa584 100644
--- a/java/src/com/android/inputmethod/latin/Settings.java
+++ b/java/src/com/android/inputmethod/latin/Settings.java
@@ -109,6 +109,7 @@ public class Settings extends InputMethodSettingsActivity
public final String mSuggestPuncs;
public final SuggestedWords mSuggestPuncList;
private final String mSymbolsExcludedFromWordSeparators;
+ public final CharSequence mHintToSaveText;
// From preferences:
public final boolean mSoundOn; // Sound setting private to Latin IME (see mSilentModeOn)
@@ -158,6 +159,7 @@ public class Settings extends InputMethodSettingsActivity
mSuggestPuncs = res.getString(R.string.suggested_punctuations);
// TODO: it would be nice not to recreate this each time we change the configuration
mSuggestPuncList = createSuggestPuncList(mSuggestPuncs);
+ mHintToSaveText = context.getText(R.string.hint_add_to_dictionary);
// Get the settings preferences
final boolean hasVibrator = VibratorCompatWrapper.getInstance(context).hasVibrator();
@@ -299,9 +301,9 @@ public class Settings extends InputMethodSettingsActivity
return mShowSettingsKey;
}
- public boolean isVoiceKeyEnabled(EditorInfo attribute) {
+ public boolean isVoiceKeyEnabled(EditorInfo editorInfo) {
final boolean shortcutImeEnabled = SubtypeSwitcher.getInstance().isShortcutImeEnabled();
- final int inputType = (attribute != null) ? attribute.inputType : 0;
+ final int inputType = (editorInfo != null) ? editorInfo.inputType : 0;
return shortcutImeEnabled && mVoiceKeyEnabled
&& !InputTypeCompatUtils.isPasswordInputType(inputType);
}
@@ -774,4 +776,4 @@ public class Settings extends InputMethodSettingsActivity
builder.setView(v);
builder.create().show();
}
-} \ No newline at end of file
+}
diff --git a/java/src/com/android/inputmethod/latin/Suggest.java b/java/src/com/android/inputmethod/latin/Suggest.java
index caa5aac51..2a36f8266 100644
--- a/java/src/com/android/inputmethod/latin/Suggest.java
+++ b/java/src/com/android/inputmethod/latin/Suggest.java
@@ -20,6 +20,7 @@ import android.content.Context;
import android.text.TextUtils;
import android.util.Log;
+import com.android.inputmethod.keyboard.Keyboard;
import com.android.inputmethod.keyboard.ProximityInfo;
import java.io.File;
@@ -101,11 +102,12 @@ public class Suggest implements Dictionary.WordCallback {
private ArrayList<CharSequence> mSuggestions = new ArrayList<CharSequence>();
ArrayList<CharSequence> mBigramSuggestions = new ArrayList<CharSequence>();
- private CharSequence mTypedWord;
+ private CharSequence mConsideredWord;
// TODO: Remove these member variables by passing more context to addWord() callback method
private boolean mIsFirstCharCapitalized;
private boolean mIsAllUpperCase;
+ private int mTrailingSingleQuotesCount;
private int mCorrectionMode = CORRECTION_BASIC;
@@ -144,7 +146,7 @@ public class Suggest implements Dictionary.WordCallback {
initWhitelistAndAutocorrectAndPool(context, locale);
}
- private void addOrReplaceDictionary(Map<String, Dictionary> dictionaries, String key,
+ private static void addOrReplaceDictionary(Map<String, Dictionary> dictionaries, String key,
Dictionary dict) {
final Dictionary oldDict = (dict == null)
? dictionaries.remove(key)
@@ -295,17 +297,20 @@ public class Suggest implements Dictionary.WordCallback {
mAutoCorrection.init();
mIsFirstCharCapitalized = wordComposer.isFirstCharCapitalized();
mIsAllUpperCase = wordComposer.isAllUpperCase();
+ mTrailingSingleQuotesCount = wordComposer.trailingSingleQuotesCount();
collectGarbage(mSuggestions, mPrefMaxSuggestions);
Arrays.fill(mScores, 0);
- // Save a lowercase version of the original word
- String typedWord = wordComposer.getTypedWord();
+ final String typedWord = wordComposer.getTypedWord();
+ final String consideredWord = mTrailingSingleQuotesCount > 0
+ ? typedWord.substring(0, typedWord.length() - mTrailingSingleQuotesCount)
+ : typedWord;
if (typedWord != null) {
// Treating USER_TYPED as UNIGRAM suggestion for logging now.
LatinImeLogger.onAddSuggestedWord(typedWord, Suggest.DIC_USER_TYPED,
Dictionary.DataType.UNIGRAM);
}
- mTypedWord = typedWord;
+ mConsideredWord = consideredWord;
if (wordComposer.size() <= 1 && (mCorrectionMode == CORRECTION_FULL_BIGRAM
|| mCorrectionMode == CORRECTION_BASIC)) {
@@ -321,7 +326,7 @@ public class Suggest implements Dictionary.WordCallback {
for (final Dictionary dictionary : mBigramDictionaries.values()) {
dictionary.getBigrams(wordComposer, prevWordForBigram, this);
}
- if (TextUtils.isEmpty(typedWord)) {
+ if (TextUtils.isEmpty(consideredWord)) {
// Nothing entered: return all bigrams for the previous word
int insertCount = Math.min(mBigramSuggestions.size(), mPrefMaxSuggestions);
for (int i = 0; i < insertCount; ++i) {
@@ -330,7 +335,7 @@ public class Suggest implements Dictionary.WordCallback {
} else {
// Word entered: return only bigrams that match the first char of the typed word
@SuppressWarnings("null")
- final char currentChar = typedWord.charAt(0);
+ final char currentChar = consideredWord.charAt(0);
// TODO: Must pay attention to locale when changing case.
final char currentCharUpper = Character.toUpperCase(currentChar);
int count = 0;
@@ -354,24 +359,41 @@ public class Suggest implements Dictionary.WordCallback {
if (key.equals(DICT_KEY_USER_UNIGRAM) || key.equals(DICT_KEY_WHITELIST))
continue;
final Dictionary dictionary = mUnigramDictionaries.get(key);
- dictionary.getWords(wordComposer, this, proximityInfo);
+ if (mTrailingSingleQuotesCount > 0) {
+ final WordComposer tmpWordComposer = new WordComposer(wordComposer);
+ for (int i = mTrailingSingleQuotesCount - 1; i >= 0; --i) {
+ tmpWordComposer.deleteLast();
+ }
+ dictionary.getWords(tmpWordComposer, this, proximityInfo);
+ } else {
+ dictionary.getWords(wordComposer, this, proximityInfo);
+ }
}
}
- final String typedWordString = typedWord == null ? null : typedWord.toString();
+ final String consideredWordString =
+ consideredWord == null ? null : consideredWord.toString();
CharSequence whitelistedWord = capitalizeWord(mIsAllUpperCase, mIsFirstCharCapitalized,
- mWhiteListDictionary.getWhitelistedWord(typedWordString));
+ mWhiteListDictionary.getWhitelistedWord(consideredWordString));
mAutoCorrection.updateAutoCorrectionStatus(mUnigramDictionaries, wordComposer,
- mSuggestions, mScores, typedWord, mAutoCorrectionThreshold, mCorrectionMode,
+ mSuggestions, mScores, consideredWord, mAutoCorrectionThreshold, mCorrectionMode,
whitelistedWord);
if (whitelistedWord != null) {
- mSuggestions.add(0, whitelistedWord);
+ if (mTrailingSingleQuotesCount > 0) {
+ final StringBuilder sb = new StringBuilder(whitelistedWord);
+ for (int i = mTrailingSingleQuotesCount - 1; i >= 0; --i) {
+ sb.appendCodePoint(Keyboard.CODE_SINGLE_QUOTE);
+ }
+ mSuggestions.add(0, sb.toString());
+ } else {
+ mSuggestions.add(0, whitelistedWord);
+ }
}
if (typedWord != null) {
- mSuggestions.add(0, typedWordString);
+ mSuggestions.add(0, typedWord.toString());
}
Utils.removeDupes(mSuggestions);
@@ -424,7 +446,7 @@ public class Suggest implements Dictionary.WordCallback {
int pos = 0;
// Check if it's the same word, only caps are different
- if (Utils.equalsIgnoreCase(mTypedWord, word, offset, length)) {
+ if (Utils.equalsIgnoreCase(mConsideredWord, word, offset, length)) {
// TODO: remove this surrounding if clause and move this logic to
// getSuggestedWordBuilder.
if (suggestions.size() > 0) {
@@ -486,6 +508,9 @@ public class Suggest implements Dictionary.WordCallback {
} else {
sb.append(word, offset, length);
}
+ for (int i = mTrailingSingleQuotesCount - 1; i >= 0; --i) {
+ sb.appendCodePoint(Keyboard.CODE_SINGLE_QUOTE);
+ }
suggestions.add(pos, sb);
if (suggestions.size() > prefMaxSuggestions) {
final CharSequence garbage = suggestions.remove(prefMaxSuggestions);
@@ -518,7 +543,8 @@ public class Suggest implements Dictionary.WordCallback {
return -1;
}
- private void collectGarbage(ArrayList<CharSequence> suggestions, int prefMaxSuggestions) {
+ private static void collectGarbage(ArrayList<CharSequence> suggestions,
+ int prefMaxSuggestions) {
int poolSize = StringBuilderPool.getSize();
int garbageSize = suggestions.size();
while (poolSize < prefMaxSuggestions && garbageSize > 0) {
diff --git a/java/src/com/android/inputmethod/latin/SuggestionsView.java b/java/src/com/android/inputmethod/latin/SuggestionsView.java
index c25ecb382..8c49ba0cf 100644
--- a/java/src/com/android/inputmethod/latin/SuggestionsView.java
+++ b/java/src/com/android/inputmethod/latin/SuggestionsView.java
@@ -30,7 +30,6 @@ import android.graphics.drawable.BitmapDrawable;
import android.graphics.drawable.ColorDrawable;
import android.graphics.drawable.Drawable;
import android.os.Message;
-import android.os.SystemClock;
import android.text.Spannable;
import android.text.SpannableString;
import android.text.Spanned;
@@ -172,7 +171,6 @@ public class SuggestionsView extends RelativeLayout implements OnClickListener,
public final TextView mWordToSaveView;
private final TextView mHintToSaveView;
- private final CharSequence mHintToSaveText;
public SuggestionsViewParams(Context context, AttributeSet attrs, int defStyle,
List<TextView> words, List<View> dividers, List<TextView> infos) {
@@ -228,7 +226,6 @@ public class SuggestionsView extends RelativeLayout implements OnClickListener,
final LayoutInflater inflater = LayoutInflater.from(context);
mWordToSaveView = (TextView)inflater.inflate(R.layout.suggestion_word, null);
mHintToSaveView = (TextView)inflater.inflate(R.layout.suggestion_word, null);
- mHintToSaveText = context.getText(R.string.hint_add_to_dictionary);
}
private static Drawable getMoreSuggestionsHint(Resources res, float textSize, int color) {
@@ -445,7 +442,7 @@ public class SuggestionsView extends RelativeLayout implements OnClickListener,
}
public void layoutAddToDictionaryHint(CharSequence word, ViewGroup stripView,
- int stripWidth) {
+ int stripWidth, CharSequence hintText) {
final int width = stripWidth - mDividerWidth - mPadding * 2;
final TextView wordView = mWordToSaveView;
@@ -464,8 +461,8 @@ public class SuggestionsView extends RelativeLayout implements OnClickListener,
final TextView hintView = mHintToSaveView;
hintView.setTextColor(mColorAutoCorrect);
final int hintWidth = width - wordWidth;
- final float hintScaleX = getTextScaleX(mHintToSaveText, hintWidth, hintView.getPaint());
- hintView.setText(mHintToSaveText);
+ final float hintScaleX = getTextScaleX(hintText, hintWidth, hintView.getPaint());
+ hintView.setText(hintText);
hintView.setTextScaleX(hintScaleX);
stripView.addView(hintView);
setLayoutWeight(
@@ -647,9 +644,9 @@ public class SuggestionsView extends RelativeLayout implements OnClickListener,
&& mSuggestionsStrip.getChildAt(0) == mParams.mWordToSaveView;
}
- public void showAddToDictionaryHint(CharSequence word) {
+ public void showAddToDictionaryHint(CharSequence word, CharSequence hintText) {
clear();
- mParams.layoutAddToDictionaryHint(word, mSuggestionsStrip, getWidth());
+ mParams.layoutAddToDictionaryHint(word, mSuggestionsStrip, getWidth(), hintText);
}
public boolean dismissAddToDictionaryHint() {
@@ -832,8 +829,7 @@ public class SuggestionsView extends RelativeLayout implements OnClickListener,
// Decided to be in the sliding input mode only when the touch point has been moved
// upward.
mMoreSuggestionsMode = MORE_SUGGESTIONS_IN_SLIDING_MODE;
- tracker.onShowMoreKeysPanel(
- translatedX, translatedY, SystemClock.uptimeMillis(), moreKeysPanel);
+ tracker.onShowMoreKeysPanel(translatedX, translatedY, moreKeysPanel);
} else if (action == MotionEvent.ACTION_UP || action == MotionEvent.ACTION_POINTER_UP) {
// Decided to be in the modal input mode
mMoreSuggestionsMode = MORE_SUGGESTIONS_IN_MODAL_MODE;
diff --git a/java/src/com/android/inputmethod/latin/TextEntryState.java b/java/src/com/android/inputmethod/latin/TextEntryState.java
index 79b3bdebb..a6041b310 100644
--- a/java/src/com/android/inputmethod/latin/TextEntryState.java
+++ b/java/src/com/android/inputmethod/latin/TextEntryState.java
@@ -30,13 +30,10 @@ public class TextEntryState {
private static final int IN_WORD = 2;
private static final int ACCEPTED_DEFAULT = 3;
private static final int PICKED_SUGGESTION = 4;
- private static final int PUNCTUATION_AFTER_WORD = 5;
- private static final int PUNCTUATION_AFTER_ACCEPTED = 6;
- private static final int SPACE_AFTER_ACCEPTED = 7;
- private static final int SPACE_AFTER_PICKED = 8;
- private static final int UNDO_COMMIT = 9;
- private static final int RECORRECTING = 10;
- private static final int PICKED_RECORRECTION = 11;
+ private static final int PUNCTUATION_AFTER_ACCEPTED = 5;
+ private static final int SPACE_AFTER_ACCEPTED = 6;
+ private static final int SPACE_AFTER_PICKED = 7;
+ private static final int UNDO_COMMIT = 8;
private static int sState = UNKNOWN;
private static int sPreviousState = UNKNOWN;
@@ -79,27 +76,11 @@ public class TextEntryState {
}
public static void acceptedSuggestion(CharSequence typedWord, CharSequence actualWord) {
- if (sState == RECORRECTING || sState == PICKED_RECORRECTION) {
- setState(PICKED_RECORRECTION);
- } else {
- setState(PICKED_SUGGESTION);
- }
+ setState(PICKED_SUGGESTION);
if (DEBUG)
displayState("acceptedSuggestion", "typedWord", typedWord, "actualWord", actualWord);
}
- public static void selectedForRecorrection() {
- setState(RECORRECTING);
- if (DEBUG) displayState("selectedForRecorrection");
- }
-
- public static void onAbortRecorrection() {
- if (sState == RECORRECTING || sState == PICKED_RECORRECTION) {
- setState(START);
- }
- if (DEBUG) displayState("onAbortRecorrection");
- }
-
public static void typedCharacter(char c, boolean isSeparator, int x, int y) {
final boolean isSpace = (c == Keyboard.CODE_SPACE);
switch (sState) {
@@ -123,7 +104,6 @@ public class TextEntryState {
}
break;
case PICKED_SUGGESTION:
- case PICKED_RECORRECTION:
if (isSpace) {
setState(SPACE_AFTER_PICKED);
} else if (isSeparator) {
@@ -136,7 +116,6 @@ public class TextEntryState {
case START:
case UNKNOWN:
case SPACE_AFTER_ACCEPTED:
- case PUNCTUATION_AFTER_WORD:
if (!isSpace && !isSeparator) {
setState(IN_WORD);
} else {
@@ -150,9 +129,6 @@ public class TextEntryState {
setState(IN_WORD);
}
break;
- case RECORRECTING:
- setState(START);
- break;
}
RingCharBuffer.getInstance().push(c, x, y);
if (isSeparator) {
@@ -170,34 +146,33 @@ public class TextEntryState {
} else if (sState == UNDO_COMMIT) {
setState(IN_WORD);
}
+ // TODO: tidy up this logic. At the moment, for example, writing a word goes to
+ // ACCEPTED_DEFAULT, backspace will go to UNDO_COMMIT, another backspace will go to IN_WORD,
+ // and subsequent backspaces will leave the status at IN_WORD, even if the user backspaces
+ // past the end of the word. We are not in a word any more but the state is still IN_WORD.
if (DEBUG) displayState("backspace");
}
+ public static void restartSuggestionsOnWordBeforeCursor() {
+ if (UNKNOWN == sState || ACCEPTED_DEFAULT == sState) {
+ // Here we can come from pretty much any state, except the ones that we can't
+ // come from after backspace, so supposedly anything except UNKNOWN and
+ // ACCEPTED_DEFAULT. Note : we could be in UNDO_COMMIT if
+ // LatinIME#revertLastWord() was calling LatinIME#restartSuggestions...()
+ Log.e(TAG, "Strange state change : coming from state " + sState);
+ }
+ setState(IN_WORD);
+ }
+
public static void reset() {
setState(START);
if (DEBUG) displayState("reset");
}
- public static boolean isAcceptedDefault() {
- return sState == ACCEPTED_DEFAULT;
- }
-
- public static boolean isSpaceAfterPicked() {
- return sState == SPACE_AFTER_PICKED;
- }
-
public static boolean isUndoCommit() {
return sState == UNDO_COMMIT;
}
- public static boolean isPunctuationAfterAccepted() {
- return sState == PUNCTUATION_AFTER_ACCEPTED;
- }
-
- public static boolean isRecorrecting() {
- return sState == RECORRECTING || sState == PICKED_RECORRECTION;
- }
-
public static String getState() {
return stateName(sState);
}
@@ -208,13 +183,10 @@ public class TextEntryState {
case IN_WORD: return "IN_WORD";
case ACCEPTED_DEFAULT: return "ACCEPTED_DEFAULT";
case PICKED_SUGGESTION: return "PICKED_SUGGESTION";
- case PUNCTUATION_AFTER_WORD: return "PUNCTUATION_AFTER_WORD";
case PUNCTUATION_AFTER_ACCEPTED: return "PUNCTUATION_AFTER_ACCEPTED";
case SPACE_AFTER_ACCEPTED: return "SPACE_AFTER_ACCEPTED";
case SPACE_AFTER_PICKED: return "SPACE_AFTER_PICKED";
case UNDO_COMMIT: return "UNDO_COMMIT";
- case RECORRECTING: return "RECORRECTING";
- case PICKED_RECORRECTION: return "PICKED_RECORRECTION";
default: return "UNKNOWN";
}
}
diff --git a/java/src/com/android/inputmethod/latin/UserBigramDictionary.java b/java/src/com/android/inputmethod/latin/UserBigramDictionary.java
index 9e656675e..3a1af9311 100644
--- a/java/src/com/android/inputmethod/latin/UserBigramDictionary.java
+++ b/java/src/com/android/inputmethod/latin/UserBigramDictionary.java
@@ -238,7 +238,7 @@ public class UserBigramDictionary extends ExpandableDictionary {
/**
* Query the database
*/
- private Cursor query(String selection, String[] selectionArgs) {
+ private static Cursor query(String selection, String[] selectionArgs) {
SQLiteQueryBuilder qb = new SQLiteQueryBuilder();
// main INNER JOIN frequency ON (main._id=freq.pair_id)
@@ -310,7 +310,7 @@ public class UserBigramDictionary extends ExpandableDictionary {
}
/** Prune any old data if the database is getting too big. */
- private void checkPruneData(SQLiteDatabase db) {
+ private static void checkPruneData(SQLiteDatabase db) {
db.execSQL("PRAGMA foreign_keys = ON;");
Cursor c = db.query(FREQ_TABLE_NAME, new String[] { FREQ_COLUMN_PAIR_ID },
null, null, null, null, null);
@@ -380,7 +380,7 @@ public class UserBigramDictionary extends ExpandableDictionary {
return null;
}
- private ContentValues getContentValues(String word1, String word2, String locale) {
+ private static ContentValues getContentValues(String word1, String word2, String locale) {
ContentValues values = new ContentValues(3);
values.put(MAIN_COLUMN_WORD1, word1);
values.put(MAIN_COLUMN_WORD2, word2);
@@ -388,7 +388,7 @@ public class UserBigramDictionary extends ExpandableDictionary {
return values;
}
- private ContentValues getFrequencyContentValues(int pairId, int frequency) {
+ private static ContentValues getFrequencyContentValues(int pairId, int frequency) {
ContentValues values = new ContentValues(2);
values.put(FREQ_COLUMN_PAIR_ID, pairId);
values.put(FREQ_COLUMN_FREQUENCY, frequency);
diff --git a/java/src/com/android/inputmethod/latin/UserDictionary.java b/java/src/com/android/inputmethod/latin/UserDictionary.java
index 0bbbf3995..3e53bb0a3 100644
--- a/java/src/com/android/inputmethod/latin/UserDictionary.java
+++ b/java/src/com/android/inputmethod/latin/UserDictionary.java
@@ -134,7 +134,11 @@ public class UserDictionary extends ExpandableDictionary {
final Cursor cursor = getContext().getContentResolver()
.query(Words.CONTENT_URI, PROJECTION_QUERY, request.toString(),
requestArguments, null);
- addWords(cursor);
+ try {
+ addWords(cursor);
+ } finally {
+ if (null != cursor) cursor.close();
+ }
}
public boolean isEnabled() {
@@ -242,6 +246,5 @@ public class UserDictionary extends ExpandableDictionary {
cursor.moveToNext();
}
}
- cursor.close();
}
}
diff --git a/java/src/com/android/inputmethod/latin/UserUnigramDictionary.java b/java/src/com/android/inputmethod/latin/UserUnigramDictionary.java
index e41230b3c..de7cb5716 100644
--- a/java/src/com/android/inputmethod/latin/UserUnigramDictionary.java
+++ b/java/src/com/android/inputmethod/latin/UserUnigramDictionary.java
@@ -206,7 +206,7 @@ public class UserUnigramDictionary extends ExpandableDictionary {
}
}
- private Cursor query(String selection, String[] selectionArgs) {
+ private static Cursor query(String selection, String[] selectionArgs) {
SQLiteQueryBuilder qb = new SQLiteQueryBuilder();
qb.setTables(USER_UNIGRAM_DICT_TABLE_NAME);
qb.setProjectionMap(sDictProjectionMap);
@@ -251,7 +251,7 @@ public class UserUnigramDictionary extends ExpandableDictionary {
return null;
}
- private ContentValues getContentValues(String word, int frequency, String locale) {
+ private static ContentValues getContentValues(String word, int frequency, String locale) {
ContentValues values = new ContentValues(4);
values.put(COLUMN_WORD, word);
values.put(COLUMN_FREQUENCY, frequency);
diff --git a/java/src/com/android/inputmethod/latin/Utils.java b/java/src/com/android/inputmethod/latin/Utils.java
index b29ff1975..3d0aa09f1 100644
--- a/java/src/com/android/inputmethod/latin/Utils.java
+++ b/java/src/com/android/inputmethod/latin/Utils.java
@@ -242,7 +242,7 @@ public class Utils {
UsabilityStudyLogUtils.getInstance().init(context);
return sRingCharBuffer;
}
- private int normalize(int in) {
+ private static int normalize(int in) {
int ret = in % BUFSIZE;
return ret < 0 ? ret + BUFSIZE : ret;
}
@@ -465,7 +465,7 @@ public class Utils {
}
}
- public void writeBackSpace() {
+ public static void writeBackSpace() {
UsabilityStudyLogUtils.getInstance().write("<backspace>\t0\t0");
}
diff --git a/java/src/com/android/inputmethod/latin/WordComposer.java b/java/src/com/android/inputmethod/latin/WordComposer.java
index adc5637f6..44c89f73c 100644
--- a/java/src/com/android/inputmethod/latin/WordComposer.java
+++ b/java/src/com/android/inputmethod/latin/WordComposer.java
@@ -16,9 +16,13 @@
package com.android.inputmethod.latin;
+import com.android.inputmethod.keyboard.Keyboard;
+import com.android.inputmethod.keyboard.Key;
import com.android.inputmethod.keyboard.KeyDetector;
+import com.android.inputmethod.keyboard.LatinKeyboard;
import java.util.ArrayList;
+import java.util.Arrays;
/**
* A place to store the currently composing word with information such as adjacent key codes as well
@@ -41,7 +45,9 @@ public class WordComposer {
private int mCapsCount;
private boolean mAutoCapitalized;
-
+ // Cache this value for performance
+ private int mTrailingSingleQuotesCount;
+
/**
* Whether the user chose to capitalize the first char of the word.
*/
@@ -53,6 +59,7 @@ public class WordComposer {
mTypedWord = new StringBuilder(N);
mXCoordinates = new int[N];
mYCoordinates = new int[N];
+ mTrailingSingleQuotesCount = 0;
}
public WordComposer(WordComposer source) {
@@ -62,11 +69,12 @@ public class WordComposer {
public void init(WordComposer source) {
mCodes = new ArrayList<int[]>(source.mCodes);
mTypedWord = new StringBuilder(source.mTypedWord);
- mXCoordinates = source.mXCoordinates;
- mYCoordinates = source.mYCoordinates;
+ mXCoordinates = Arrays.copyOf(source.mXCoordinates, source.mXCoordinates.length);
+ mYCoordinates = Arrays.copyOf(source.mYCoordinates, source.mYCoordinates.length);
mCapsCount = source.mCapsCount;
mIsFirstCharCapitalized = source.mIsFirstCharCapitalized;
mAutoCapitalized = source.mAutoCapitalized;
+ mTrailingSingleQuotesCount = source.mTrailingSingleQuotesCount;
}
/**
@@ -77,6 +85,7 @@ public class WordComposer {
mTypedWord.setLength(0);
mCapsCount = 0;
mIsFirstCharCapitalized = false;
+ mTrailingSingleQuotesCount = 0;
}
/**
@@ -126,6 +135,55 @@ public class WordComposer {
mIsFirstCharCapitalized = isFirstCharCapitalized(
newIndex, primaryCode, mIsFirstCharCapitalized);
if (Character.isUpperCase(primaryCode)) mCapsCount++;
+ if (Keyboard.CODE_SINGLE_QUOTE == primaryCode) {
+ ++mTrailingSingleQuotesCount;
+ } else {
+ mTrailingSingleQuotesCount = 0;
+ }
+ }
+
+ /**
+ * Internal method to retrieve reasonable proximity info for a character.
+ */
+ private void addKeyInfo(final int codePoint, final LatinKeyboard keyboard,
+ final KeyDetector keyDetector) {
+ for (final Key key : keyboard.mKeys) {
+ if (key.mCode == codePoint) {
+ final int x = key.mX + key.mWidth / 2;
+ final int y = key.mY + key.mHeight / 2;
+ final int[] codes = keyDetector.newCodeArray();
+ keyDetector.getKeyAndNearbyCodes(x, y, codes);
+ add(codePoint, codes, x, y);
+ return;
+ }
+ }
+ add(codePoint, new int[] { codePoint },
+ WordComposer.NOT_A_COORDINATE, WordComposer.NOT_A_COORDINATE);
+ }
+
+ /**
+ * Set the currently composing word to the one passed as an argument.
+ * This will register NOT_A_COORDINATE for X and Ys, and use the passed keyboard for proximity.
+ */
+ public void setComposingWord(final CharSequence word, final LatinKeyboard keyboard,
+ final KeyDetector keyDetector) {
+ reset();
+ final int length = word.length();
+ for (int i = 0; i < length; ++i) {
+ int codePoint = word.charAt(i);
+ addKeyInfo(codePoint, keyboard, keyDetector);
+ }
+ }
+
+ /**
+ * Shortcut for the above method, this will create a new KeyDetector for the passed keyboard.
+ */
+ public void setComposingWord(final CharSequence word, final LatinKeyboard keyboard) {
+ final KeyDetector keyDetector = new KeyDetector(0);
+ keyDetector.setKeyboard(keyboard, 0, 0);
+ keyDetector.setProximityCorrectionEnabled(true);
+ keyDetector.setProximityThreshold(keyboard.mMostCommonKeyWidth);
+ setComposingWord(word, keyboard, keyDetector);
}
/**
@@ -135,7 +193,7 @@ public class WordComposer {
* @param primaryCode the preferred character
* @param codes array of codes based on distance from touch point
*/
- private void correctPrimaryJuxtapos(int primaryCode, int[] codes) {
+ private static void correctPrimaryJuxtapos(int primaryCode, int[] codes) {
if (codes.length < 2) return;
if (codes[0] > 0 && codes[1] > 0 && codes[0] != primaryCode && codes[1] == primaryCode) {
codes[1] = codes[0];
@@ -158,6 +216,14 @@ public class WordComposer {
if (size() == 0) {
mIsFirstCharCapitalized = false;
}
+ if (mTrailingSingleQuotesCount > 0) {
+ --mTrailingSingleQuotesCount;
+ } else {
+ for (int i = mTypedWord.length() - 1; i >= 0; --i) {
+ if (Keyboard.CODE_SINGLE_QUOTE != mTypedWord.codePointAt(i)) break;
+ ++mTrailingSingleQuotesCount;
+ }
+ }
}
/**
@@ -179,6 +245,10 @@ public class WordComposer {
return mIsFirstCharCapitalized;
}
+ public int trailingSingleQuotesCount() {
+ return mTrailingSingleQuotesCount;
+ }
+
/**
* Whether or not all of the user typed chars are upper case
* @return true if all user typed chars are upper case, false otherwise
diff --git a/java/src/com/android/inputmethod/latin/spellcheck/AndroidSpellCheckerService.java b/java/src/com/android/inputmethod/latin/spellcheck/AndroidSpellCheckerService.java
index 095c2c51c..9dfbe7a54 100644
--- a/java/src/com/android/inputmethod/latin/spellcheck/AndroidSpellCheckerService.java
+++ b/java/src/com/android/inputmethod/latin/spellcheck/AndroidSpellCheckerService.java
@@ -17,7 +17,9 @@
package com.android.inputmethod.latin.spellcheck;
import android.content.Intent;
+import android.content.SharedPreferences;
import android.content.res.Resources;
+import android.preference.PreferenceManager;
import android.service.textservice.SpellCheckerService;
import android.text.TextUtils;
import android.util.Log;
@@ -41,21 +43,27 @@ import com.android.inputmethod.latin.Utils;
import com.android.inputmethod.latin.WhitelistDictionary;
import com.android.inputmethod.latin.WordComposer;
+import java.lang.ref.WeakReference;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
+import java.util.Iterator;
import java.util.Locale;
import java.util.Map;
import java.util.TreeMap;
+import java.util.HashSet;
/**
* Service for spell checking, using LatinIME's dictionaries and mechanisms.
*/
-public class AndroidSpellCheckerService extends SpellCheckerService {
+public class AndroidSpellCheckerService extends SpellCheckerService
+ implements SharedPreferences.OnSharedPreferenceChangeListener {
private static final String TAG = AndroidSpellCheckerService.class.getSimpleName();
private static final boolean DBG = false;
private static final int POOL_SIZE = 2;
+ public static final String PREF_USE_CONTACTS_KEY = "pref_spellcheck_use_contacts";
+
private static final int CAPITALIZE_NONE = 0; // No caps, or mixed case
private static final int CAPITALIZE_FIRST = 1; // First only
private static final int CAPITALIZE_ALL = 2; // All caps
@@ -82,15 +90,72 @@ public class AndroidSpellCheckerService extends SpellCheckerService {
// The threshold for a candidate to be offered as a suggestion.
private double mSuggestionThreshold;
- // The threshold for a suggestion to be considered "likely".
- private double mLikelyThreshold;
+ // The threshold for a suggestion to be considered "recommended".
+ private double mRecommendedThreshold;
+ // Whether to use the contacts dictionary
+ private boolean mUseContactsDictionary;
+ private final Object mUseContactsLock = new Object();
+
+ private final HashSet<WeakReference<DictionaryCollection>> mDictionaryCollectionsList =
+ new HashSet<WeakReference<DictionaryCollection>>();
@Override public void onCreate() {
super.onCreate();
mSuggestionThreshold =
Double.parseDouble(getString(R.string.spellchecker_suggestion_threshold_value));
- mLikelyThreshold =
- Double.parseDouble(getString(R.string.spellchecker_likely_threshold_value));
+ mRecommendedThreshold =
+ Double.parseDouble(getString(R.string.spellchecker_recommended_threshold_value));
+ final SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(this);
+ prefs.registerOnSharedPreferenceChangeListener(this);
+ onSharedPreferenceChanged(prefs, PREF_USE_CONTACTS_KEY);
+ }
+
+ @Override
+ public void onSharedPreferenceChanged(final SharedPreferences prefs, final String key) {
+ if (!PREF_USE_CONTACTS_KEY.equals(key)) return;
+ synchronized(mUseContactsLock) {
+ mUseContactsDictionary = prefs.getBoolean(PREF_USE_CONTACTS_KEY, true);
+ if (mUseContactsDictionary) {
+ startUsingContactsDictionaryLocked();
+ } else {
+ stopUsingContactsDictionaryLocked();
+ }
+ }
+ }
+
+ private void startUsingContactsDictionaryLocked() {
+ if (null == mContactsDictionary) {
+ mContactsDictionary = new SynchronouslyLoadedContactsDictionary(this);
+ }
+ final Iterator<WeakReference<DictionaryCollection>> iterator =
+ mDictionaryCollectionsList.iterator();
+ while (iterator.hasNext()) {
+ final WeakReference<DictionaryCollection> dictRef = iterator.next();
+ final DictionaryCollection dict = dictRef.get();
+ if (null == dict) {
+ iterator.remove();
+ } else {
+ dict.addDictionary(mContactsDictionary);
+ }
+ }
+ }
+
+ private void stopUsingContactsDictionaryLocked() {
+ if (null == mContactsDictionary) return;
+ final SynchronouslyLoadedContactsDictionary contactsDict = mContactsDictionary;
+ mContactsDictionary = null;
+ final Iterator<WeakReference<DictionaryCollection>> iterator =
+ mDictionaryCollectionsList.iterator();
+ while (iterator.hasNext()) {
+ final WeakReference<DictionaryCollection> dictRef = iterator.next();
+ final DictionaryCollection dict = dictRef.get();
+ if (null == dict) {
+ iterator.remove();
+ } else {
+ dict.removeDictionary(contactsDict);
+ }
+ }
+ contactsDict.close();
}
@Override
@@ -110,10 +175,11 @@ public class AndroidSpellCheckerService extends SpellCheckerService {
private static class SuggestionsGatherer implements WordCallback {
public static class Result {
public final String[] mSuggestions;
- public final boolean mHasLikelySuggestions;
- public Result(final String[] gatheredSuggestions, final boolean hasLikelySuggestions) {
+ public final boolean mHasRecommendedSuggestions;
+ public Result(final String[] gatheredSuggestions,
+ final boolean hasRecommendedSuggestions) {
mSuggestions = gatheredSuggestions;
- mHasLikelySuggestions = hasLikelySuggestions;
+ mHasRecommendedSuggestions = hasRecommendedSuggestions;
}
}
@@ -121,7 +187,7 @@ public class AndroidSpellCheckerService extends SpellCheckerService {
private final int[] mScores;
private final String mOriginalText;
private final double mSuggestionThreshold;
- private final double mLikelyThreshold;
+ private final double mRecommendedThreshold;
private final int mMaxLength;
private int mLength = 0;
@@ -131,10 +197,10 @@ public class AndroidSpellCheckerService extends SpellCheckerService {
private int mBestScore = Integer.MIN_VALUE; // As small as possible
SuggestionsGatherer(final String originalText, final double suggestionThreshold,
- final double likelyThreshold, final int maxLength) {
+ final double recommendedThreshold, final int maxLength) {
mOriginalText = originalText;
mSuggestionThreshold = suggestionThreshold;
- mLikelyThreshold = likelyThreshold;
+ mRecommendedThreshold = recommendedThreshold;
mMaxLength = maxLength;
mSuggestions = new ArrayList<CharSequence>(maxLength + 1);
mScores = new int[mMaxLength];
@@ -198,19 +264,19 @@ public class AndroidSpellCheckerService extends SpellCheckerService {
public Result getResults(final int capitalizeType, final Locale locale) {
final String[] gatheredSuggestions;
- final boolean hasLikelySuggestions;
+ final boolean hasRecommendedSuggestions;
if (0 == mLength) {
// Either we found no suggestions, or we found some BUT the max length was 0.
// If we found some mBestSuggestion will not be null. If it is null, then
// we found none, regardless of the max length.
if (null == mBestSuggestion) {
gatheredSuggestions = null;
- hasLikelySuggestions = false;
+ hasRecommendedSuggestions = false;
} else {
gatheredSuggestions = EMPTY_STRING_ARRAY;
final double normalizedScore =
Utils.calcNormalizedScore(mOriginalText, mBestSuggestion, mBestScore);
- hasLikelySuggestions = (normalizedScore > mLikelyThreshold);
+ hasRecommendedSuggestions = (normalizedScore > mRecommendedThreshold);
}
} else {
if (DBG) {
@@ -244,15 +310,15 @@ public class AndroidSpellCheckerService extends SpellCheckerService {
final CharSequence bestSuggestion = mSuggestions.get(0);
final double normalizedScore =
Utils.calcNormalizedScore(mOriginalText, bestSuggestion, bestScore);
- hasLikelySuggestions = (normalizedScore > mLikelyThreshold);
+ hasRecommendedSuggestions = (normalizedScore > mRecommendedThreshold);
if (DBG) {
Log.i(TAG, "Best suggestion : " + bestSuggestion + ", score " + bestScore);
Log.i(TAG, "Normalized score = " + normalizedScore
- + " (threshold " + mLikelyThreshold
- + ") => hasLikelySuggestions = " + hasLikelySuggestions);
+ + " (threshold " + mRecommendedThreshold
+ + ") => hasRecommendedSuggestions = " + hasRecommendedSuggestions);
}
}
- return new Result(gatheredSuggestions, hasLikelySuggestions);
+ return new Result(gatheredSuggestions, hasRecommendedSuggestions);
}
}
@@ -273,13 +339,15 @@ public class AndroidSpellCheckerService extends SpellCheckerService {
for (Dictionary dict : oldWhitelistDictionaries.values()) {
dict.close();
}
- if (null != mContactsDictionary) {
- // The synchronously loaded contacts dictionary should have been in one
- // or several pools, but it is shielded against multiple closing and it's
- // safe to call it several times.
- final SynchronouslyLoadedContactsDictionary dictToClose = mContactsDictionary;
- mContactsDictionary = null;
- dictToClose.close();
+ synchronized(mUseContactsLock) {
+ if (null != mContactsDictionary) {
+ // The synchronously loaded contacts dictionary should have been in one
+ // or several pools, but it is shielded against multiple closing and it's
+ // safe to call it several times.
+ final SynchronouslyLoadedContactsDictionary dictToClose = mContactsDictionary;
+ mContactsDictionary = null;
+ dictToClose.close();
+ }
}
return false;
}
@@ -314,11 +382,16 @@ public class AndroidSpellCheckerService extends SpellCheckerService {
mWhitelistDictionaries.put(localeStr, whitelistDictionary);
}
dictionaryCollection.addDictionary(whitelistDictionary);
- if (null == mContactsDictionary) {
- mContactsDictionary = new SynchronouslyLoadedContactsDictionary(this);
+ synchronized(mUseContactsLock) {
+ if (mUseContactsDictionary) {
+ if (null == mContactsDictionary) {
+ mContactsDictionary = new SynchronouslyLoadedContactsDictionary(this);
+ }
+ }
+ dictionaryCollection.addDictionary(mContactsDictionary);
+ mDictionaryCollectionsList.add(
+ new WeakReference<DictionaryCollection>(dictionaryCollection));
}
- // TODO: add a setting to use or not contacts when checking spelling
- dictionaryCollection.addDictionary(mContactsDictionary);
return new DictAndProximity(dictionaryCollection, proximityInfo);
}
@@ -360,6 +433,27 @@ public class AndroidSpellCheckerService extends SpellCheckerService {
mLocale = LocaleUtils.constructLocaleFromString(localeString);
}
+ /*
+ * Returns whether the code point is a letter that makes sense for the specified
+ * locale for this spell checker.
+ * The dictionaries supported by Latin IME are described in res/xml/spellchecker.xml
+ * and is limited to EFIGS language.
+ * Hence at the moment this explicitly excludes non-Latin scripts, including CJK
+ * characters, but also Cyrillic, Arabic or Hebrew characters.
+ * The locale should be used to rule out inappropriate characters when we support
+ * spellchecking other languages like Russian.
+ */
+ private static boolean isLetterCheckableByLanguage(final int codePoint,
+ final Locale locale) {
+ // Our supported dictionaries (EFIGS) at the moment only includes characters
+ // in the C0, C1, Latin Extended A and B, IPA extensions unicode blocks.
+ // As it happens, those are back-to-back in the code range 0x40 to 0x2AF, so
+ // the below is a very efficient way to test for it. As for the 0-0x3F, it's
+ // excluded from isLetter anyway.
+ // TODO: change this to use locale when we support other scripts
+ return codePoint <= 0x2AF && Character.isLetter(codePoint);
+ }
+
/**
* Finds out whether a particular string should be filtered out of spell checking.
*
@@ -368,7 +462,7 @@ public class AndroidSpellCheckerService extends SpellCheckerService {
* @param text the string to evaluate.
* @return true if we should filter this text out, false otherwise
*/
- private boolean shouldFilterOut(final String text) {
+ private static boolean shouldFilterOut(final String text, final Locale locale) {
if (TextUtils.isEmpty(text) || text.length() <= 1) return true;
// TODO: check if an equivalent processing can't be done more quickly with a
@@ -376,7 +470,7 @@ public class AndroidSpellCheckerService extends SpellCheckerService {
// Filter by first letter
final int firstCodePoint = text.codePointAt(0);
// Filter out words that don't start with a letter or an apostrophe
- if (!Character.isLetter(firstCodePoint)
+ if (!isLetterCheckableByLanguage(firstCodePoint, locale)
&& '\'' != firstCodePoint) return true;
// Filter contents
@@ -389,7 +483,7 @@ public class AndroidSpellCheckerService extends SpellCheckerService {
// words or a URI - in either case we don't want to spell check that
if ('@' == codePoint
|| '/' == codePoint) return true;
- if (Character.isLetter(codePoint)) ++letterCount;
+ if (isLetterCheckableByLanguage(codePoint, locale)) ++letterCount;
}
// Guestimate heuristic: perform spell checking if at least 3/4 of the characters
// in this word are letters
@@ -408,7 +502,7 @@ public class AndroidSpellCheckerService extends SpellCheckerService {
try {
final String text = textInfo.getText();
- if (shouldFilterOut(text)) {
+ if (shouldFilterOut(text, mLocale)) {
DictAndProximity dictInfo = null;
try {
dictInfo = mDictionaryPool.takeOrGetNull();
@@ -426,7 +520,8 @@ public class AndroidSpellCheckerService extends SpellCheckerService {
// TODO: Don't gather suggestions if the limit is <= 0 unless necessary
final SuggestionsGatherer suggestionsGatherer = new SuggestionsGatherer(text,
- mService.mSuggestionThreshold, mService.mLikelyThreshold, suggestionsLimit);
+ mService.mSuggestionThreshold, mService.mRecommendedThreshold,
+ suggestionsLimit);
final WordComposer composer = new WordComposer();
final int length = text.length();
for (int i = 0; i < length; ++i) {
@@ -475,7 +570,7 @@ public class AndroidSpellCheckerService extends SpellCheckerService {
+ suggestionsLimit);
Log.i(TAG, "IsInDict = " + isInDict);
Log.i(TAG, "LooksLikeTypo = " + (!isInDict));
- Log.i(TAG, "HasLikelySuggestions = " + result.mHasLikelySuggestions);
+ Log.i(TAG, "HasRecommendedSuggestions = " + result.mHasRecommendedSuggestions);
if (null != result.mSuggestions) {
for (String suggestion : result.mSuggestions) {
Log.i(TAG, suggestion);
@@ -483,10 +578,12 @@ public class AndroidSpellCheckerService extends SpellCheckerService {
}
}
- // TODO: actually use result.mHasLikelySuggestions
final int flags =
(isInDict ? SuggestionsInfo.RESULT_ATTR_IN_THE_DICTIONARY
- : SuggestionsInfo.RESULT_ATTR_LOOKS_LIKE_TYPO);
+ : SuggestionsInfo.RESULT_ATTR_LOOKS_LIKE_TYPO)
+ | (result.mHasRecommendedSuggestions
+ ? SuggestionsInfo.RESULT_ATTR_HAS_RECOMMENDED_SUGGESTIONS
+ : 0);
return new SuggestionsInfo(flags, result.mSuggestions);
} catch (RuntimeException e) {
// Don't kill the keyboard if there is a bug in the spell checker