aboutsummaryrefslogtreecommitdiffstats
path: root/java/src/com/android/inputmethod/latin
diff options
context:
space:
mode:
Diffstat (limited to 'java/src/com/android/inputmethod/latin')
-rw-r--r--java/src/com/android/inputmethod/latin/AdditionalSubtypeSettings.java1
-rw-r--r--java/src/com/android/inputmethod/latin/BinaryDictionary.java12
-rw-r--r--java/src/com/android/inputmethod/latin/LatinIME.java35
-rw-r--r--java/src/com/android/inputmethod/latin/StringUtils.java17
-rw-r--r--java/src/com/android/inputmethod/latin/Suggest.java34
-rw-r--r--java/src/com/android/inputmethod/latin/makedict/BinaryDictInputOutput.java41
-rw-r--r--java/src/com/android/inputmethod/latin/suggestions/SuggestionsView.java25
7 files changed, 116 insertions, 49 deletions
diff --git a/java/src/com/android/inputmethod/latin/AdditionalSubtypeSettings.java b/java/src/com/android/inputmethod/latin/AdditionalSubtypeSettings.java
index 613c20304..994b917a7 100644
--- a/java/src/com/android/inputmethod/latin/AdditionalSubtypeSettings.java
+++ b/java/src/com/android/inputmethod/latin/AdditionalSubtypeSettings.java
@@ -366,6 +366,7 @@ public class AdditionalSubtypeSettings extends PreferenceFragment {
final Preference pref = mSubtypePrefGroup.getPreference(i);
if (pref instanceof SubtypePreference) {
final InputMethodSubtype subtype = ((SubtypePreference)pref).getSubtype();
+ if (subtype == null) continue;
if (sb.length() > 0) {
sb.append(AdditionalSubtype.PREF_SUBTYPE_SEPARATOR);
}
diff --git a/java/src/com/android/inputmethod/latin/BinaryDictionary.java b/java/src/com/android/inputmethod/latin/BinaryDictionary.java
index 9429ef411..a644ec0d9 100644
--- a/java/src/com/android/inputmethod/latin/BinaryDictionary.java
+++ b/java/src/com/android/inputmethod/latin/BinaryDictionary.java
@@ -83,11 +83,11 @@ public class BinaryDictionary extends Dictionary {
private native long openNative(String sourceDir, long dictOffset, long dictSize,
int typedLetterMultiplier, int fullWordMultiplier, int maxWordLength, int maxWords);
private native void closeNative(long dict);
- private native boolean isValidWordNative(long dict, char[] word, int wordLength);
+ private native boolean isValidWordNative(long dict, int[] word, int wordLength);
private native int getSuggestionsNative(long dict, long proximityInfo, int[] xCoordinates,
int[] yCoordinates, int[] inputCodes, int codesSize, int[] prevWordForBigrams,
boolean useFullEditDistance, char[] outputChars, int[] scores);
- private native int getBigramsNative(long dict, char[] prevWord, int prevWordLength,
+ private native int getBigramsNative(long dict, int[] prevWord, int prevWordLength,
int[] inputCodes, int inputCodesLength, char[] outputChars, int[] scores,
int maxWordLength, int maxBigrams);
private static native double calcNormalizedScoreNative(
@@ -105,7 +105,7 @@ public class BinaryDictionary extends Dictionary {
final WordCallback callback) {
if (mNativeDict == 0) return;
- char[] chars = previousWord.toString().toCharArray();
+ int[] codePoints = StringUtils.toCodePointArray(previousWord.toString());
Arrays.fill(mOutputChars_bigrams, (char) 0);
Arrays.fill(mBigramScores, 0);
@@ -115,8 +115,8 @@ public class BinaryDictionary extends Dictionary {
mInputCodes[0] = codes.getCodeAt(0);
}
- int count = getBigramsNative(mNativeDict, chars, chars.length, mInputCodes, codesSize,
- mOutputChars_bigrams, mBigramScores, MAX_WORD_LENGTH, MAX_BIGRAMS);
+ int count = getBigramsNative(mNativeDict, codePoints, codePoints.length, mInputCodes,
+ codesSize, mOutputChars_bigrams, mBigramScores, MAX_WORD_LENGTH, MAX_BIGRAMS);
if (count > MAX_BIGRAMS) {
count = MAX_BIGRAMS;
}
@@ -200,7 +200,7 @@ public class BinaryDictionary extends Dictionary {
@Override
public boolean isValidWord(CharSequence word) {
if (word == null) return false;
- char[] chars = word.toString().toCharArray();
+ int[] chars = StringUtils.toCodePointArray(word.toString());
return isValidWordNative(mNativeDict, chars, chars.length);
}
diff --git a/java/src/com/android/inputmethod/latin/LatinIME.java b/java/src/com/android/inputmethod/latin/LatinIME.java
index e1978fca1..c6381180c 100644
--- a/java/src/com/android/inputmethod/latin/LatinIME.java
+++ b/java/src/com/android/inputmethod/latin/LatinIME.java
@@ -1793,6 +1793,24 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
public void pickSuggestionManually(final int index, final CharSequence suggestion,
int x, int y) {
final SuggestedWords suggestedWords = mSuggestionsView.getSuggestions();
+ final InputConnection ic = getCurrentInputConnection();
+ if (ic != null) ic.beginBatchEdit();
+
+ // If this is a punctuation picked from the suggestion strip, pass it to onCodeInput
+ if (suggestion.length() == 1 && isShowingPunctuationList()) {
+ // Word separators are suggested before the user inputs something.
+ // So, LatinImeLogger logs "" as a user's input.
+ LatinImeLogger.logOnManualSuggestion("", suggestion.toString(), index, suggestedWords);
+ // Rely on onCodeInput to do the complicated swapping/stripping logic consistently.
+ if (ProductionFlag.IS_EXPERIMENTAL) {
+ ResearchLogger.latinIME_punctuationSuggestion(index, suggestion, x, y);
+ }
+ final int primaryCode = suggestion.charAt(0);
+ onCodeInput(primaryCode,
+ KeyboardActionListener.SUGGESTION_STRIP_COORDINATE,
+ KeyboardActionListener.SUGGESTION_STRIP_COORDINATE);
+ return;
+ }
if (SPACE_STATE_PHANTOM == mSpaceState && suggestion.length() > 0) {
int firstChar = Character.codePointAt(suggestion, 0);
@@ -1810,7 +1828,6 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
}
mKeyboardSwitcher.updateShiftState();
resetComposingState(true /* alsoResetLastComposedWord */);
- final InputConnection ic = getCurrentInputConnection();
if (ic != null) {
final CompletionInfo completionInfo = mApplicationSpecifiedCompletions[index];
ic.commitCompletion(completionInfo);
@@ -1822,21 +1839,6 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
return;
}
- // If this is a punctuation picked from the suggestion strip, pass it to onCodeInput
- if (suggestion.length() == 1 && isShowingPunctuationList()) {
- // Word separators are suggested before the user inputs something.
- // So, LatinImeLogger logs "" as a user's input.
- LatinImeLogger.logOnManualSuggestion("", suggestion.toString(), index, suggestedWords);
- // Rely on onCodeInput to do the complicated swapping/stripping logic consistently.
- if (ProductionFlag.IS_EXPERIMENTAL) {
- ResearchLogger.latinIME_punctuationSuggestion(index, suggestion, x, y);
- }
- final int primaryCode = suggestion.charAt(0);
- onCodeInput(primaryCode,
- KeyboardActionListener.SUGGESTION_STRIP_COORDINATE,
- KeyboardActionListener.SUGGESTION_STRIP_COORDINATE);
- return;
- }
// We need to log before we commit, because the word composer will store away the user
// typed word.
final String replacedWord = mWordComposer.getTypedWord().toString();
@@ -1889,6 +1891,7 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
mHandler.postUpdateSuggestions();
}
}
+ if (null != ic) ic.endBatchEdit();
}
/**
diff --git a/java/src/com/android/inputmethod/latin/StringUtils.java b/java/src/com/android/inputmethod/latin/StringUtils.java
index 160581cbe..a43b90525 100644
--- a/java/src/com/android/inputmethod/latin/StringUtils.java
+++ b/java/src/com/android/inputmethod/latin/StringUtils.java
@@ -123,6 +123,23 @@ public class StringUtils {
}
/**
+ * Returns true if cs contains any upper case characters.
+ *
+ * @param cs the CharSequence to check
+ * @return {@code true} if cs contains any upper case characters, {@code false} otherwise.
+ */
+ public static boolean hasUpperCase(final CharSequence cs) {
+ final int length = cs.length();
+ for (int i = 0, cp = 0; i < length; i += Character.charCount(cp)) {
+ cp = Character.codePointAt(cs, i);
+ if (Character.isUpperCase(cp)) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ /**
* Remove duplicates from an array of strings.
*
* This method will always keep the first occurrence of all strings at their position
diff --git a/java/src/com/android/inputmethod/latin/Suggest.java b/java/src/com/android/inputmethod/latin/Suggest.java
index 86753e2cc..7cbee4f71 100644
--- a/java/src/com/android/inputmethod/latin/Suggest.java
+++ b/java/src/com/android/inputmethod/latin/Suggest.java
@@ -242,13 +242,8 @@ public class Suggest implements Dictionary.WordCallback {
mBigramSuggestions = new ArrayList<SuggestedWordInfo>(PREF_MAX_BIGRAMS);
- CharSequence lowerPrevWord = prevWordForBigram.toString().toLowerCase();
- if (mMainDict != null && mMainDict.isValidWord(lowerPrevWord)) {
- prevWordForBigram = lowerPrevWord;
- }
- for (final Dictionary dictionary : mBigramDictionaries.values()) {
- dictionary.getBigrams(sEmptyWordComposer, prevWordForBigram, this);
- }
+ getAllBigrams(prevWordForBigram, sEmptyWordComposer);
+
// Nothing entered: return all bigrams for the previous word
int insertCount = Math.min(mBigramSuggestions.size(), mPrefMaxSuggestions);
for (int i = 0; i < insertCount; ++i) {
@@ -290,13 +285,7 @@ public class Suggest implements Dictionary.WordCallback {
mBigramSuggestions = new ArrayList<SuggestedWordInfo>(PREF_MAX_BIGRAMS);
if (!TextUtils.isEmpty(prevWordForBigram)) {
- CharSequence lowerPrevWord = prevWordForBigram.toString().toLowerCase();
- if (mMainDict != null && mMainDict.isValidWord(lowerPrevWord)) {
- prevWordForBigram = lowerPrevWord;
- }
- for (final Dictionary dictionary : mBigramDictionaries.values()) {
- dictionary.getBigrams(wordComposer, prevWordForBigram, this);
- }
+ getAllBigrams(prevWordForBigram, wordComposer);
if (TextUtils.isEmpty(consideredWord)) {
// Nothing entered: return all bigrams for the previous word
int insertCount = Math.min(mBigramSuggestions.size(), mPrefMaxSuggestions);
@@ -409,6 +398,23 @@ public class Suggest implements Dictionary.WordCallback {
false /* isObsoleteSuggestions */);
}
+ /**
+ * Adds all bigram predictions for prevWord. Also checks the lower case version of prevWord if
+ * it contains any upper case characters.
+ */
+ private void getAllBigrams(final CharSequence prevWord, final WordComposer wordComposer) {
+ if (StringUtils.hasUpperCase(prevWord)) {
+ // TODO: Must pay attention to locale when changing case.
+ final CharSequence lowerPrevWord = prevWord.toString().toLowerCase();
+ for (final Dictionary dictionary : mBigramDictionaries.values()) {
+ dictionary.getBigrams(wordComposer, lowerPrevWord, this);
+ }
+ }
+ for (final Dictionary dictionary : mBigramDictionaries.values()) {
+ dictionary.getBigrams(wordComposer, prevWord, this);
+ }
+ }
+
private static ArrayList<SuggestedWordInfo> getSuggestionsInfoListWithDebugInfo(
final String typedWord, final ArrayList<SuggestedWordInfo> suggestions) {
final SuggestedWordInfo typedWordInfo = suggestions.get(0);
diff --git a/java/src/com/android/inputmethod/latin/makedict/BinaryDictInputOutput.java b/java/src/com/android/inputmethod/latin/makedict/BinaryDictInputOutput.java
index 97df98e34..cc98010fb 100644
--- a/java/src/com/android/inputmethod/latin/makedict/BinaryDictInputOutput.java
+++ b/java/src/com/android/inputmethod/latin/makedict/BinaryDictInputOutput.java
@@ -174,6 +174,13 @@ public class BinaryDictInputOutput {
private static final int MAX_TERMINAL_FREQUENCY = 255;
+ // Arbitrary limit to how much passes we consider address size compression should
+ // terminate in. At the time of this writing, our largest dictionary completes
+ // compression in five passes.
+ // If the number of passes exceeds this number, makedict bails with an exception on
+ // suspicion that a bug might be causing an infinite loop.
+ private static final int MAX_PASSES = 24;
+
/**
* A class grouping utility function for our specific character encoding.
*/
@@ -510,14 +517,22 @@ public class BinaryDictInputOutput {
* Each node stores its tentative address. During dictionary address computing, these
* are not final, but they can be used to compute the node size (the node size depends
* on the address of the children because the number of bytes necessary to store an
- * address depends on its numeric value.
+ * address depends on its numeric value. The return value indicates whether the node
+ * contents (as in, any of the addresses stored in the cache fields) have changed with
+ * respect to their previous value.
*
* @param node the node to compute the size of.
* @param dict the dictionary in which the word/attributes are to be found.
+ * @return false if none of the cached addresses inside the node changed, true otherwise.
*/
- private static void computeActualNodeSize(Node node, FusionDictionary dict) {
+ private static boolean computeActualNodeSize(Node node, FusionDictionary dict) {
+ boolean changed = false;
int size = getGroupCountSize(node);
for (CharGroup group : node.mData) {
+ if (group.mCachedAddress != node.mCachedAddress + size) {
+ changed = true;
+ group.mCachedAddress = node.mCachedAddress + size;
+ }
int groupSize = GROUP_FLAGS_SIZE + getGroupCharactersSize(group);
if (group.isTerminal()) groupSize += GROUP_FREQUENCY_SIZE;
if (null != group.mChildren) {
@@ -538,7 +553,11 @@ public class BinaryDictInputOutput {
group.mCachedSize = groupSize;
size += groupSize;
}
- node.mCachedSize = size;
+ if (node.mCachedSize != size) {
+ node.mCachedSize = size;
+ changed = true;
+ }
+ return changed;
}
/**
@@ -594,13 +613,14 @@ public class BinaryDictInputOutput {
changesDone = false;
for (Node n : flatNodes) {
final int oldNodeSize = n.mCachedSize;
- computeActualNodeSize(n, dict);
+ final boolean changed = computeActualNodeSize(n, dict);
final int newNodeSize = n.mCachedSize;
if (oldNodeSize < newNodeSize) throw new RuntimeException("Increased size ?!");
- if (oldNodeSize != newNodeSize) changesDone = true;
+ changesDone |= changed;
}
stackNodes(flatNodes);
++passes;
+ if (passes > MAX_PASSES) throw new RuntimeException("Too many passes - probably a bug");
} while (changesDone);
final Node lastNode = flatNodes.get(flatNodes.size() - 1);
@@ -1122,6 +1142,12 @@ public class BinaryDictInputOutput {
}
}
+ // The word cache here is a stopgap bandaid to help the catastrophic performance
+ // of this method. Since it performs direct, unbuffered random access to the file and
+ // may be called hundreds of thousands of times, the resulting performance is not
+ // reasonable without some kind of cache. Thus:
+ // TODO: perform buffered I/O here and in other places in the code.
+ private static TreeMap<Integer, String> wordCache = new TreeMap<Integer, String>();
/**
* Finds, as a string, the word at the address passed as an argument.
*
@@ -1131,8 +1157,10 @@ public class BinaryDictInputOutput {
* @return the word, as a string.
* @throws IOException if the file can't be read.
*/
- private static String getWordAtAddress(RandomAccessFile source, long headerSize,
+ private static String getWordAtAddress(final RandomAccessFile source, final long headerSize,
int address) throws IOException {
+ final String cachedString = wordCache.get(address);
+ if (null != cachedString) return cachedString;
final long originalPointer = source.getFilePointer();
source.seek(headerSize);
final int count = readCharGroupCount(source);
@@ -1171,6 +1199,7 @@ public class BinaryDictInputOutput {
}
}
source.seek(originalPointer);
+ wordCache.put(address, result);
return result;
}
diff --git a/java/src/com/android/inputmethod/latin/suggestions/SuggestionsView.java b/java/src/com/android/inputmethod/latin/suggestions/SuggestionsView.java
index a17371396..26a9415c4 100644
--- a/java/src/com/android/inputmethod/latin/suggestions/SuggestionsView.java
+++ b/java/src/com/android/inputmethod/latin/suggestions/SuggestionsView.java
@@ -171,7 +171,7 @@ public class SuggestionsView extends RelativeLayout implements OnClickListener,
public boolean mMoreSuggestionsAvailable;
- public final TextView mWordToSaveView;
+ private final TextView mWordToSaveView;
private final TextView mLeftwardsArrowView;
private final TextView mHintToSaveView;
@@ -477,7 +477,7 @@ public class SuggestionsView extends RelativeLayout implements OnClickListener,
}
public void layoutAddToDictionaryHint(CharSequence word, ViewGroup stripView,
- int stripWidth, CharSequence hintText) {
+ int stripWidth, CharSequence hintText, OnClickListener listener) {
final int width = stripWidth - mDividerWidth - mPadding * 2;
final TextView wordView = mWordToSaveView;
@@ -508,6 +508,18 @@ public class SuggestionsView extends RelativeLayout implements OnClickListener,
stripView.addView(hintView);
setLayoutWeight(
hintView, 1.0f - mCenterSuggestionWeight, ViewGroup.LayoutParams.MATCH_PARENT);
+
+ wordView.setOnClickListener(listener);
+ leftArrowView.setOnClickListener(listener);
+ hintView.setOnClickListener(listener);
+ }
+
+ public CharSequence getAddToDictionaryWord() {
+ return (CharSequence)mWordToSaveView.getTag();
+ }
+
+ public boolean isAddToDictionaryShowing(View v) {
+ return v == mWordToSaveView || v == mHintToSaveView || v == mLeftwardsArrowView;
}
private static void setLayoutWeight(View v, float weight, int height) {
@@ -620,7 +632,6 @@ public class SuggestionsView extends RelativeLayout implements OnClickListener,
}
mParams = new SuggestionsViewParams(context, attrs, defStyle, mWords, mDividers, mInfos);
- mParams.mWordToSaveView.setOnClickListener(this);
mMoreSuggestionsContainer = inflater.inflate(R.layout.more_suggestions, null);
mMoreSuggestionsView = (MoreSuggestionsView)mMoreSuggestionsContainer
@@ -676,12 +687,12 @@ public class SuggestionsView extends RelativeLayout implements OnClickListener,
public boolean isShowingAddToDictionaryHint() {
return mSuggestionsStrip.getChildCount() > 0
- && mSuggestionsStrip.getChildAt(0) == mParams.mWordToSaveView;
+ && mParams.isAddToDictionaryShowing(mSuggestionsStrip.getChildAt(0));
}
public void showAddToDictionaryHint(CharSequence word, CharSequence hintText) {
clear();
- mParams.layoutAddToDictionaryHint(word, mSuggestionsStrip, getWidth(), hintText);
+ mParams.layoutAddToDictionaryHint(word, mSuggestionsStrip, getWidth(), hintText, this);
}
public boolean dismissAddToDictionaryHint() {
@@ -851,8 +862,8 @@ public class SuggestionsView extends RelativeLayout implements OnClickListener,
@Override
public void onClick(View view) {
- if (view == mParams.mWordToSaveView) {
- addToDictionary((CharSequence)view.getTag());
+ if (mParams.isAddToDictionaryShowing(view)) {
+ addToDictionary(mParams.getAddToDictionaryWord());
clear();
return;
}