diff options
Diffstat (limited to 'java/src')
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) |