aboutsummaryrefslogtreecommitdiffstats
path: root/java/src
diff options
context:
space:
mode:
Diffstat (limited to 'java/src')
-rwxr-xr-xjava/src/com/android/inputmethod/latin/CandidateView.java5
-rw-r--r--java/src/com/android/inputmethod/latin/EditingUtil.java188
-rw-r--r--java/src/com/android/inputmethod/latin/KeyDetector.java56
-rw-r--r--java/src/com/android/inputmethod/latin/LatinIME.java267
-rw-r--r--java/src/com/android/inputmethod/latin/LatinKeyboardBaseView.java24
-rw-r--r--java/src/com/android/inputmethod/latin/PointerTracker.java61
-rw-r--r--java/src/com/android/inputmethod/latin/ProximityKeyDetector.java30
7 files changed, 390 insertions, 241 deletions
diff --git a/java/src/com/android/inputmethod/latin/CandidateView.java b/java/src/com/android/inputmethod/latin/CandidateView.java
index 7fcc3d532..4995727da 100755
--- a/java/src/com/android/inputmethod/latin/CandidateView.java
+++ b/java/src/com/android/inputmethod/latin/CandidateView.java
@@ -107,7 +107,6 @@ public class CandidateView extends View {
}
break;
}
-
}
};
@@ -333,6 +332,10 @@ public class CandidateView extends View {
requestLayout();
}
+ public boolean isShowingAddToDictionaryHint() {
+ return mShowingAddToDictionary;
+ }
+
public void showAddToDictionaryHint(CharSequence word) {
ArrayList<CharSequence> suggestions = new ArrayList<CharSequence>();
suggestions.add(word);
diff --git a/java/src/com/android/inputmethod/latin/EditingUtil.java b/java/src/com/android/inputmethod/latin/EditingUtil.java
index be31cb787..781d7fd4a 100644
--- a/java/src/com/android/inputmethod/latin/EditingUtil.java
+++ b/java/src/com/android/inputmethod/latin/EditingUtil.java
@@ -16,10 +16,13 @@
package com.android.inputmethod.latin;
+import android.text.TextUtils;
import android.view.inputmethod.ExtractedText;
import android.view.inputmethod.ExtractedTextRequest;
import android.view.inputmethod.InputConnection;
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
import java.util.regex.Pattern;
/**
@@ -31,6 +34,11 @@ public class EditingUtil {
*/
private static final int LOOKBACK_CHARACTER_NUM = 15;
+ // Cache Method pointers
+ private static boolean sMethodsInitialized;
+ private static Method sMethodGetSelectedText;
+ private static Method sMethodSetComposingRegion;
+
private EditingUtil() {};
/**
@@ -65,36 +73,16 @@ public class EditingUtil {
return extracted.startOffset + extracted.selectionStart;
}
- private static int getSelectionEnd(InputConnection connection) {
- ExtractedText extracted = connection.getExtractedText(
- new ExtractedTextRequest(), 0);
- if (extracted == null) {
- return -1;
- }
- return extracted.startOffset + extracted.selectionEnd;
- }
-
/**
* @param connection connection to the current text field.
* @param sep characters which may separate words
+ * @param range the range object to store the result into
* @return the word that surrounds the cursor, including up to one trailing
* separator. For example, if the field contains "he|llo world", where |
* represents the cursor, then "hello " will be returned.
*/
public static String getWordAtCursor(
- InputConnection connection, String separators) {
- return getWordAtCursor(connection, separators, null);
- }
-
- /**
- * @param connection connection to the current text field.
- * @param sep characters which may separate words
- * @return the word that surrounds the cursor, including up to one trailing
- * separator. For example, if the field contains "he|llo world", where |
- * represents the cursor, then "hello " will be returned.
- */
- public static String getWordAtCursor(
- InputConnection connection, String separators, Range range) {
+ InputConnection connection, String separators, Range range) {
Range r = getWordRangeAtCursor(connection, separators, range);
return (r == null) ? null : r.word;
}
@@ -204,26 +192,146 @@ public class EditingUtil {
}
}
+ public static class SelectedWord {
+ public int start;
+ public int end;
+ public CharSequence word;
+ }
+
/**
- * Checks if the cursor is touching/inside a word or the selection is for a whole
- * word and no more and no less.
- * @param range the Range object that contains the bounds of the word around the cursor
- * @param start the start of the selection
- * @param end the end of the selection, which could be the same as the start, if text is not
- * in selection mode
- * @return false if the selection is a partial word or straddling multiple words, true if
- * the selection is a full word or there is no selection.
+ * Takes a character sequence with a single character and checks if the character occurs
+ * in a list of word separators or is empty.
+ * @param singleChar A CharSequence with null, zero or one character
+ * @param wordSeparators A String containing the word separators
+ * @return true if the character is at a word boundary, false otherwise
*/
- public static boolean isFullWordOrInside(Range range, int start, int end) {
- // Is the cursor inside or touching a word?
- if (start == end) return true;
-
- // Is it a selection? Then is the start of the selection the start of the word and
- // the size of the selection the size of the word? Then return true
- if (start < end
- && (range.charsBefore == 0 && range.charsAfter == end - start)) {
- return true;
+ private static boolean isWordBoundary(CharSequence singleChar, String wordSeparators) {
+ return TextUtils.isEmpty(singleChar) || wordSeparators.contains(singleChar);
+ }
+
+ /**
+ * Checks if the cursor is inside a word or the current selection is a whole word.
+ * @param ic the InputConnection for accessing the text field
+ * @param selStart the start position of the selection within the text field
+ * @param selEnd the end position of the selection within the text field. This could be
+ * the same as selStart, if there's no selection.
+ * @param wordSeparators the word separator characters for the current language
+ * @return an object containing the text and coordinates of the selected/touching word,
+ * null if the selection/cursor is not marking a whole word.
+ */
+ public static SelectedWord getWordAtCursorOrSelection(final InputConnection ic,
+ int selStart, int selEnd, String wordSeparators) {
+ if (selStart == selEnd) {
+ // There is just a cursor, so get the word at the cursor
+ EditingUtil.Range range = new EditingUtil.Range();
+ CharSequence touching = getWordAtCursor(ic, wordSeparators, range);
+ if (!TextUtils.isEmpty(touching)) {
+ SelectedWord selWord = new SelectedWord();
+ selWord.word = touching;
+ selWord.start = selStart - range.charsBefore;
+ selWord.end = selEnd + range.charsAfter;
+ return selWord;
+ }
+ } else {
+ // Is the previous character empty or a word separator? If not, return null.
+ CharSequence charsBefore = ic.getTextBeforeCursor(1, 0);
+ if (!isWordBoundary(charsBefore, wordSeparators)) {
+ return null;
+ }
+
+ // Is the next character empty or a word separator? If not, return null.
+ CharSequence charsAfter = ic.getTextAfterCursor(1, 0);
+ if (!isWordBoundary(charsAfter, wordSeparators)) {
+ return null;
+ }
+
+ // Extract the selection alone
+ CharSequence touching = getSelectedText(ic, selStart, selEnd);
+ if (TextUtils.isEmpty(touching)) return null;
+ // Is any part of the selection a separator? If so, return null.
+ final int length = touching.length();
+ for (int i = 0; i < length; i++) {
+ if (wordSeparators.contains(touching.subSequence(i, i + 1))) {
+ return null;
+ }
+ }
+ // Prepare the selected word
+ SelectedWord selWord = new SelectedWord();
+ selWord.start = selStart;
+ selWord.end = selEnd;
+ selWord.word = touching;
+ return selWord;
+ }
+ return null;
+ }
+
+ /**
+ * Cache method pointers for performance
+ */
+ private static void initializeMethodsForReflection() {
+ try {
+ // These will either both exist or not, so no need for separate try/catch blocks.
+ // If other methods are added later, use separate try/catch blocks.
+ sMethodGetSelectedText = InputConnection.class.getMethod("getSelectedText", int.class);
+ sMethodSetComposingRegion = InputConnection.class.getMethod("setComposingRegion",
+ int.class, int.class);
+ } catch (NoSuchMethodException exc) {
+ // Ignore
+ }
+ sMethodsInitialized = true;
+ }
+
+ /**
+ * Returns the selected text between the selStart and selEnd positions.
+ */
+ private static CharSequence getSelectedText(InputConnection ic, int selStart, int selEnd) {
+ // Use reflection, for backward compatibility
+ CharSequence result = null;
+ if (!sMethodsInitialized) {
+ initializeMethodsForReflection();
+ }
+ if (sMethodGetSelectedText != null) {
+ try {
+ result = (CharSequence) sMethodGetSelectedText.invoke(ic, 0);
+ return result;
+ } catch (InvocationTargetException exc) {
+ // Ignore
+ } catch (IllegalArgumentException e) {
+ // Ignore
+ } catch (IllegalAccessException e) {
+ // Ignore
+ }
+ }
+ // Reflection didn't work, try it the poor way, by moving the cursor to the start,
+ // getting the text after the cursor and moving the text back to selected mode.
+ // TODO: Verify that this works properly in conjunction with
+ // LatinIME#onUpdateSelection
+ ic.setSelection(selStart, selEnd);
+ result = ic.getTextAfterCursor(selEnd - selStart, 0);
+ ic.setSelection(selStart, selEnd);
+ return result;
+ }
+
+ /**
+ * Tries to set the text into composition mode if there is support for it in the framework.
+ */
+ public static void underlineWord(InputConnection ic, SelectedWord word) {
+ // Use reflection, for backward compatibility
+ // If method not found, there's nothing we can do. It still works but just wont underline
+ // the word.
+ if (!sMethodsInitialized) {
+ initializeMethodsForReflection();
+ }
+ if (sMethodSetComposingRegion != null) {
+ try {
+ sMethodSetComposingRegion.invoke(ic, word.start, word.end);
+ } catch (InvocationTargetException exc) {
+ // Ignore
+ } catch (IllegalArgumentException e) {
+ // Ignore
+ } catch (IllegalAccessException e) {
+ // Ignore
+ }
}
- return false;
}
}
diff --git a/java/src/com/android/inputmethod/latin/KeyDetector.java b/java/src/com/android/inputmethod/latin/KeyDetector.java
new file mode 100644
index 000000000..11d5f861d
--- /dev/null
+++ b/java/src/com/android/inputmethod/latin/KeyDetector.java
@@ -0,0 +1,56 @@
+/*
+ * Copyright (C) 2010 Google Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+
+package com.android.inputmethod.latin;
+
+import android.inputmethodservice.Keyboard;
+import android.inputmethodservice.Keyboard.Key;
+
+import java.util.List;
+
+abstract class KeyDetector {
+ protected Keyboard mKeyboard;
+ protected Key[] mKeys;
+
+ protected boolean mProximityCorrectOn;
+ protected int mProximityThresholdSquare;
+
+ public Key[] setKeyboard(Keyboard keyboard) {
+ if (keyboard == null)
+ throw new NullPointerException();
+ mKeyboard = keyboard;
+ List<Key> keys = mKeyboard.getKeys();
+ Key[] array = keys.toArray(new Key[keys.size()]);
+ mKeys = array;
+ return array;
+ }
+
+ public void setProximityCorrectionEnabled(boolean enabled) {
+ mProximityCorrectOn = enabled;
+ }
+
+ public boolean isProximityCorrectionEnabled() {
+ return mProximityCorrectOn;
+ }
+
+ public void setProximityThreshold(int threshold) {
+ mProximityThresholdSquare = threshold * threshold;
+ }
+
+ abstract public int[] newCodeArray();
+
+ abstract public int getKeyIndexAndNearbyCodes(int x, int y, int[] allKeys);
+} \ No newline at end of file
diff --git a/java/src/com/android/inputmethod/latin/LatinIME.java b/java/src/com/android/inputmethod/latin/LatinIME.java
index 9312ce2c8..76f774c96 100644
--- a/java/src/com/android/inputmethod/latin/LatinIME.java
+++ b/java/src/com/android/inputmethod/latin/LatinIME.java
@@ -86,7 +86,6 @@ public class LatinIME extends InputMethodService
static final boolean TRACE = false;
static final boolean VOICE_INSTALLED = true;
static final boolean ENABLE_VOICE_BUTTON = true;
- private static final boolean MODIFY_TEXT_FOR_CORRECTION = false;
private static final String PREF_VIBRATE_ON = "vibrate_on";
private static final String PREF_SOUND_ON = "sound_on";
@@ -767,16 +766,21 @@ public class LatinIME extends InputMethodService
mLastSelectionEnd = newSelEnd;
- // Check if we should go in or out of correction mode.
- if (isPredictionOn() && mJustRevertedSeparator == null
- && (candidatesStart == candidatesEnd || newSelStart != oldSelStart
- || TextEntryState.isCorrecting())
- && (newSelStart < newSelEnd - 1 || (!mPredicting))
- && !mVoiceInputHighlighted) {
- if (isCursorTouchingWord() || mLastSelectionStart < mLastSelectionEnd) {
- postUpdateOldSuggestions();
- } else {
- abortCorrection(false);
+ // Don't look for corrections if the keyboard is not visible
+ if (mKeyboardSwitcher != null && mKeyboardSwitcher.getInputView() != null
+ && mKeyboardSwitcher.getInputView().isShown()) {
+ // Check if we should go in or out of correction mode.
+ if (isPredictionOn()
+ && mJustRevertedSeparator == null
+ && (candidatesStart == candidatesEnd || newSelStart != oldSelStart
+ || TextEntryState.isCorrecting())
+ && (newSelStart < newSelEnd - 1 || (!mPredicting))
+ && !mVoiceInputHighlighted) {
+ if (isCursorTouchingWord() || mLastSelectionStart < mLastSelectionEnd) {
+ postUpdateOldSuggestions();
+ } else {
+ abortCorrection(false);
+ }
}
}
}
@@ -818,7 +822,7 @@ public class LatinIME extends InputMethodService
if (mCompletionOn) {
mCompletions = completions;
if (completions == null) {
- setSuggestions(null, false, false, false);
+ clearSuggestions();
return;
}
@@ -1253,7 +1257,7 @@ public class LatinIME extends InputMethodService
private void abortCorrection(boolean force) {
if (force || TextEntryState.isCorrecting()) {
getCurrentInputConnection().finishComposingText();
- setSuggestions(null, false, false, false);
+ clearSuggestions();
}
}
@@ -1266,7 +1270,9 @@ public class LatinIME extends InputMethodService
// Assume input length is 1. This assumption fails for smiley face insertions.
mVoiceInput.incrementTextModificationInsertCount(1);
}
- abortCorrection(false);
+ if (mLastSelectionStart == mLastSelectionEnd && TextEntryState.isCorrecting()) {
+ abortCorrection(false);
+ }
if (isAlphabet(primaryCode) && isPredictionOn() && !isCursorTouchingWord()) {
if (!mPredicting) {
@@ -1495,7 +1501,7 @@ public class LatinIME extends InputMethodService
}
// Clear N-best suggestions
- setSuggestions(null, false, false, true);
+ clearSuggestions();
FieldContext context = new FieldContext(
getCurrentInputConnection(),
@@ -1602,13 +1608,15 @@ public class LatinIME extends InputMethodService
mVoiceInputHighlighted = true;
mWordToSuggestions.putAll(mVoiceResults.alternatives);
+ }
+ private void clearSuggestions() {
+ setSuggestions(null, false, false, false);
}
private void setSuggestions(
List<CharSequence> suggestions,
boolean completions,
-
boolean typedWordValid,
boolean haveMinimalSuggestion) {
@@ -1652,14 +1660,14 @@ public class LatinIME extends InputMethodService
}
private void showSuggestions(WordComposer word) {
- //long startTime = System.currentTimeMillis(); // TIME MEASUREMENT!
+ // long startTime = System.currentTimeMillis(); // TIME MEASUREMENT!
// TODO Maybe need better way of retrieving previous word
CharSequence prevWord = EditingUtil.getPreviousWord(getCurrentInputConnection(),
mWordSeparators);
List<CharSequence> stringList = mSuggest.getSuggestions(
- mKeyboardSwitcher.getInputView(), word, false, prevWord);
- //long stopTime = System.currentTimeMillis(); // TIME MEASUREMENT!
- //Log.d("LatinIME","Suggest Total Time - " + (stopTime - startTime));
+ mKeyboardSwitcher.getInputView(), word, false, prevWord);
+ // long stopTime = System.currentTimeMillis(); // TIME MEASUREMENT!
+ // Log.d("LatinIME","Suggest Total Time - " + (stopTime - startTime));
int[] nextLettersFrequencies = mSuggest.getNextLettersFrequencies();
@@ -1780,18 +1788,23 @@ public class LatinIME extends InputMethodService
mJustAddedAutoSpace = true;
}
- // 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.
+ final boolean showingAddToDictionaryHint = index == 0 && mCorrectionMode > 0
+ && !mSuggest.isValidWord(suggestion)
+ && !mSuggest.isValidWord(suggestion.toString().toLowerCase());
+
if (!correcting) {
+ // 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) KEYCODE_SPACE, true);
setNextSuggestions();
- } else {
+ } else if (!showingAddToDictionaryHint) {
+ // If we're not showing the "Tap again to save hint", then show corrections again.
// In case the cursor position doesn't change, make sure we show the suggestions again.
+ clearSuggestions();
postUpdateOldSuggestions();
}
- if (index == 0 && mCorrectionMode > 0 && !mSuggest.isValidWord(suggestion)
- && !mSuggest.isValidWord(suggestion.toString().toLowerCase())) {
+ if (showingAddToDictionaryHint) {
mCandidateView.showAddToDictionaryHint(suggestion);
}
if (ic != null) {
@@ -1841,16 +1854,6 @@ public class LatinIME extends InputMethodService
InputConnection ic = getCurrentInputConnection();
if (ic != null) {
rememberReplacedWord(suggestion);
- // If text is in correction mode and we're not using composing
- // text to underline, then the word at the cursor position needs
- // to be removed before committing the correction
- if (correcting && !MODIFY_TEXT_FOR_CORRECTION) {
- if (mLastSelectionStart < mLastSelectionEnd) {
- ic.setSelection(mLastSelectionStart, mLastSelectionStart);
- }
- EditingUtil.deleteWordAtCursor(ic, getWordSeparators());
- }
-
ic.commitText(suggestion, 1);
}
saveWordInHistory(suggestion);
@@ -1864,96 +1867,108 @@ public class LatinIME extends InputMethodService
updateShiftKeyState(getCurrentInputEditorInfo());
}
+ /**
+ * Tries to apply any voice alternatives for the word if this was a spoken word and
+ * there are voice alternatives.
+ * @param touching The word that the cursor is touching, with position information
+ * @return true if an alternative was found, false otherwise.
+ */
+ private boolean applyVoiceAlternatives(EditingUtil.SelectedWord touching) {
+ // Search for result in spoken word alternatives
+ String selectedWord = touching.word.toString().trim();
+ if (!mWordToSuggestions.containsKey(selectedWord)) {
+ selectedWord = selectedWord.toLowerCase();
+ }
+ if (mWordToSuggestions.containsKey(selectedWord)) {
+ mShowingVoiceSuggestions = true;
+ List<CharSequence> suggestions = mWordToSuggestions.get(selectedWord);
+ // If the first letter of touching is capitalized, make all the suggestions
+ // start with a capital letter.
+ if (Character.isUpperCase((char) touching.word.charAt(0))) {
+ for (int i = 0; i < suggestions.size(); i++) {
+ String origSugg = (String) suggestions.get(i);
+ String capsSugg = origSugg.toUpperCase().charAt(0)
+ + origSugg.subSequence(1, origSugg.length()).toString();
+ suggestions.set(i, capsSugg);
+ }
+ }
+ setSuggestions(suggestions, false, true, true);
+ setCandidatesViewShown(true);
+ return true;
+ }
+ return false;
+ }
+
+ /**
+ * Tries to apply any typed alternatives for the word if we have any cached alternatives,
+ * otherwise tries to find new corrections and completions for the word.
+ * @param touching The word that the cursor is touching, with position information
+ * @return true if an alternative was found, false otherwise.
+ */
+ private boolean applyTypedAlternatives(EditingUtil.SelectedWord touching) {
+ // If we didn't find a match, search for result in typed word history
+ WordComposer foundWord = null;
+ WordAlternatives alternatives = null;
+ for (WordAlternatives entry : mWordHistory) {
+ if (TextUtils.equals(entry.getChosenWord(), touching.word)) {
+ if (entry instanceof TypedWordAlternatives) {
+ foundWord = ((TypedWordAlternatives) entry).word;
+ }
+ alternatives = entry;
+ break;
+ }
+ }
+ // If we didn't find a match, at least suggest completions
+ if (foundWord == null
+ && (mSuggest.isValidWord(touching.word)
+ || mSuggest.isValidWord(touching.word.toString().toLowerCase()))) {
+ foundWord = new WordComposer();
+ for (int i = 0; i < touching.word.length(); i++) {
+ foundWord.add(touching.word.charAt(i), new int[] {
+ touching.word.charAt(i)
+ });
+ }
+ foundWord.setCapitalized(Character.isUpperCase(touching.word.charAt(0)));
+ }
+ // Found a match, show suggestions
+ if (foundWord != null || alternatives != null) {
+ if (alternatives == null) {
+ alternatives = new TypedWordAlternatives(touching.word, foundWord);
+ }
+ showCorrections(alternatives);
+ if (foundWord != null) {
+ mWord = new WordComposer(foundWord);
+ } else {
+ mWord.reset();
+ }
+ return true;
+ }
+ return false;
+ }
+
private void setOldSuggestions() {
- // TODO: Inefficient to check if touching word and then get the touching word. Do it
- // in one go.
mShowingVoiceSuggestions = false;
+ if (mCandidateView != null && mCandidateView.isShowingAddToDictionaryHint()) {
+ return;
+ }
InputConnection ic = getCurrentInputConnection();
if (ic == null) return;
- ic.beginBatchEdit();
- // If there is a selection, then undo the selection first. Unfortunately this causes
- // a flicker. TODO: Add getSelectionText() to InputConnection API.
- if (mLastSelectionStart < mLastSelectionEnd) {
- ic.setSelection(mLastSelectionStart, mLastSelectionStart);
- }
- if (!mPredicting && isCursorTouchingWord()) {
- EditingUtil.Range range = new EditingUtil.Range();
- CharSequence touching = EditingUtil.getWordAtCursor(getCurrentInputConnection(),
- mWordSeparators, range);
- // If it's a selection, check if it's an entire word and no more, no less.
- boolean fullword = EditingUtil.isFullWordOrInside(range, mLastSelectionStart,
- mLastSelectionEnd);
- if (fullword && touching != null && touching.length() > 1) {
- // Strip out any trailing word separator
- if (mWordSeparators.indexOf(touching.charAt(touching.length() - 1)) > 0) {
- touching = touching.toString().substring(0, touching.length() - 1);
- }
+ if (!mPredicting) {
+ // Extract the selected or touching text
+ EditingUtil.SelectedWord touching = EditingUtil.getWordAtCursorOrSelection(ic,
+ mLastSelectionStart, mLastSelectionEnd, mWordSeparators);
- // Search for result in spoken word alternatives
- String selectedWord = touching.toString().trim();
- if (!mWordToSuggestions.containsKey(selectedWord)){
- selectedWord = selectedWord.toLowerCase();
- }
- if (mWordToSuggestions.containsKey(selectedWord)){
- mShowingVoiceSuggestions = true;
- underlineWord(touching, range.charsBefore, range.charsAfter);
- List<CharSequence> suggestions = mWordToSuggestions.get(selectedWord);
- // If the first letter of touching is capitalized, make all the suggestions
- // start with a capital letter.
- if (Character.isUpperCase((char) touching.charAt(0))) {
- for (int i=0; i< suggestions.size(); i++) {
- String origSugg = (String) suggestions.get(i);
- String capsSugg = origSugg.toUpperCase().charAt(0)
- + origSugg.subSequence(1, origSugg.length()).toString();
- suggestions.set(i,capsSugg);
- }
- }
- setSuggestions(suggestions, false, true, true);
- setCandidatesViewShown(true);
- TextEntryState.selectedForCorrection();
- ic.endBatchEdit();
- return;
- }
+ if (touching != null && touching.word.length() > 1) {
+ ic.beginBatchEdit();
- // If we didn't find a match, search for result in typed word history
- WordComposer foundWord = null;
- WordAlternatives alternatives = null;
- for (WordAlternatives entry : mWordHistory) {
- if (TextUtils.equals(entry.getChosenWord(), touching)) {
- if (entry instanceof TypedWordAlternatives) {
- foundWord = ((TypedWordAlternatives)entry).word;
- }
- alternatives = entry;
- break;
- }
- }
- // If we didn't find a match, at least suggest completions
- if (foundWord == null && mSuggest.isValidWord(touching)) {
- foundWord = new WordComposer();
- for (int i = 0; i < touching.length(); i++) {
- foundWord.add(touching.charAt(i), new int[] { touching.charAt(i) });
- }
- }
- // Found a match, show suggestions
- if (foundWord != null || alternatives != null) {
- underlineWord(touching, range.charsBefore, range.charsAfter);
+ if (!applyVoiceAlternatives(touching) && !applyTypedAlternatives(touching)) {
+ abortCorrection(true);
+ } else {
TextEntryState.selectedForCorrection();
- if (alternatives == null) alternatives = new TypedWordAlternatives(touching,
- foundWord);
- showCorrections(alternatives);
- if (foundWord != null) {
- mWord = new WordComposer(foundWord);
- } else {
- mWord.reset();
- }
- // Revert the selection
- if (mLastSelectionStart < mLastSelectionEnd) {
- ic.setSelection(mLastSelectionStart, mLastSelectionEnd);
- }
- ic.endBatchEdit();
- return;
+ EditingUtil.underlineWord(ic, touching);
}
- abortCorrection(true);
+
+ ic.endBatchEdit();
} else {
abortCorrection(true);
setNextSuggestions();
@@ -1961,28 +1976,12 @@ public class LatinIME extends InputMethodService
} else {
abortCorrection(true);
}
- // Revert the selection
- if (mLastSelectionStart < mLastSelectionEnd) {
- ic.setSelection(mLastSelectionStart, mLastSelectionEnd);
- }
- ic.endBatchEdit();
}
private void setNextSuggestions() {
setSuggestions(mSuggestPuncList, false, false, false);
}
- private void underlineWord(CharSequence word, int left, int right) {
- InputConnection ic = getCurrentInputConnection();
- if (ic == null) return;
- if (MODIFY_TEXT_FOR_CORRECTION) {
- ic.finishComposingText();
- ic.deleteSurroundingText(left, right);
- ic.setComposingText(word, 1);
- }
- ic.setSelection(mLastSelectionStart, mLastSelectionStart);
- }
-
private void addToDictionaries(CharSequence suggestion, int frequencyDelta) {
checkAddToDictionary(suggestion, frequencyDelta, false);
}
diff --git a/java/src/com/android/inputmethod/latin/LatinKeyboardBaseView.java b/java/src/com/android/inputmethod/latin/LatinKeyboardBaseView.java
index d1a5cd8e4..4daf6515f 100644
--- a/java/src/com/android/inputmethod/latin/LatinKeyboardBaseView.java
+++ b/java/src/com/android/inputmethod/latin/LatinKeyboardBaseView.java
@@ -45,7 +45,6 @@ import android.widget.TextView;
import java.util.ArrayList;
import java.util.HashMap;
-import java.util.List;
import java.util.Map;
/**
@@ -202,7 +201,7 @@ public class LatinKeyboardBaseView extends View implements View.OnClickListener,
private final ArrayList<PointerTracker> mPointerTrackers = new ArrayList<PointerTracker>();
private final float mDebounceHysteresis;
- private final ProximityKeyDetector mProximityKeyDetector = new ProximityKeyDetector();
+ protected KeyDetector mKeyDetector = new ProximityKeyDetector();
// Swipe gesture detector
private final GestureDetector mGestureDetector;
@@ -473,8 +472,7 @@ public class LatinKeyboardBaseView extends View implements View.OnClickListener,
public void setOnKeyboardActionListener(OnKeyboardActionListener listener) {
mKeyboardActionListener = listener;
for (PointerTracker tracker : mPointerTrackers) {
- if (tracker != null)
- tracker.setOnKeyboardActionListener(listener);
+ tracker.setOnKeyboardActionListener(listener);
}
}
@@ -501,13 +499,10 @@ public class LatinKeyboardBaseView extends View implements View.OnClickListener,
mHandler.cancelKeyTimers();
mHandler.cancelPopupPreview();
mKeyboard = keyboard;
- LatinImeLogger.onSetKeyboard(mKeyboard);
- List<Key> keys = mKeyboard.getKeys();
- mKeys = keys.toArray(new Key[keys.size()]);
- mProximityKeyDetector.setKeyboard(keyboard, mKeys);
+ LatinImeLogger.onSetKeyboard(keyboard);
+ mKeys = mKeyDetector.setKeyboard(keyboard);
for (PointerTracker tracker : mPointerTrackers) {
- if (tracker != null)
- tracker.setKeyboard(mKeys, mDebounceHysteresis);
+ tracker.setKeyboard(mKeys, mDebounceHysteresis);
}
requestLayout();
// Hint to reallocate the buffer if the size changed
@@ -599,14 +594,14 @@ public class LatinKeyboardBaseView extends View implements View.OnClickListener,
* @param enabled whether or not the proximity correction is enabled
*/
public void setProximityCorrectionEnabled(boolean enabled) {
- mProximityKeyDetector.setProximityCorrectionEnabled(enabled);
+ mKeyDetector.setProximityCorrectionEnabled(enabled);
}
/**
* Returns true if proximity correction is enabled.
*/
public boolean isProximityCorrectionEnabled() {
- return mProximityKeyDetector.isProximityCorrectionEnabled();
+ return mKeyDetector.isProximityCorrectionEnabled();
}
/**
@@ -658,7 +653,7 @@ public class LatinKeyboardBaseView extends View implements View.OnClickListener,
dimensionSum += Math.min(key.width, key.height) + key.gap;
}
if (dimensionSum < 0 || length == 0) return;
- mProximityKeyDetector.setProximityThreshold((int) (dimensionSum * 1.4f / length));
+ mKeyDetector.setProximityThreshold((int) (dimensionSum * 1.4f / length));
}
@Override
@@ -779,7 +774,6 @@ public class LatinKeyboardBaseView extends View implements View.OnClickListener,
if (DEBUG) {
if (mShowTouchPoints) {
for (PointerTracker tracker : mPointerTrackers) {
- if (tracker == null) continue;
int startX = tracker.getStartX();
int startY = tracker.getStartY();
int lastX = tracker.getLastX();
@@ -1052,7 +1046,7 @@ public class LatinKeyboardBaseView extends View implements View.OnClickListener,
// Create pointer trackers until we can get 'id+1'-th tracker, if needed.
for (int i = pointers.size(); i <= id; i++) {
final PointerTracker tracker =
- new PointerTracker(mHandler, mProximityKeyDetector, this);
+ new PointerTracker(i, mHandler, mKeyDetector, this);
if (keys != null)
tracker.setKeyboard(keys, mDebounceHysteresis);
if (listener != null)
diff --git a/java/src/com/android/inputmethod/latin/PointerTracker.java b/java/src/com/android/inputmethod/latin/PointerTracker.java
index 3c67ebece..c8976a372 100644
--- a/java/src/com/android/inputmethod/latin/PointerTracker.java
+++ b/java/src/com/android/inputmethod/latin/PointerTracker.java
@@ -32,6 +32,8 @@ public class PointerTracker {
public boolean isMiniKeyboardOnScreen();
}
+ public final int mPointerId;
+
// Timing constants
private static final int REPEAT_START_DELAY = 400;
/* package */ static final int REPEAT_INTERVAL = 50; // ~20 keys per second
@@ -45,7 +47,7 @@ public class PointerTracker {
private final UIProxy mProxy;
private final UIHandler mHandler;
- private final ProximityKeyDetector mKeyDetector;
+ private final KeyDetector mKeyDetector;
private OnKeyboardActionListener mListener;
private Key[] mKeys;
@@ -77,9 +79,10 @@ public class PointerTracker {
// pressed key
private int mPreviousKey = NOT_A_KEY;
- public PointerTracker(UIHandler handler, ProximityKeyDetector keyDetector, UIProxy proxy) {
+ public PointerTracker(int id, UIHandler handler, KeyDetector keyDetector, UIProxy proxy) {
if (proxy == null || handler == null || keyDetector == null)
throw new NullPointerException();
+ mPointerId = id;
mProxy = proxy;
mHandler = handler;
mKeyDetector = keyDetector;
@@ -97,21 +100,25 @@ public class PointerTracker {
mKeyDebounceThresholdSquared = (int)(hysteresisPixel * hysteresisPixel);
}
+ private boolean isValidKeyIndex(int keyIndex) {
+ return keyIndex >= 0 && keyIndex < mKeys.length;
+ }
+
public Key getKey(int keyIndex) {
- return (keyIndex >= 0 && keyIndex < mKeys.length) ? mKeys[keyIndex] : null;
+ return isValidKeyIndex(keyIndex) ? mKeys[keyIndex] : null;
}
public void updateKey(int keyIndex) {
int oldKeyIndex = mPreviousKey;
mPreviousKey = keyIndex;
if (keyIndex != oldKeyIndex) {
- if (oldKeyIndex != NOT_A_KEY && oldKeyIndex < mKeys.length) {
+ if (isValidKeyIndex(oldKeyIndex)) {
// if new key index is not a key, old key was just released inside of the key.
final boolean inside = (keyIndex == NOT_A_KEY);
mKeys[oldKeyIndex].onReleased(inside);
mProxy.invalidateKey(mKeys[oldKeyIndex]);
}
- if (keyIndex != NOT_A_KEY && keyIndex < mKeys.length) {
+ if (isValidKeyIndex(keyIndex)) {
mKeys[keyIndex].onPressed();
mProxy.invalidateKey(mKeys[keyIndex]);
}
@@ -127,14 +134,14 @@ public class PointerTracker {
startTimeDebouncing(eventTime);
checkMultiTap(eventTime, keyIndex);
if (mListener != null) {
- int primaryCode = (keyIndex != NOT_A_KEY) ? mKeys[keyIndex].codes[0] : 0;
+ int primaryCode = isValidKeyIndex(keyIndex) ? mKeys[keyIndex].codes[0] : 0;
mListener.onPress(primaryCode);
}
- if (keyIndex >= 0 && mKeys[keyIndex].repeatable) {
- repeatKey(keyIndex);
- mHandler.startKeyRepeatTimer(REPEAT_START_DELAY, keyIndex, this);
- }
- if (keyIndex != NOT_A_KEY) {
+ if (isValidKeyIndex(keyIndex)) {
+ if (mKeys[keyIndex].repeatable) {
+ repeatKey(keyIndex);
+ mHandler.startKeyRepeatTimer(REPEAT_START_DELAY, keyIndex, this);
+ }
mHandler.startLongPressTimer(keyIndex, LONGPRESS_TIMEOUT);
}
showKeyPreviewAndUpdateKey(keyIndex);
@@ -143,7 +150,7 @@ public class PointerTracker {
public void onMoveEvent(int touchX, int touchY, long eventTime) {
int keyIndex = mKeyDetector.getKeyIndexAndNearbyCodes(touchX, touchY, null);
- if (keyIndex != NOT_A_KEY) {
+ if (isValidKeyIndex(keyIndex)) {
if (mCurrentKey == NOT_A_KEY) {
updateTimeDebouncing(eventTime);
mCurrentKey = keyIndex;
@@ -192,7 +199,7 @@ public class PointerTracker {
if (!wasInKeyRepeat && !mProxy.isMiniKeyboardOnScreen()) {
detectAndSendKey(mCurrentKey, touchX, touchY, eventTime);
}
- if (keyIndex != NOT_A_KEY && keyIndex < mKeys.length)
+ if (isValidKeyIndex(keyIndex))
mProxy.invalidateKey(mKeys[keyIndex]);
}
@@ -202,15 +209,17 @@ public class PointerTracker {
mProxy.dismissPopupKeyboard();
showKeyPreviewAndUpdateKey(NOT_A_KEY);
int keyIndex = mCurrentKey;
- if (keyIndex != NOT_A_KEY && keyIndex < mKeys.length)
+ if (isValidKeyIndex(keyIndex))
mProxy.invalidateKey(mKeys[keyIndex]);
}
public void repeatKey(int keyIndex) {
- Key key = mKeys[keyIndex];
- // While key is repeating, because there is no need to handle multi-tap key, we can pass
- // -1 as eventTime argument.
- detectAndSendKey(keyIndex, key.x, key.y, -1);
+ Key key = getKey(keyIndex);
+ if (key != null) {
+ // While key is repeating, because there is no need to handle multi-tap key, we can
+ // pass -1 as eventTime argument.
+ detectAndSendKey(keyIndex, key.x, key.y, -1);
+ }
}
// These package scope methods are only for debugging purpose.
@@ -250,7 +259,7 @@ public class PointerTracker {
throw new IllegalStateException("keyboard and/or hysteresis not set");
if (newKey == curKey) {
return true;
- } else if (curKey >= 0 && curKey < mKeys.length) {
+ } else if (isValidKeyIndex(curKey)) {
return getSquareDistanceToKeyEdge(x, y, mKeys[curKey])
< mKeyDebounceThresholdSquared;
} else {
@@ -300,7 +309,7 @@ public class PointerTracker {
}
private void detectAndSendKey(int index, int x, int y, long eventTime) {
- if (index != NOT_A_KEY && index < mKeys.length) {
+ if (isValidKeyIndex(index)) {
final Key key = mKeys[index];
OnKeyboardActionListener listener = mListener;
if (key.text != null) {
@@ -363,11 +372,15 @@ public class PointerTracker {
}
private void checkMultiTap(long eventTime, int keyIndex) {
- if (keyIndex == NOT_A_KEY) return;
- Key key = mKeys[keyIndex];
+ Key key = getKey(keyIndex);
+ if (key == null)
+ return;
+
+ final boolean isMultiTap =
+ (eventTime < mLastTapTime + MULTITAP_INTERVAL && keyIndex == mLastSentIndex);
if (key.codes.length > 1) {
mInMultiTap = true;
- if (eventTime < mLastTapTime + MULTITAP_INTERVAL && keyIndex == mLastSentIndex) {
+ if (isMultiTap) {
mTapCount = (mTapCount + 1) % key.codes.length;
return;
} else {
@@ -375,7 +388,7 @@ public class PointerTracker {
return;
}
}
- if (eventTime > mLastTapTime + MULTITAP_INTERVAL || keyIndex != mLastSentIndex) {
+ if (!isMultiTap) {
resetMultiTap();
}
}
diff --git a/java/src/com/android/inputmethod/latin/ProximityKeyDetector.java b/java/src/com/android/inputmethod/latin/ProximityKeyDetector.java
index eae2d7f08..6ee005510 100644
--- a/java/src/com/android/inputmethod/latin/ProximityKeyDetector.java
+++ b/java/src/com/android/inputmethod/latin/ProximityKeyDetector.java
@@ -16,48 +16,24 @@
package com.android.inputmethod.latin;
-import android.inputmethodservice.Keyboard;
import android.inputmethodservice.Keyboard.Key;
import java.util.Arrays;
-class ProximityKeyDetector {
+class ProximityKeyDetector extends KeyDetector {
private static final int MAX_NEARBY_KEYS = 12;
- private Keyboard mKeyboard;
- private Key[] mKeys;
-
- private boolean mProximityCorrectOn;
- private int mProximityThresholdSquare;
-
// working area
private int[] mDistances = new int[MAX_NEARBY_KEYS];
- public void setKeyboard(Keyboard keyboard, Key[] keys) {
- if (keyboard == null || keys == null)
- throw new NullPointerException();
- mKeyboard = keyboard;
- mKeys = keys;
- }
-
- public void setProximityCorrectionEnabled(boolean enabled) {
- mProximityCorrectOn = enabled;
- }
-
- public boolean isProximityCorrectionEnabled() {
- return mProximityCorrectOn;
- }
-
- public void setProximityThreshold(int threshold) {
- mProximityThresholdSquare = threshold * threshold;
- }
-
+ @Override
public int[] newCodeArray() {
int[] codes = new int[MAX_NEARBY_KEYS];
Arrays.fill(codes, LatinKeyboardBaseView.NOT_A_KEY);
return codes;
}
+ @Override
public int getKeyIndexAndNearbyCodes(int x, int y, int[] allKeys) {
final Key[] keys = mKeys;
if (keys == null)