Use user-history bigrams when no input if available.
This also fixes a small inconsistency upon clicking on whitespace twice in a row. Also add some unit tests for an introduced and an existing method. Change-Id: I1be2fb53c9624f4d0f5299009632cb4384fdfc15
This commit is contained in:
parent
6c10cf6bf8
commit
89bd776cf6
6 changed files with 235 additions and 37 deletions
|
@ -197,6 +197,11 @@ public class BinaryDictionary extends Dictionary {
|
|||
Arrays.fill(mBigramScores, 0);
|
||||
|
||||
int codesSize = codes.size();
|
||||
if (codesSize <= 0) {
|
||||
// Do not return bigrams from BinaryDictionary when nothing was typed.
|
||||
// Only use user-history bigrams (or whatever other bigram dictionaries decide).
|
||||
return;
|
||||
}
|
||||
Arrays.fill(mInputCodes, -1);
|
||||
int[] alternatives = codes.getCodesAt(0);
|
||||
System.arraycopy(alternatives, 0, mInputCodes, 0,
|
||||
|
|
|
@ -161,23 +161,62 @@ public class EditingUtils {
|
|||
|
||||
private static final Pattern spaceRegex = Pattern.compile("\\s+");
|
||||
|
||||
|
||||
public static CharSequence getPreviousWord(InputConnection connection,
|
||||
String sentenceSeperators) {
|
||||
//TODO: Should fix this. This could be slow!
|
||||
CharSequence prev = connection.getTextBeforeCursor(LOOKBACK_CHARACTER_NUM, 0);
|
||||
if (prev == null) {
|
||||
return null;
|
||||
}
|
||||
return getPreviousWord(prev, sentenceSeperators);
|
||||
}
|
||||
|
||||
// Get the word before the whitespace preceding the non-whitespace preceding the cursor.
|
||||
// Also, it won't return words that end in a separator.
|
||||
// Example :
|
||||
// "abc def|" -> abc
|
||||
// "abc def |" -> abc
|
||||
// "abc def. |" -> abc
|
||||
// "abc def . |" -> def
|
||||
// "abc|" -> null
|
||||
// "abc |" -> null
|
||||
// "abc. def|" -> null
|
||||
public static CharSequence getPreviousWord(CharSequence prev, String sentenceSeperators) {
|
||||
if (prev == null) return null;
|
||||
String[] w = spaceRegex.split(prev);
|
||||
if (w.length >= 2 && w[w.length-2].length() > 0) {
|
||||
char lastChar = w[w.length-2].charAt(w[w.length-2].length() -1);
|
||||
if (sentenceSeperators.contains(String.valueOf(lastChar))) {
|
||||
return null;
|
||||
}
|
||||
return w[w.length-2];
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
|
||||
// If we can't find two words, or we found an empty word, return null.
|
||||
if (w.length < 2 || w[w.length - 2].length() <= 0) return null;
|
||||
|
||||
// If ends in a separator, return null
|
||||
char lastChar = w[w.length - 2].charAt(w[w.length - 2].length() - 1);
|
||||
if (sentenceSeperators.contains(String.valueOf(lastChar))) return null;
|
||||
|
||||
return w[w.length - 2];
|
||||
}
|
||||
|
||||
public static CharSequence getThisWord(InputConnection connection, String sentenceSeperators) {
|
||||
final CharSequence prev = connection.getTextBeforeCursor(LOOKBACK_CHARACTER_NUM, 0);
|
||||
return getThisWord(prev, sentenceSeperators);
|
||||
}
|
||||
|
||||
// Get the word immediately before the cursor, even if there is whitespace between it and
|
||||
// the cursor - but not if there is punctuation.
|
||||
// Example :
|
||||
// "abc def|" -> def
|
||||
// "abc def |" -> def
|
||||
// "abc def. |" -> null
|
||||
// "abc def . |" -> null
|
||||
public static CharSequence getThisWord(CharSequence prev, String sentenceSeperators) {
|
||||
if (prev == null) return null;
|
||||
String[] w = spaceRegex.split(prev);
|
||||
|
||||
// No word : return null
|
||||
if (w.length < 1 || w[w.length - 1].length() <= 0) return null;
|
||||
|
||||
// If ends in a separator, return null
|
||||
char lastChar = w[w.length - 1].charAt(w[w.length - 1].length() - 1);
|
||||
if (sentenceSeperators.contains(String.valueOf(lastChar))) return null;
|
||||
|
||||
return w[w.length - 1];
|
||||
}
|
||||
|
||||
public static class SelectedWord {
|
||||
|
|
|
@ -266,6 +266,7 @@ public class LatinIME extends InputMethodServiceCompatWrapper implements Keyboar
|
|||
private static final int MSG_FADEOUT_LANGUAGE_ON_SPACEBAR = 4;
|
||||
private static final int MSG_DISMISS_LANGUAGE_ON_SPACEBAR = 5;
|
||||
private static final int MSG_SPACE_TYPED = 6;
|
||||
private static final int MSG_SET_BIGRAM_SUGGESTIONS = 7;
|
||||
|
||||
@Override
|
||||
public void handleMessage(Message msg) {
|
||||
|
@ -281,6 +282,9 @@ public class LatinIME extends InputMethodServiceCompatWrapper implements Keyboar
|
|||
case MSG_UPDATE_SHIFT_STATE:
|
||||
switcher.updateShiftState();
|
||||
break;
|
||||
case MSG_SET_BIGRAM_SUGGESTIONS:
|
||||
updateBigramSuggestions();
|
||||
break;
|
||||
case MSG_VOICE_RESULTS:
|
||||
mVoiceProxy.handleVoiceResults(preferCapitalization()
|
||||
|| (switcher.isAlphabetMode() && switcher.isShiftedOrShiftLocked()));
|
||||
|
@ -333,6 +337,15 @@ public class LatinIME extends InputMethodServiceCompatWrapper implements Keyboar
|
|||
removeMessages(MSG_UPDATE_SHIFT_STATE);
|
||||
}
|
||||
|
||||
public void postSetBigramSuggestions() {
|
||||
removeMessages(MSG_SET_BIGRAM_SUGGESTIONS);
|
||||
sendMessageDelayed(obtainMessage(MSG_SET_BIGRAM_SUGGESTIONS), DELAY_UPDATE_SUGGESTIONS);
|
||||
}
|
||||
|
||||
public void cancelSetBigramSuggestions() {
|
||||
removeMessages(MSG_SET_BIGRAM_SUGGESTIONS);
|
||||
}
|
||||
|
||||
public void updateVoiceResults() {
|
||||
sendMessage(obtainMessage(MSG_VOICE_RESULTS));
|
||||
}
|
||||
|
@ -548,7 +561,7 @@ public class LatinIME extends InputMethodServiceCompatWrapper implements Keyboar
|
|||
final KeyboardSwitcher switcher = mKeyboardSwitcher;
|
||||
LatinKeyboardView inputView = switcher.getInputView();
|
||||
|
||||
if(DEBUG) {
|
||||
if (DEBUG) {
|
||||
Log.d(TAG, "onStartInputView: " + inputView);
|
||||
}
|
||||
// In landscape mode, this method gets called without the input view being created.
|
||||
|
@ -754,7 +767,12 @@ public class LatinIME extends InputMethodServiceCompatWrapper implements Keyboar
|
|||
}
|
||||
mComposing.setLength(0);
|
||||
mHasValidSuggestions = false;
|
||||
mHandler.postUpdateSuggestions();
|
||||
if (isCursorTouchingWord()) {
|
||||
mHandler.cancelSetBigramSuggestions();
|
||||
mHandler.postUpdateSuggestions();
|
||||
} else {
|
||||
setPunctuationSuggestions();
|
||||
}
|
||||
TextEntryState.reset();
|
||||
InputConnection ic = getCurrentInputConnection();
|
||||
if (ic != null) {
|
||||
|
@ -784,14 +802,20 @@ public class LatinIME extends InputMethodServiceCompatWrapper implements Keyboar
|
|||
|| TextEntryState.isRecorrecting())
|
||||
&& (newSelStart < newSelEnd - 1 || !mHasValidSuggestions)) {
|
||||
if (isCursorTouchingWord() || mLastSelectionStart < mLastSelectionEnd) {
|
||||
mHandler.cancelSetBigramSuggestions();
|
||||
mHandler.postUpdateOldSuggestions();
|
||||
} else {
|
||||
abortRecorrection(false);
|
||||
// Show the punctuation suggestions list if the current one is not
|
||||
// and if not showing "Touch again to save".
|
||||
if (mCandidateView != null && !isShowingPunctuationList()
|
||||
// If showing the "touch again to save" hint, do not replace it. Else,
|
||||
// show the bigrams if we are at the end of the text, punctuation otherwise.
|
||||
if (mCandidateView != null
|
||||
&& !mCandidateView.isShowingAddToDictionaryHint()) {
|
||||
setPunctuationSuggestions();
|
||||
InputConnection ic = getCurrentInputConnection();
|
||||
if (null == ic || !TextUtils.isEmpty(ic.getTextAfterCursor(1, 0))) {
|
||||
if (!isShowingPunctuationList()) setPunctuationSuggestions();
|
||||
} else {
|
||||
mHandler.postSetBigramSuggestions();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1231,7 +1255,14 @@ public class LatinIME extends InputMethodServiceCompatWrapper implements Keyboar
|
|||
if (mComposing.length() == 0) {
|
||||
mHasValidSuggestions = false;
|
||||
}
|
||||
mHandler.postUpdateSuggestions();
|
||||
if (1 == length) {
|
||||
// 1 == length means we are about to erase the last character of the word,
|
||||
// so we can show bigrams.
|
||||
mHandler.postSetBigramSuggestions();
|
||||
} else {
|
||||
// length > 1, so we still have letters to deduce a suggestion from.
|
||||
mHandler.postUpdateSuggestions();
|
||||
}
|
||||
} else {
|
||||
ic.deleteSurroundingText(1, 0);
|
||||
}
|
||||
|
@ -1367,6 +1398,7 @@ public class LatinIME extends InputMethodServiceCompatWrapper implements Keyboar
|
|||
|
||||
// Should dismiss the "Touch again to save" message when handling separator
|
||||
if (mCandidateView != null && mCandidateView.dismissAddToDictionaryHint()) {
|
||||
mHandler.cancelSetBigramSuggestions();
|
||||
mHandler.postUpdateSuggestions();
|
||||
}
|
||||
|
||||
|
@ -1406,6 +1438,7 @@ public class LatinIME extends InputMethodServiceCompatWrapper implements Keyboar
|
|||
}
|
||||
|
||||
TextEntryState.typedCharacter((char) primaryCode, true, x, y);
|
||||
|
||||
if (TextEntryState.isPunctuationAfterAccepted() && primaryCode != Keyboard.CODE_ENTER) {
|
||||
swapPunctuationAndSpace();
|
||||
} else if (isSuggestionsRequested() && primaryCode == Keyboard.CODE_SPACE) {
|
||||
|
@ -1416,10 +1449,20 @@ public class LatinIME extends InputMethodServiceCompatWrapper implements Keyboar
|
|||
TextEntryState.backToAcceptedDefault(typedWord);
|
||||
if (!TextUtils.isEmpty(typedWord) && !typedWord.equals(mBestWord)) {
|
||||
InputConnectionCompatUtils.commitCorrection(
|
||||
ic, mLastSelectionEnd - typedWord.length(), typedWord, mBestWord);
|
||||
ic, mLastSelectionEnd - typedWord.length(), typedWord, mBestWord);
|
||||
if (mCandidateView != null)
|
||||
mCandidateView.onAutoCorrectionInverted(mBestWord);
|
||||
}
|
||||
}
|
||||
if (Keyboard.CODE_SPACE == primaryCode) {
|
||||
if (!isCursorTouchingWord()) {
|
||||
mHandler.cancelUpdateSuggestions();
|
||||
mHandler.cancelUpdateOldSuggestions();
|
||||
mHandler.postSetBigramSuggestions();
|
||||
}
|
||||
} else {
|
||||
// Set punctuation right away. onUpdateSelection will fire but tests whether it is
|
||||
// already displayed or not, so it's okay.
|
||||
setPunctuationSuggestions();
|
||||
}
|
||||
mKeyboardSwitcher.updateShiftState();
|
||||
|
@ -1654,6 +1697,11 @@ public class LatinIME extends InputMethodServiceCompatWrapper implements Keyboar
|
|||
}
|
||||
return;
|
||||
}
|
||||
if (!mHasValidSuggestions) {
|
||||
// If we are not composing a word, then it was a suggestion inferred from
|
||||
// context - no user input. We should reset the word composer.
|
||||
mWord.reset();
|
||||
}
|
||||
mJustAccepted = true;
|
||||
pickSuggestion(suggestion);
|
||||
// Add the word to the auto dictionary if it's not a known word
|
||||
|
@ -1692,7 +1740,7 @@ public class LatinIME extends InputMethodServiceCompatWrapper implements Keyboar
|
|||
// TextEntryState.State.PICKED_SUGGESTION state.
|
||||
TextEntryState.typedCharacter((char) Keyboard.CODE_SPACE, true,
|
||||
WordComposer.NOT_A_COORDINATE, WordComposer.NOT_A_COORDINATE);
|
||||
setPunctuationSuggestions();
|
||||
// From there on onUpdateSelection() will fire so suggestions will be updated
|
||||
} else 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.
|
||||
|
@ -1807,6 +1855,25 @@ public class LatinIME extends InputMethodServiceCompatWrapper implements Keyboar
|
|||
}
|
||||
}
|
||||
|
||||
private static final WordComposer sEmptyWordComposer = new WordComposer();
|
||||
private void updateBigramSuggestions() {
|
||||
if (mSuggest == null || !isSuggestionsRequested())
|
||||
return;
|
||||
|
||||
final CharSequence prevWord = EditingUtils.getThisWord(getCurrentInputConnection(),
|
||||
mWordSeparators);
|
||||
SuggestedWords.Builder builder = mSuggest.getSuggestedWordBuilder(
|
||||
mKeyboardSwitcher.getInputView(), sEmptyWordComposer, prevWord);
|
||||
|
||||
if (builder.size() > 0) {
|
||||
// Explicitly supply an empty typed word (the no-second-arg version of
|
||||
// showSuggestions will retrieve the word near the cursor, we don't want that here)
|
||||
showSuggestions(builder.build(), "");
|
||||
} else {
|
||||
if (!isShowingPunctuationList()) setPunctuationSuggestions();
|
||||
}
|
||||
}
|
||||
|
||||
private void setPunctuationSuggestions() {
|
||||
setSuggestions(mSuggestPuncList);
|
||||
setCandidatesViewShown(isCandidateStripVisible());
|
||||
|
@ -1907,6 +1974,7 @@ public class LatinIME extends InputMethodServiceCompatWrapper implements Keyboar
|
|||
ic.setComposingText(mComposing, 1);
|
||||
TextEntryState.backspace();
|
||||
}
|
||||
mHandler.cancelSetBigramSuggestions();
|
||||
mHandler.postUpdateSuggestions();
|
||||
} else {
|
||||
sendDownUpKeyEvents(KeyEvent.KEYCODE_DEL);
|
||||
|
|
|
@ -265,6 +265,16 @@ public class Suggest implements Dictionary.WordCallback {
|
|||
return sb;
|
||||
}
|
||||
|
||||
protected void addBigramToSuggestions(CharSequence bigram) {
|
||||
final int poolSize = mStringPool.size();
|
||||
final StringBuilder sb = poolSize > 0 ?
|
||||
(StringBuilder) mStringPool.remove(poolSize - 1)
|
||||
: new StringBuilder(getApproxMaxWordLength());
|
||||
sb.setLength(0);
|
||||
sb.append(bigram);
|
||||
mSuggestions.add(sb);
|
||||
}
|
||||
|
||||
// TODO: cleanup dictionaries looking up and suggestions building with SuggestedWords.Builder
|
||||
public SuggestedWords.Builder getSuggestedWordBuilder(View view, WordComposer wordComposer,
|
||||
CharSequence prevWordForBigram) {
|
||||
|
@ -286,7 +296,7 @@ public class Suggest implements Dictionary.WordCallback {
|
|||
}
|
||||
mTypedWord = typedWord;
|
||||
|
||||
if (wordComposer.size() == 1 && (mCorrectionMode == CORRECTION_FULL_BIGRAM
|
||||
if (wordComposer.size() <= 1 && (mCorrectionMode == CORRECTION_FULL_BIGRAM
|
||||
|| mCorrectionMode == CORRECTION_BASIC)) {
|
||||
// At first character typed, search only the bigrams
|
||||
Arrays.fill(mBigramScores, 0);
|
||||
|
@ -300,21 +310,26 @@ public class Suggest implements Dictionary.WordCallback {
|
|||
for (final Dictionary dictionary : mBigramDictionaries.values()) {
|
||||
dictionary.getBigrams(wordComposer, prevWordForBigram, this);
|
||||
}
|
||||
char currentChar = wordComposer.getTypedWord().charAt(0);
|
||||
char currentCharUpper = Character.toUpperCase(currentChar);
|
||||
int count = 0;
|
||||
int bigramSuggestionSize = mBigramSuggestions.size();
|
||||
for (int i = 0; i < bigramSuggestionSize; i++) {
|
||||
if (mBigramSuggestions.get(i).charAt(0) == currentChar
|
||||
|| mBigramSuggestions.get(i).charAt(0) == currentCharUpper) {
|
||||
int poolSize = mStringPool.size();
|
||||
StringBuilder sb = poolSize > 0 ?
|
||||
(StringBuilder) mStringPool.remove(poolSize - 1)
|
||||
: new StringBuilder(getApproxMaxWordLength());
|
||||
sb.setLength(0);
|
||||
sb.append(mBigramSuggestions.get(i));
|
||||
mSuggestions.add(count++, sb);
|
||||
if (count > mPrefMaxSuggestions) break;
|
||||
if (TextUtils.isEmpty(typedWord)) {
|
||||
// Nothing entered: return all bigrams for the previous word
|
||||
int insertCount = Math.min(mBigramSuggestions.size(), mPrefMaxSuggestions);
|
||||
for (int i = 0; i < insertCount; ++i) {
|
||||
addBigramToSuggestions(mBigramSuggestions.get(i));
|
||||
}
|
||||
} else {
|
||||
// Word entered: return only bigrams that match the first char of the typed word
|
||||
final char currentChar = typedWord.charAt(0);
|
||||
final char currentCharUpper = Character.toUpperCase(currentChar);
|
||||
int count = 0;
|
||||
final int bigramSuggestionSize = mBigramSuggestions.size();
|
||||
for (int i = 0; i < bigramSuggestionSize; i++) {
|
||||
final CharSequence bigramSuggestion = mBigramSuggestions.get(i);
|
||||
final char bigramSuggestionFirstChar = bigramSuggestion.charAt(0);
|
||||
if (bigramSuggestionFirstChar == currentChar
|
||||
|| bigramSuggestionFirstChar == currentCharUpper) {
|
||||
addBigramToSuggestions(bigramSuggestion);
|
||||
if (++count > mPrefMaxSuggestions) break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -162,6 +162,10 @@ public class UserBigramDictionary extends ExpandableDictionary {
|
|||
if (mIme != null && mIme.getCurrentWord().isAutoCapitalized()) {
|
||||
word2 = Character.toLowerCase(word2.charAt(0)) + word2.substring(1);
|
||||
}
|
||||
// Do not insert a word as a bigram of itself
|
||||
if (word1.equals(word2)) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
int freq = super.addBigram(word1, word2, FREQUENCY_FOR_TYPED);
|
||||
if (freq > FREQUENCY_MAX) freq = FREQUENCY_MAX;
|
||||
|
|
67
tests/src/com/android/inputmethod/latin/UtilsTests.java
Normal file
67
tests/src/com/android/inputmethod/latin/UtilsTests.java
Normal file
|
@ -0,0 +1,67 @@
|
|||
/*
|
||||
* Copyright (C) 2010,2011 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License"); you may not
|
||||
* use this file except in compliance with the License. You may obtain a copy of
|
||||
* the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
* License for the specific language governing permissions and limitations under
|
||||
* the License.
|
||||
*/
|
||||
|
||||
package com.android.inputmethod.latin;
|
||||
|
||||
import android.test.AndroidTestCase;
|
||||
|
||||
import com.android.inputmethod.latin.tests.R;
|
||||
|
||||
public class UtilsTests extends AndroidTestCase {
|
||||
|
||||
// The following is meant to be a reasonable default for
|
||||
// the "word_separators" resource.
|
||||
private static final String sSeparators = ".,:;!?-";
|
||||
|
||||
@Override
|
||||
protected void setUp() throws Exception {
|
||||
super.setUp();
|
||||
}
|
||||
|
||||
/************************** Tests ************************/
|
||||
|
||||
/**
|
||||
* Test for getting previous word (for bigram suggestions)
|
||||
*/
|
||||
public void testGetPreviousWord() {
|
||||
// If one of the following cases breaks, the bigram suggestions won't work.
|
||||
assertEquals(EditingUtils.getPreviousWord("abc def", sSeparators), "abc");
|
||||
assertNull(EditingUtils.getPreviousWord("abc", sSeparators));
|
||||
assertNull(EditingUtils.getPreviousWord("abc. def", sSeparators));
|
||||
|
||||
// The following tests reflect the current behavior of the function
|
||||
// EditingUtils#getPreviousWord.
|
||||
// TODO: However at this time, the code does never go
|
||||
// into such a path, so it should be safe to change the behavior of
|
||||
// this function if needed - especially since it does not seem very
|
||||
// logical. These tests are just there to catch any unintentional
|
||||
// changes in the behavior of the EditingUtils#getPreviousWord method.
|
||||
assertEquals(EditingUtils.getPreviousWord("abc def ", sSeparators), "abc");
|
||||
assertEquals(EditingUtils.getPreviousWord("abc def.", sSeparators), "abc");
|
||||
assertEquals(EditingUtils.getPreviousWord("abc def .", sSeparators), "def");
|
||||
assertNull(EditingUtils.getPreviousWord("abc ", sSeparators));
|
||||
}
|
||||
|
||||
/**
|
||||
* Test for getting the word before the cursor (for bigram)
|
||||
*/
|
||||
public void testGetThisWord() {
|
||||
assertEquals(EditingUtils.getThisWord("abc def", sSeparators), "def");
|
||||
assertEquals(EditingUtils.getThisWord("abc def ", sSeparators), "def");
|
||||
assertNull(EditingUtils.getThisWord("abc def.", sSeparators));
|
||||
assertNull(EditingUtils.getThisWord("abc def .", sSeparators));
|
||||
}
|
||||
}
|
Loading…
Reference in a new issue