diff options
Diffstat (limited to 'java/src/com/android/inputmethod/latin')
12 files changed, 187 insertions, 23 deletions
diff --git a/java/src/com/android/inputmethod/latin/BinaryDictionaryFileDumper.java b/java/src/com/android/inputmethod/latin/BinaryDictionaryFileDumper.java index 562e1d0b7..42f713697 100644 --- a/java/src/com/android/inputmethod/latin/BinaryDictionaryFileDumper.java +++ b/java/src/com/android/inputmethod/latin/BinaryDictionaryFileDumper.java @@ -422,7 +422,7 @@ public final class BinaryDictionaryFileDumper { private static void reinitializeClientRecordInDictionaryContentProvider(final Context context, final ContentProviderClient client, final String clientId) throws RemoteException { - final String metadataFileUri = context.getString(R.string.dictionary_pack_metadata_uri); + final String metadataFileUri = MetadataFileUriGetter.getMetadataUri(context); if (TextUtils.isEmpty(metadataFileUri)) return; // Tell the content provider to reset all information about this client id final Uri metadataContentUri = getProviderUriBuilder(clientId) diff --git a/java/src/com/android/inputmethod/latin/CompletionInfoUtils.java b/java/src/com/android/inputmethod/latin/CompletionInfoUtils.java new file mode 100644 index 000000000..792a446c9 --- /dev/null +++ b/java/src/com/android/inputmethod/latin/CompletionInfoUtils.java @@ -0,0 +1,43 @@ +/* + * Copyright (C) 2013 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.text.TextUtils; +import android.view.inputmethod.CompletionInfo; + +import java.util.Arrays; + +/** + * Utilities to do various stuff with CompletionInfo. + */ +public class CompletionInfoUtils { + private CompletionInfoUtils() { + // This utility class is not publicly instantiable. + } + + public static CompletionInfo[] removeNulls(final CompletionInfo[] src) { + int j = 0; + final CompletionInfo[] dst = new CompletionInfo[src.length]; + for (int i = 0; i < src.length; ++i) { + if (null != src[i] && !TextUtils.isEmpty(src[i].getText())) { + dst[j] = src[i]; + ++j; + } + } + return Arrays.copyOfRange(dst, 0, j); + } +} diff --git a/java/src/com/android/inputmethod/latin/ContactsBinaryDictionary.java b/java/src/com/android/inputmethod/latin/ContactsBinaryDictionary.java index 8b5a76a17..22d189987 100644 --- a/java/src/com/android/inputmethod/latin/ContactsBinaryDictionary.java +++ b/java/src/com/android/inputmethod/latin/ContactsBinaryDictionary.java @@ -173,7 +173,8 @@ public class ContactsBinaryDictionary extends ExpandableBinaryDictionary { // capitalization of i. final int wordLen = StringUtils.codePointCount(word); if (wordLen < MAX_WORD_LENGTH && wordLen > 1) { - super.addWord(word, null /* shortcut */, FREQUENCY_FOR_CONTACTS); + super.addWord(word, null /* shortcut */, FREQUENCY_FOR_CONTACTS, + false /* isNotAWord */); if (!TextUtils.isEmpty(prevWord)) { if (mUseFirstLastBigrams) { super.setBigram(prevWord, word, FREQUENCY_FOR_CONTACTS_BIGRAM); diff --git a/java/src/com/android/inputmethod/latin/Dictionary.java b/java/src/com/android/inputmethod/latin/Dictionary.java index ff3d83fad..9691fa231 100644 --- a/java/src/com/android/inputmethod/latin/Dictionary.java +++ b/java/src/com/android/inputmethod/latin/Dictionary.java @@ -37,6 +37,8 @@ public abstract class Dictionary { public static final String TYPE_USER = "user"; // User history dictionary internal to LatinIME. public static final String TYPE_USER_HISTORY = "history"; + // Spawned by resuming suggestions. Comes from a span that was in the TextView. + public static final String TYPE_RESUMED = "resumed"; protected final String mDictType; public Dictionary(final String dictType) { diff --git a/java/src/com/android/inputmethod/latin/ExpandableBinaryDictionary.java b/java/src/com/android/inputmethod/latin/ExpandableBinaryDictionary.java index 97dc6a8ac..4b1975a00 100644 --- a/java/src/com/android/inputmethod/latin/ExpandableBinaryDictionary.java +++ b/java/src/com/android/inputmethod/latin/ExpandableBinaryDictionary.java @@ -176,14 +176,15 @@ abstract public class ExpandableBinaryDictionary extends Dictionary { */ // TODO: Create "cache dictionary" to cache fresh words for frequently updated dictionaries, // considering performance regression. - protected void addWord(final String word, final String shortcutTarget, final int frequency) { + protected void addWord(final String word, final String shortcutTarget, final int frequency, + final boolean isNotAWord) { if (shortcutTarget == null) { - mFusionDictionary.add(word, frequency, null, false /* isNotAWord */); + mFusionDictionary.add(word, frequency, null, isNotAWord); } else { // TODO: Do this in the subclass, with this class taking an arraylist. final ArrayList<WeightedString> shortcutTargets = CollectionUtils.newArrayList(); shortcutTargets.add(new WeightedString(shortcutTarget, frequency)); - mFusionDictionary.add(word, frequency, shortcutTargets, false /* isNotAWord */); + mFusionDictionary.add(word, frequency, shortcutTargets, isNotAWord); } } diff --git a/java/src/com/android/inputmethod/latin/LatinIME.java b/java/src/com/android/inputmethod/latin/LatinIME.java index f08e10763..094ccd77f 100644 --- a/java/src/com/android/inputmethod/latin/LatinIME.java +++ b/java/src/com/android/inputmethod/latin/LatinIME.java @@ -44,7 +44,9 @@ import android.os.Message; import android.os.SystemClock; import android.preference.PreferenceManager; import android.text.InputType; +import android.text.SpannableString; import android.text.TextUtils; +import android.text.style.SuggestionSpan; import android.util.Log; import android.util.PrintWriterPrinter; import android.util.Printer; @@ -72,6 +74,7 @@ import com.android.inputmethod.keyboard.KeyboardActionListener; import com.android.inputmethod.keyboard.KeyboardId; import com.android.inputmethod.keyboard.KeyboardSwitcher; import com.android.inputmethod.keyboard.MainKeyboardView; +import com.android.inputmethod.latin.RichInputConnection.Range; import com.android.inputmethod.latin.SuggestedWords.SuggestedWordInfo; import com.android.inputmethod.latin.Utils.Stats; import com.android.inputmethod.latin.define.ProductionFlag; @@ -197,6 +200,7 @@ public final class LatinIME extends InputMethodService implements KeyboardAction private static final int MSG_PENDING_IMS_CALLBACK = 1; private static final int MSG_UPDATE_SUGGESTION_STRIP = 2; private static final int MSG_SHOW_GESTURE_PREVIEW_AND_SUGGESTION_STRIP = 3; + private static final int MSG_RESUME_SUGGESTIONS = 4; private static final int ARG1_DISMISS_GESTURE_FLOATING_PREVIEW_TEXT = 1; @@ -234,6 +238,9 @@ public final class LatinIME extends InputMethodService implements KeyboardAction latinIme.showGesturePreviewAndSuggestionStrip((SuggestedWords)msg.obj, msg.arg1 == ARG1_DISMISS_GESTURE_FLOATING_PREVIEW_TEXT); break; + case MSG_RESUME_SUGGESTIONS: + latinIme.restartSuggestionsOnWordTouchedByCursor(); + break; } } @@ -241,6 +248,10 @@ public final class LatinIME extends InputMethodService implements KeyboardAction sendMessageDelayed(obtainMessage(MSG_UPDATE_SUGGESTION_STRIP), mDelayUpdateSuggestions); } + public void postResumeSuggestions() { + sendMessageDelayed(obtainMessage(MSG_RESUME_SUGGESTIONS), mDelayUpdateSuggestions); + } + public void cancelUpdateSuggestionStrip() { removeMessages(MSG_UPDATE_SUGGESTION_STRIP); } @@ -910,13 +921,12 @@ public final class LatinIME extends InputMethodService implements KeyboardAction resetEntireInputState(newSelStart); } + // We moved the cursor. If we are touching a word, we need to resume suggestion. + mHandler.postResumeSuggestions(); + mKeyboardSwitcher.updateShiftState(); } mExpectingUpdateSelection = false; - // TODO: Decide to call restartSuggestionsOnWordBeforeCursorIfAtEndOfWord() or not - // here. It would probably be too expensive to call directly here but we may want to post a - // message to delay it. The point would be to unify behavior between backspace to the - // end of a word and manually put the pointer at the end of the word. // Make a note of the cursor position mLastSelectionStart = newSelStart; @@ -983,7 +993,8 @@ public final class LatinIME extends InputMethodService implements KeyboardAction } } if (!mSettings.getCurrent().isApplicationSpecifiedCompletionsOn()) return; - mApplicationSpecifiedCompletions = applicationSpecifiedCompletions; + mApplicationSpecifiedCompletions = + CompletionInfoUtils.removeNulls(applicationSpecifiedCompletions); if (applicationSpecifiedCompletions == null) { clearSuggestionStrip(); if (ProductionFlag.USES_DEVELOPMENT_ONLY_DIAGNOSTICS) { @@ -1723,6 +1734,9 @@ public final class LatinIME extends InputMethodService implements KeyboardAction // during key repeat. mHandler.postUpdateShiftState(); + if (mWordComposer.isComposingWord() && !mWordComposer.isCursorAtEndOfComposingWord()) { + resetEntireInputState(mLastSelectionStart); + } if (mWordComposer.isComposingWord()) { final int length = mWordComposer.size(); if (length > 0) { @@ -1854,6 +1868,10 @@ public final class LatinIME extends InputMethodService implements KeyboardAction promotePhantomSpace(); } + if (mWordComposer.isComposingWord() && !mWordComposer.isCursorAtEndOfComposingWord()) { + resetEntireInputState(mLastSelectionStart); + isComposingWord = false; + } // NOTE: isCursorTouchingWord() is a blocking IPC call, so it often takes several // dozen milliseconds. Avoid calling it as much as possible, since we are on the UI // thread here. @@ -2327,6 +2345,48 @@ public final class LatinIME extends InputMethodService implements KeyboardAction } /** + * Check if the cursor is touching a word. If so, restart suggestions on this word, else + * do nothing. + */ + private void restartSuggestionsOnWordTouchedByCursor() { + // If the cursor is not touching a word, or if there is a selection, return right away. + if (mLastSelectionStart != mLastSelectionEnd) return; + if (!mConnection.isCursorTouchingWord(mSettings.getCurrent())) return; + final Range range = mConnection.getWordRangeAtCursor(mSettings.getWordSeparators(), + 0 /* additionalPrecedingWordsCount */); + final ArrayList<SuggestedWordInfo> suggestions = CollectionUtils.newArrayList(); + if (range.mWord instanceof SpannableString) { + final SpannableString spannableString = (SpannableString)range.mWord; + final String typedWord = spannableString.toString(); + int i = 0; + for (Object object : spannableString.getSpans(0, spannableString.length(), + SuggestionSpan.class)) { + SuggestionSpan span = (SuggestionSpan)object; + for (String s : span.getSuggestions()) { + ++i; + if (!TextUtils.equals(s, typedWord)) { + suggestions.add(new SuggestedWordInfo(s, + SuggestionStripView.MAX_SUGGESTIONS - i, + SuggestedWordInfo.KIND_RESUMED, Dictionary.TYPE_RESUMED)); + } + } + } + } + mWordComposer.setComposingWord(range.mWord, mKeyboardSwitcher.getKeyboard()); + mWordComposer.setCursorPositionWithinWord(range.mCharsBefore); + mConnection.setComposingRegion(mLastSelectionStart - range.mCharsBefore, + mLastSelectionEnd + range.mCharsAfter); + if (suggestions.isEmpty()) { + suggestions.add(new SuggestedWordInfo(range.mWord.toString(), 1, + SuggestedWordInfo.KIND_TYPED, Dictionary.TYPE_RESUMED)); + } + showSuggestionStrip(new SuggestedWords(suggestions, + true /* typedWordValid */, false /* willAutoCorrect */, + false /* isPunctuationSuggestions */, false /* isObsoleteSuggestions */, + false /* isPrediction */), range.mWord.toString()); + } + + /** * Check if the cursor is actually at the end of a word. If so, restart suggestions on this * word, else do nothing. */ @@ -2334,17 +2394,18 @@ public final class LatinIME extends InputMethodService implements KeyboardAction final CharSequence word = mConnection.getWordBeforeCursorIfAtEndOfWord(mSettings.getCurrent()); if (null != word) { - restartSuggestionsOnWordBeforeCursor(word); + final String wordString = word.toString(); + restartSuggestionsOnWordBeforeCursor(wordString); // TODO: Handle the case where the user manually moves the cursor and then backs up over // a separator. In that case, the current log unit should not be uncommitted. if (ProductionFlag.USES_DEVELOPMENT_ONLY_DIAGNOSTICS) { - ResearchLogger.getInstance().uncommitCurrentLogUnit(word.toString(), + ResearchLogger.getInstance().uncommitCurrentLogUnit(wordString, true /* dumpCurrentLogUnit */); } } } - private void restartSuggestionsOnWordBeforeCursor(final CharSequence word) { + private void restartSuggestionsOnWordBeforeCursor(final String word) { mWordComposer.setComposingWord(word, mKeyboardSwitcher.getKeyboard()); final int length = word.length(); mConnection.deleteSurroundingText(length, 0); diff --git a/java/src/com/android/inputmethod/latin/MetadataFileUriGetter.java b/java/src/com/android/inputmethod/latin/MetadataFileUriGetter.java new file mode 100644 index 000000000..e6dc6db8f --- /dev/null +++ b/java/src/com/android/inputmethod/latin/MetadataFileUriGetter.java @@ -0,0 +1,28 @@ +/* + * Copyright (C) 2013 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.content.Context; + +/** + * Helper class to get the metadata URI. + */ +public class MetadataFileUriGetter { + public static String getMetadataUri(Context context) { + return context.getString(R.string.dictionary_pack_metadata_uri); + } +} diff --git a/java/src/com/android/inputmethod/latin/RichInputConnection.java b/java/src/com/android/inputmethod/latin/RichInputConnection.java index 16744d1f0..b74ea593d 100644 --- a/java/src/com/android/inputmethod/latin/RichInputConnection.java +++ b/java/src/com/android/inputmethod/latin/RichInputConnection.java @@ -17,7 +17,9 @@ package com.android.inputmethod.latin; import android.inputmethodservice.InputMethodService; +import android.text.SpannableString; import android.text.TextUtils; +import android.text.style.SuggestionSpan; import android.util.Log; import android.view.KeyEvent; import android.view.inputmethod.CompletionInfo; @@ -392,7 +394,9 @@ public final class RichInputConnection { public void commitCompletion(final CompletionInfo completionInfo) { if (DEBUG_BATCH_NESTING) checkBatchEdit(); if (DEBUG_PREVIOUS_TEXT) checkConsistencyForDebug(); - final CharSequence text = completionInfo.getText(); + CharSequence text = completionInfo.getText(); + // text should never be null, but just in case, it's better to insert nothing than to crash + if (null == text) text = ""; mCommittedTextBeforeComposingText.append(text); mCurrentCursorPosition += text.length() - mComposingText.length(); mComposingText.setLength(0); @@ -442,9 +446,9 @@ public final class RichInputConnection { public final int mCharsAfter; /** The actual characters that make up a word */ - public final String mWord; + public final CharSequence mWord; - public Range(int charsBefore, int charsAfter, String word) { + public Range(int charsBefore, int charsAfter, CharSequence word) { if (charsBefore < 0 || charsAfter < 0) { throw new IndexOutOfBoundsException(); } @@ -498,7 +502,7 @@ public final class RichInputConnection { * separator. For example, if the field contains "he|llo world", where | * represents the cursor, then "hello " will be returned. */ - public String getWordAtCursor(String separators) { + public CharSequence getWordAtCursor(String separators) { // getWordRangeAtCursor returns null if the connection is null Range r = getWordRangeAtCursor(separators, 0); return (r == null) ? null : r.mWord; @@ -517,8 +521,10 @@ public final class RichInputConnection { if (mIC == null || sep == null) { return null; } - final CharSequence before = mIC.getTextBeforeCursor(1000, 0); - final CharSequence after = mIC.getTextAfterCursor(1000, 0); + final CharSequence before = mIC.getTextBeforeCursor(1000, + InputConnection.GET_TEXT_WITH_STYLES); + final CharSequence after = mIC.getTextAfterCursor(1000, + InputConnection.GET_TEXT_WITH_STYLES); if (before == null || after == null) { return null; } @@ -560,8 +566,9 @@ public final class RichInputConnection { } } - final String word = before.toString().substring(startIndexInBefore, before.length()) - + after.toString().substring(0, endIndexInAfter); + final SpannableString word = new SpannableString(TextUtils.concat( + before.subSequence(startIndexInBefore, before.length()), + after.subSequence(0, endIndexInAfter))); return new Range(before.length() - startIndexInBefore, endIndexInAfter, word); } diff --git a/java/src/com/android/inputmethod/latin/Settings.java b/java/src/com/android/inputmethod/latin/Settings.java index 8fbe843cf..318d2b23f 100644 --- a/java/src/com/android/inputmethod/latin/Settings.java +++ b/java/src/com/android/inputmethod/latin/Settings.java @@ -134,6 +134,10 @@ public final class Settings implements SharedPreferences.OnSharedPreferenceChang return mSettingsValues.mIsInternal; } + public String getWordSeparators() { + return mSettingsValues.mWordSeparators; + } + // Accessed from the settings interface, hence public public static boolean readKeypressSoundEnabled(final SharedPreferences prefs, final Resources res) { diff --git a/java/src/com/android/inputmethod/latin/SuggestedWords.java b/java/src/com/android/inputmethod/latin/SuggestedWords.java index 3d6fe2d22..158cc1155 100644 --- a/java/src/com/android/inputmethod/latin/SuggestedWords.java +++ b/java/src/com/android/inputmethod/latin/SuggestedWords.java @@ -131,6 +131,7 @@ public final class SuggestedWords { public static final int KIND_APP_DEFINED = 6; // Suggested by the application public static final int KIND_SHORTCUT = 7; // A shortcut public static final int KIND_PREDICTION = 8; // A prediction (== a suggestion with no input) + public static final int KIND_RESUMED = 9; // A resumed suggestion (comes from a span) public final String mWord; public final int mScore; public final int mKind; // one of the KIND_* constants above diff --git a/java/src/com/android/inputmethod/latin/UserBinaryDictionary.java b/java/src/com/android/inputmethod/latin/UserBinaryDictionary.java index 8d2e7623f..90f92972a 100644 --- a/java/src/com/android/inputmethod/latin/UserBinaryDictionary.java +++ b/java/src/com/android/inputmethod/latin/UserBinaryDictionary.java @@ -252,10 +252,10 @@ public class UserBinaryDictionary extends ExpandableBinaryDictionary { final int adjustedFrequency = scaleFrequencyFromDefaultToLatinIme(frequency); // Safeguard against adding really long words. if (word.length() < MAX_WORD_LENGTH) { - super.addWord(word, null, adjustedFrequency); + super.addWord(word, null, adjustedFrequency, false /* isNotAWord */); } if (null != shortcut && shortcut.length() < MAX_WORD_LENGTH) { - super.addWord(shortcut, word, adjustedFrequency); + super.addWord(shortcut, word, adjustedFrequency, true /* isNotAWord */); } cursor.moveToNext(); } diff --git a/java/src/com/android/inputmethod/latin/WordComposer.java b/java/src/com/android/inputmethod/latin/WordComposer.java index f7cb4346a..1af12428d 100644 --- a/java/src/com/android/inputmethod/latin/WordComposer.java +++ b/java/src/com/android/inputmethod/latin/WordComposer.java @@ -49,6 +49,7 @@ public final class WordComposer { private int mCapitalizedMode; private int mTrailingSingleQuotesCount; private int mCodePointSize; + private int mCursorPositionWithinWord; /** * Whether the user chose to capitalize the first char of the word. @@ -62,6 +63,7 @@ public final class WordComposer { mTrailingSingleQuotesCount = 0; mIsResumed = false; mIsBatchMode = false; + mCursorPositionWithinWord = 0; refreshSize(); } @@ -76,6 +78,7 @@ public final class WordComposer { mTrailingSingleQuotesCount = source.mTrailingSingleQuotesCount; mIsResumed = source.mIsResumed; mIsBatchMode = source.mIsBatchMode; + mCursorPositionWithinWord = source.mCursorPositionWithinWord; refreshSize(); } @@ -91,6 +94,7 @@ public final class WordComposer { mTrailingSingleQuotesCount = 0; mIsResumed = false; mIsBatchMode = false; + mCursorPositionWithinWord = 0; refreshSize(); } @@ -135,6 +139,7 @@ public final class WordComposer { final int newIndex = size(); mTypedWord.appendCodePoint(primaryCode); refreshSize(); + mCursorPositionWithinWord = mCodePointSize; if (newIndex < MAX_WORD_LENGTH) { mPrimaryKeyCodes[newIndex] = primaryCode >= Constants.CODE_SPACE ? Character.toLowerCase(primaryCode) : primaryCode; @@ -158,6 +163,14 @@ public final class WordComposer { mAutoCorrection = null; } + public void setCursorPositionWithinWord(final int posWithinWord) { + mCursorPositionWithinWord = posWithinWord; + } + + public boolean isCursorAtEndOfComposingWord() { + return mCursorPositionWithinWord == mCodePointSize; + } + public void setBatchInputPointers(final InputPointers batchPointers) { mInputPointers.set(batchPointers); mIsBatchMode = true; @@ -242,6 +255,7 @@ public final class WordComposer { ++mTrailingSingleQuotesCount; } } + mCursorPositionWithinWord = mCodePointSize; mAutoCorrection = null; } @@ -368,6 +382,7 @@ public final class WordComposer { mCapitalizedMode = CAPS_MODE_OFF; refreshSize(); mAutoCorrection = null; + mCursorPositionWithinWord = 0; mIsResumed = false; return lastComposedWord; } @@ -380,6 +395,7 @@ public final class WordComposer { refreshSize(); mCapitalizedMode = lastComposedWord.mCapitalizedMode; mAutoCorrection = null; // This will be filled by the next call to updateSuggestion. + mCursorPositionWithinWord = mCodePointSize; mIsResumed = true; } |