diff options
Diffstat (limited to 'java/src')
15 files changed, 295 insertions, 207 deletions
diff --git a/java/src/com/android/inputmethod/accessibility/AccessibilityUtils.java b/java/src/com/android/inputmethod/accessibility/AccessibilityUtils.java index 1eee1df87..0a700bda4 100644 --- a/java/src/com/android/inputmethod/accessibility/AccessibilityUtils.java +++ b/java/src/com/android/inputmethod/accessibility/AccessibilityUtils.java @@ -32,7 +32,6 @@ import android.view.accessibility.AccessibilityEvent; import android.view.accessibility.AccessibilityManager; import android.view.inputmethod.EditorInfo; -import com.android.inputmethod.compat.AudioManagerCompatWrapper; import com.android.inputmethod.compat.SettingsSecureCompatUtils; import com.android.inputmethod.latin.InputTypeUtils; import com.android.inputmethod.latin.R; @@ -40,14 +39,14 @@ import com.android.inputmethod.latin.R; public final class AccessibilityUtils { private static final String TAG = AccessibilityUtils.class.getSimpleName(); private static final String CLASS = AccessibilityUtils.class.getClass().getName(); - private static final String PACKAGE = AccessibilityUtils.class.getClass().getPackage() - .getName(); + private static final String PACKAGE = + AccessibilityUtils.class.getClass().getPackage().getName(); private static final AccessibilityUtils sInstance = new AccessibilityUtils(); private Context mContext; private AccessibilityManager mAccessibilityManager; - private AudioManagerCompatWrapper mAudioManager; + private AudioManager mAudioManager; /* * Setting this constant to {@code false} will disable all keyboard @@ -57,8 +56,7 @@ public final class AccessibilityUtils { private static final boolean ENABLE_ACCESSIBILITY = true; public static void init(InputMethodService inputMethod) { - if (!ENABLE_ACCESSIBILITY) - return; + if (!ENABLE_ACCESSIBILITY) return; // These only need to be initialized if the kill switch is off. sInstance.initInternal(inputMethod); @@ -76,12 +74,9 @@ public final class AccessibilityUtils { private void initInternal(Context context) { mContext = context; - mAccessibilityManager = (AccessibilityManager) context - .getSystemService(Context.ACCESSIBILITY_SERVICE); - - final AudioManager audioManager = (AudioManager) context - .getSystemService(Context.AUDIO_SERVICE); - mAudioManager = new AudioManagerCompatWrapper(audioManager); + mAccessibilityManager = + (AccessibilityManager) context.getSystemService(Context.ACCESSIBILITY_SERVICE); + mAudioManager = (AudioManager) context.getSystemService(Context.AUDIO_SERVICE); } /** @@ -120,20 +115,19 @@ public final class AccessibilityUtils { * @return {@code true} if the device should obscure password characters. */ public boolean shouldObscureInput(EditorInfo editorInfo) { - if (editorInfo == null) - return false; + if (editorInfo == null) return false; // The user can optionally force speaking passwords. if (SettingsSecureCompatUtils.ACCESSIBILITY_SPEAK_PASSWORD != null) { final boolean speakPassword = Settings.Secure.getInt(mContext.getContentResolver(), SettingsSecureCompatUtils.ACCESSIBILITY_SPEAK_PASSWORD, 0) != 0; - if (speakPassword) - return false; + if (speakPassword) return false; } // Always speak if the user is listening through headphones. - if (mAudioManager.isWiredHeadsetOn() || mAudioManager.isBluetoothA2dpOn()) + if (mAudioManager.isWiredHeadsetOn() || mAudioManager.isBluetoothA2dpOn()) { return false; + } // Don't speak if the IME is connected to a password field. return InputTypeUtils.isPasswordInputType(editorInfo.inputType); diff --git a/java/src/com/android/inputmethod/compat/AudioManagerCompatWrapper.java b/java/src/com/android/inputmethod/compat/AudioManagerCompatWrapper.java deleted file mode 100644 index 40eed91f2..000000000 --- a/java/src/com/android/inputmethod/compat/AudioManagerCompatWrapper.java +++ /dev/null @@ -1,54 +0,0 @@ -/* - * Copyright (C) 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.compat; - -import android.media.AudioManager; - -import java.lang.reflect.Method; - -public final class AudioManagerCompatWrapper { - private static final Method METHOD_isWiredHeadsetOn = CompatUtils.getMethod( - AudioManager.class, "isWiredHeadsetOn"); - private static final Method METHOD_isBluetoothA2dpOn = CompatUtils.getMethod( - AudioManager.class, "isBluetoothA2dpOn"); - - private final AudioManager mManager; - - public AudioManagerCompatWrapper(AudioManager manager) { - mManager = manager; - } - - /** - * Checks whether audio routing to the wired headset is on or off. - * - * @return true if audio is being routed to/from wired headset; - * false if otherwise - */ - public boolean isWiredHeadsetOn() { - return (Boolean) CompatUtils.invoke(mManager, false, METHOD_isWiredHeadsetOn); - } - - /** - * Checks whether A2DP audio routing to the Bluetooth headset is on or off. - * - * @return true if A2DP audio is being routed to/from Bluetooth headset; - * false if otherwise - */ - public boolean isBluetoothA2dpOn() { - return (Boolean) CompatUtils.invoke(mManager, false, METHOD_isBluetoothA2dpOn); - } -} diff --git a/java/src/com/android/inputmethod/compat/SuggestionSpanUtils.java b/java/src/com/android/inputmethod/compat/SuggestionSpanUtils.java index c1609ea28..0e3634d52 100644 --- a/java/src/com/android/inputmethod/compat/SuggestionSpanUtils.java +++ b/java/src/com/android/inputmethod/compat/SuggestionSpanUtils.java @@ -21,58 +21,28 @@ import android.text.Spannable; import android.text.SpannableString; import android.text.Spanned; import android.text.TextUtils; -import android.util.Log; +import android.text.style.SuggestionSpan; import com.android.inputmethod.latin.CollectionUtils; import com.android.inputmethod.latin.LatinImeLogger; import com.android.inputmethod.latin.SuggestedWords; import com.android.inputmethod.latin.SuggestionSpanPickedNotificationReceiver; -import java.lang.reflect.Constructor; import java.lang.reflect.Field; import java.util.ArrayList; -import java.util.Locale; public final class SuggestionSpanUtils { private static final String TAG = SuggestionSpanUtils.class.getSimpleName(); - // TODO: Use reflection to get field values - public static final String ACTION_SUGGESTION_PICKED = - "android.text.style.SUGGESTION_PICKED"; - public static final String SUGGESTION_SPAN_PICKED_AFTER = "after"; - public static final String SUGGESTION_SPAN_PICKED_BEFORE = "before"; - public static final String SUGGESTION_SPAN_PICKED_HASHCODE = "hashcode"; - public static final boolean SUGGESTION_SPAN_IS_SUPPORTED; - private static final Class<?> CLASS_SuggestionSpan = CompatUtils - .getClass("android.text.style.SuggestionSpan"); - private static final Class<?>[] INPUT_TYPE_SuggestionSpan = new Class<?>[] { - Context.class, Locale.class, String[].class, int.class, Class.class }; - private static final Constructor<?> CONSTRUCTOR_SuggestionSpan = CompatUtils - .getConstructor(CLASS_SuggestionSpan, INPUT_TYPE_SuggestionSpan); - public static final Field FIELD_FLAG_EASY_CORRECT = - CompatUtils.getField(CLASS_SuggestionSpan, "FLAG_EASY_CORRECT"); - public static final Field FIELD_FLAG_MISSPELLED = - CompatUtils.getField(CLASS_SuggestionSpan, "FLAG_MISSPELLED"); + // Note that SuggestionSpan.FLAG_AUTO_CORRECTION was added in API level 15. public static final Field FIELD_FLAG_AUTO_CORRECTION = - CompatUtils.getField(CLASS_SuggestionSpan, "FLAG_AUTO_CORRECTION"); - public static final Field FIELD_SUGGESTIONS_MAX_SIZE - = CompatUtils.getField(CLASS_SuggestionSpan, "SUGGESTIONS_MAX_SIZE"); - public static final Integer OBJ_FLAG_EASY_CORRECT = (Integer) CompatUtils - .getFieldValue(null, null, FIELD_FLAG_EASY_CORRECT); - public static final Integer OBJ_FLAG_MISSPELLED = (Integer) CompatUtils - .getFieldValue(null, null, FIELD_FLAG_MISSPELLED); - public static final Integer OBJ_FLAG_AUTO_CORRECTION = (Integer) CompatUtils - .getFieldValue(null, null, FIELD_FLAG_AUTO_CORRECTION); - public static final Integer OBJ_SUGGESTIONS_MAX_SIZE = (Integer) CompatUtils - .getFieldValue(null, null, FIELD_SUGGESTIONS_MAX_SIZE); + CompatUtils.getField(SuggestionSpan.class, "FLAG_AUTO_CORRECTION"); + public static final Integer OBJ_FLAG_AUTO_CORRECTION = + (Integer) CompatUtils.getFieldValue(null, null, FIELD_FLAG_AUTO_CORRECTION); static { - SUGGESTION_SPAN_IS_SUPPORTED = - CLASS_SuggestionSpan != null && CONSTRUCTOR_SuggestionSpan != null; if (LatinImeLogger.sDBG) { - if (SUGGESTION_SPAN_IS_SUPPORTED - && (OBJ_FLAG_AUTO_CORRECTION == null || OBJ_SUGGESTIONS_MAX_SIZE == null - || OBJ_FLAG_MISSPELLED == null || OBJ_FLAG_EASY_CORRECT == null)) { + if (OBJ_FLAG_AUTO_CORRECTION == null) { throw new RuntimeException("Field is accidentially null."); } } @@ -84,21 +54,13 @@ public final class SuggestionSpanUtils { public static CharSequence getTextWithAutoCorrectionIndicatorUnderline( final Context context, final String text) { - if (TextUtils.isEmpty(text) || CONSTRUCTOR_SuggestionSpan == null - || OBJ_FLAG_AUTO_CORRECTION == null || OBJ_SUGGESTIONS_MAX_SIZE == null - || OBJ_FLAG_MISSPELLED == null || OBJ_FLAG_EASY_CORRECT == null) { + if (TextUtils.isEmpty(text) || OBJ_FLAG_AUTO_CORRECTION == null) { return text; } final Spannable spannable = new SpannableString(text); - final Object[] args = - { context, null, new String[] {}, (int)OBJ_FLAG_AUTO_CORRECTION, - (Class<?>) SuggestionSpanPickedNotificationReceiver.class }; - final Object ss = CompatUtils.newInstance(CONSTRUCTOR_SuggestionSpan, args); - if (ss == null) { - Log.w(TAG, "Suggestion span was not created."); - return text; - } - spannable.setSpan(ss, 0, text.length(), + final SuggestionSpan suggestionSpan = new SuggestionSpan(context, null, new String[] {}, + (int)OBJ_FLAG_AUTO_CORRECTION, SuggestionSpanPickedNotificationReceiver.class); + spannable.setSpan(suggestionSpan, 0, text.length(), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE | Spanned.SPAN_COMPOSING); return spannable; } @@ -106,18 +68,15 @@ public final class SuggestionSpanUtils { public static CharSequence getTextWithSuggestionSpan(final Context context, final String pickedWord, final SuggestedWords suggestedWords, final boolean dictionaryAvailable) { - if (!dictionaryAvailable || TextUtils.isEmpty(pickedWord) - || CONSTRUCTOR_SuggestionSpan == null - || suggestedWords.isEmpty() || suggestedWords.mIsPrediction - || suggestedWords.mIsPunctuationSuggestions - || OBJ_SUGGESTIONS_MAX_SIZE == null) { + if (!dictionaryAvailable || TextUtils.isEmpty(pickedWord) || suggestedWords.isEmpty() + || suggestedWords.mIsPrediction || suggestedWords.mIsPunctuationSuggestions) { return pickedWord; } final Spannable spannable = new SpannableString(pickedWord); final ArrayList<String> suggestionsList = CollectionUtils.newArrayList(); for (int i = 0; i < suggestedWords.size(); ++i) { - if (suggestionsList.size() >= OBJ_SUGGESTIONS_MAX_SIZE) { + if (suggestionsList.size() >= SuggestionSpan.SUGGESTIONS_MAX_SIZE) { break; } final String word = suggestedWords.getWord(i); @@ -128,14 +87,10 @@ public final class SuggestionSpanUtils { // TODO: We should avoid adding suggestion span candidates that came from the bigram // prediction. - final Object[] args = - { context, null, suggestionsList.toArray(new String[suggestionsList.size()]), 0, - (Class<?>) SuggestionSpanPickedNotificationReceiver.class }; - final Object ss = CompatUtils.newInstance(CONSTRUCTOR_SuggestionSpan, args); - if (ss == null) { - return pickedWord; - } - spannable.setSpan(ss, 0, pickedWord.length(), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); + final SuggestionSpan suggestionSpan = new SuggestionSpan(context, null, + suggestionsList.toArray(new String[suggestionsList.size()]), 0, + SuggestionSpanPickedNotificationReceiver.class); + spannable.setSpan(suggestionSpan, 0, pickedWord.length(), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); return spannable; } } diff --git a/java/src/com/android/inputmethod/latin/BinaryDictionary.java b/java/src/com/android/inputmethod/latin/BinaryDictionary.java index 6048a3308..448d25c73 100644 --- a/java/src/com/android/inputmethod/latin/BinaryDictionary.java +++ b/java/src/com/android/inputmethod/latin/BinaryDictionary.java @@ -41,9 +41,9 @@ public final class BinaryDictionary extends Dictionary { * It is necessary to keep it at this value because some languages e.g. German have * really long words. */ - public static final int MAX_WORD_LENGTH = Constants.Dictionary.MAX_WORD_LENGTH; - public static final int MAX_WORDS = 18; - public static final int MAX_SPACES = 16; + private static final int MAX_WORD_LENGTH = Constants.Dictionary.MAX_WORD_LENGTH; + private static final int MAX_WORDS = 18; + private static final int MAX_SPACES = 16; private static final int MAX_PREDICTIONS = 60; private static final int MAX_RESULTS = Math.max(MAX_PREDICTIONS, MAX_WORDS); diff --git a/java/src/com/android/inputmethod/latin/ExpandableBinaryDictionary.java b/java/src/com/android/inputmethod/latin/ExpandableBinaryDictionary.java index 159867ade..47adaa8ed 100644 --- a/java/src/com/android/inputmethod/latin/ExpandableBinaryDictionary.java +++ b/java/src/com/android/inputmethod/latin/ExpandableBinaryDictionary.java @@ -51,10 +51,9 @@ abstract public class ExpandableBinaryDictionary extends Dictionary { private static boolean DEBUG = false; /** - * The maximum length of a word in this dictionary. This is the same value as the binary - * dictionary. + * The maximum length of a word in this dictionary. */ - protected static final int MAX_WORD_LENGTH = BinaryDictionary.MAX_WORD_LENGTH; + protected static final int MAX_WORD_LENGTH = Constants.Dictionary.MAX_WORD_LENGTH; /** * A static map of locks, each of which controls access to a single binary dictionary file. They diff --git a/java/src/com/android/inputmethod/latin/ExpandableDictionary.java b/java/src/com/android/inputmethod/latin/ExpandableDictionary.java index fa0d5497b..ae2ee577f 100644 --- a/java/src/com/android/inputmethod/latin/ExpandableDictionary.java +++ b/java/src/com/android/inputmethod/latin/ExpandableDictionary.java @@ -40,7 +40,7 @@ public class ExpandableDictionary extends Dictionary { protected static final int BIGRAM_MAX_FREQUENCY = 255; private Context mContext; - private char[] mWordBuilder = new char[BinaryDictionary.MAX_WORD_LENGTH]; + private char[] mWordBuilder = new char[Constants.Dictionary.MAX_WORD_LENGTH]; private int mMaxDepth; private int mInputLength; @@ -158,7 +158,7 @@ public class ExpandableDictionary extends Dictionary { super(dictType); mContext = context; clearDictionary(); - mCodes = new int[BinaryDictionary.MAX_WORD_LENGTH][]; + mCodes = new int[Constants.Dictionary.MAX_WORD_LENGTH][]; } public void loadDictionary() { @@ -195,11 +195,11 @@ public class ExpandableDictionary extends Dictionary { } public int getMaxWordLength() { - return BinaryDictionary.MAX_WORD_LENGTH; + return Constants.Dictionary.MAX_WORD_LENGTH; } public void addWord(final String word, final String shortcutTarget, final int frequency) { - if (word.length() >= BinaryDictionary.MAX_WORD_LENGTH) { + if (word.length() >= Constants.Dictionary.MAX_WORD_LENGTH) { return; } addWordRec(mRoots, word, 0, shortcutTarget, frequency, null); @@ -254,7 +254,7 @@ public class ExpandableDictionary extends Dictionary { final String prevWord, final ProximityInfo proximityInfo) { if (reloadDictionaryIfRequired()) return null; if (composer.size() > 1) { - if (composer.size() >= BinaryDictionary.MAX_WORD_LENGTH) { + if (composer.size() >= Constants.Dictionary.MAX_WORD_LENGTH) { return null; } final ArrayList<SuggestedWordInfo> suggestions = @@ -620,7 +620,7 @@ public class ExpandableDictionary extends Dictionary { } // Local to reverseLookUp, but do not allocate each time. - private final char[] mLookedUpString = new char[BinaryDictionary.MAX_WORD_LENGTH]; + private final char[] mLookedUpString = new char[Constants.Dictionary.MAX_WORD_LENGTH]; /** * reverseLookUp retrieves the full word given a list of terminal nodes and adds those words @@ -635,7 +635,7 @@ public class ExpandableDictionary extends Dictionary { for (NextWord nextWord : terminalNodes) { node = nextWord.getWordNode(); freq = nextWord.getFrequency(); - int index = BinaryDictionary.MAX_WORD_LENGTH; + int index = Constants.Dictionary.MAX_WORD_LENGTH; do { --index; mLookedUpString[index] = node.mCode; @@ -647,7 +647,7 @@ public class ExpandableDictionary extends Dictionary { // to ignore the word in this case. if (freq >= 0 && node == null) { suggestions.add(new SuggestedWordInfo(new String(mLookedUpString, index, - BinaryDictionary.MAX_WORD_LENGTH - index), + Constants.Dictionary.MAX_WORD_LENGTH - index), freq, SuggestedWordInfo.KIND_CORRECTION, mDictType)); } } diff --git a/java/src/com/android/inputmethod/latin/LastComposedWord.java b/java/src/com/android/inputmethod/latin/LastComposedWord.java index 44ef01204..488a6fcf2 100644 --- a/java/src/com/android/inputmethod/latin/LastComposedWord.java +++ b/java/src/com/android/inputmethod/latin/LastComposedWord.java @@ -45,7 +45,8 @@ public final class LastComposedWord { public final String mCommittedWord; public final String mSeparatorString; public final String mPrevWord; - public final InputPointers mInputPointers = new InputPointers(BinaryDictionary.MAX_WORD_LENGTH); + public final InputPointers mInputPointers = + new InputPointers(Constants.Dictionary.MAX_WORD_LENGTH); private boolean mActive; diff --git a/java/src/com/android/inputmethod/latin/LatinIME.java b/java/src/com/android/inputmethod/latin/LatinIME.java index a2c9e5b8f..a77f59750 100644 --- a/java/src/com/android/inputmethod/latin/LatinIME.java +++ b/java/src/com/android/inputmethod/latin/LatinIME.java @@ -149,6 +149,8 @@ public final class LatinIME extends InputMethodService implements KeyboardAction private boolean mIsUserDictionaryAvailable; private LastComposedWord mLastComposedWord = LastComposedWord.NOT_A_COMPOSED_WORD; + private PositionalInfoForUserDictPendingAddition + mPositionalInfoForUserDictPendingAddition = null; private final WordComposer mWordComposer = new WordComposer(); private RichInputConnection mConnection = new RichInputConnection(this); @@ -779,6 +781,19 @@ public final class LatinIME extends InputMethodService implements KeyboardAction mainKeyboardView.setGesturePreviewMode(mCurrentSettings.mGesturePreviewTrailEnabled, mCurrentSettings.mGestureFloatingPreviewTextEnabled); + // If we have a user dictionary addition in progress, we should check now if we should + // replace the previously committed string with the word that has actually been added + // to the user dictionary. + if (null != mPositionalInfoForUserDictPendingAddition + && mPositionalInfoForUserDictPendingAddition.tryReplaceWithActualWord( + mConnection, editorInfo, mLastSelectionEnd)) { + mPositionalInfoForUserDictPendingAddition = null; + } + // If tryReplaceWithActualWord returns false, we don't know what word was + // added to the user dictionary yet, so we keep the data and defer processing. The word will + // be replaced when the user dictionary reports back with the actual word, which ends + // up calling #onWordAddedToUserDictionary() in this class. + if (TRACE) Debug.startMethodTracing("/data/trace/latinime"); } @@ -1210,9 +1225,31 @@ public final class LatinIME extends InputMethodService implements KeyboardAction // Callback for the {@link SuggestionStripView}, to call when the "add to dictionary" hint is // pressed. @Override - public boolean addWordToUserDictionary(final String word) { + public void addWordToUserDictionary(final String word) { + if (TextUtils.isEmpty(word)) { + // Probably never supposed to happen, but just in case. + mPositionalInfoForUserDictPendingAddition = null; + return; + } + mPositionalInfoForUserDictPendingAddition = + new PositionalInfoForUserDictPendingAddition( + word, mLastSelectionEnd, getCurrentInputEditorInfo()); mUserDictionary.addWordToUserDictionary(word, 128); - return true; + } + + public void onWordAddedToUserDictionary(final String newSpelling) { + // If word was added but not by us, bail out + if (null == mPositionalInfoForUserDictPendingAddition) return; + if (mWordComposer.isComposingWord()) { + // We are late... give up and return + mPositionalInfoForUserDictPendingAddition = null; + return; + } + mPositionalInfoForUserDictPendingAddition.setActualWordBeingAdded(newSpelling); + if (mPositionalInfoForUserDictPendingAddition.tryReplaceWithActualWord( + mConnection, getCurrentInputEditorInfo(), mLastSelectionEnd)) { + mPositionalInfoForUserDictPendingAddition = null; + } } private static boolean isAlphabet(final int code) { @@ -1425,7 +1462,7 @@ public final class LatinIME extends InputMethodService implements KeyboardAction @Override public void onStartBatchInput() { - BatchInputUpdater.getInstance().onStartBatchInput(); + BatchInputUpdater.getInstance().onStartBatchInput(this); mConnection.beginBatchEdit(); if (mWordComposer.isComposingWord()) { if (ProductionFlag.IS_INTERNAL) { @@ -1491,34 +1528,32 @@ public final class LatinIME extends InputMethodService implements KeyboardAction public boolean handleMessage(final Message msg) { switch (msg.what) { case MSG_UPDATE_GESTURE_PREVIEW_AND_SUGGESTION_STRIP: - updateBatchInput((InputPointers)msg.obj, mLatinIme); + updateBatchInput((InputPointers)msg.obj); break; } return true; } // Run in the UI thread. - public synchronized void onStartBatchInput() { + public synchronized void onStartBatchInput(final LatinIME latinIme) { mHandler.removeMessages(MSG_UPDATE_GESTURE_PREVIEW_AND_SUGGESTION_STRIP); + mLatinIme = latinIme; mInBatchInput = true; } // Run in the Handler thread. - private synchronized void updateBatchInput(final InputPointers batchPointers, - final LatinIME latinIme) { + private synchronized void updateBatchInput(final InputPointers batchPointers) { if (!mInBatchInput) { // Batch input has ended or canceled while the message was being delivered. return; } - final SuggestedWords suggestedWords = getSuggestedWordsGestureLocked( - batchPointers, latinIme); - latinIme.mHandler.showGesturePreviewAndSuggestionStrip( + final SuggestedWords suggestedWords = getSuggestedWordsGestureLocked(batchPointers); + mLatinIme.mHandler.showGesturePreviewAndSuggestionStrip( suggestedWords, false /* dismissGestureFloatingPreviewText */); } // Run in the UI thread. - public void onUpdateBatchInput(final InputPointers batchPointers, final LatinIME latinIme) { - mLatinIme = latinIme; + public void onUpdateBatchInput(final InputPointers batchPointers) { if (mHandler.hasMessages(MSG_UPDATE_GESTURE_PREVIEW_AND_SUGGESTION_STRIP)) { return; } @@ -1527,29 +1562,34 @@ public final class LatinIME extends InputMethodService implements KeyboardAction .sendToTarget(); } - public synchronized void onCancelBatchInput(final LatinIME latinIme) { + public synchronized void onCancelBatchInput() { mInBatchInput = false; - latinIme.mHandler.showGesturePreviewAndSuggestionStrip( + mLatinIme.mHandler.showGesturePreviewAndSuggestionStrip( SuggestedWords.EMPTY, true /* dismissGestureFloatingPreviewText */); } // Run in the UI thread. - public synchronized SuggestedWords onEndBatchInput(final InputPointers batchPointers, - final LatinIME latinIme) { + public synchronized SuggestedWords onEndBatchInput(final InputPointers batchPointers) { mInBatchInput = false; - final SuggestedWords suggestedWords = getSuggestedWordsGestureLocked( - batchPointers, latinIme); - latinIme.mHandler.showGesturePreviewAndSuggestionStrip( + final SuggestedWords suggestedWords = getSuggestedWordsGestureLocked(batchPointers); + mLatinIme.mHandler.showGesturePreviewAndSuggestionStrip( suggestedWords, true /* dismissGestureFloatingPreviewText */); return suggestedWords; } // {@link LatinIME#getSuggestedWords(int)} method calls with same session id have to // be synchronized. - private static SuggestedWords getSuggestedWordsGestureLocked( - final InputPointers batchPointers, final LatinIME latinIme) { - latinIme.mWordComposer.setBatchInputPointers(batchPointers); - return latinIme.getSuggestedWords(Suggest.SESSION_GESTURE); + private SuggestedWords getSuggestedWordsGestureLocked(final InputPointers batchPointers) { + mLatinIme.mWordComposer.setBatchInputPointers(batchPointers); + final SuggestedWords suggestedWords = + mLatinIme.getSuggestedWords(Suggest.SESSION_GESTURE); + final int suggestionCount = suggestedWords.size(); + if (suggestionCount <= 1) { + final String mostProbableSuggestion = (suggestionCount == 0) ? null + : suggestedWords.getWord(0); + return mLatinIme.getOlderSuggestions(mostProbableSuggestion); + } + return suggestedWords; } } @@ -1568,13 +1608,13 @@ public final class LatinIME extends InputMethodService implements KeyboardAction @Override public void onUpdateBatchInput(final InputPointers batchPointers) { - BatchInputUpdater.getInstance().onUpdateBatchInput(batchPointers, this); + BatchInputUpdater.getInstance().onUpdateBatchInput(batchPointers); } @Override public void onEndBatchInput(final InputPointers batchPointers) { final SuggestedWords suggestedWords = BatchInputUpdater.getInstance().onEndBatchInput( - batchPointers, this); + batchPointers); final String batchInputText = suggestedWords.isEmpty() ? null : suggestedWords.getWord(0); if (TextUtils.isEmpty(batchInputText)) { @@ -1624,7 +1664,7 @@ public final class LatinIME extends InputMethodService implements KeyboardAction @Override public void onCancelBatchInput() { - BatchInputUpdater.getInstance().onCancelBatchInput(this); + BatchInputUpdater.getInstance().onCancelBatchInput(); } private void handleBackspace(final int spaceState) { @@ -1986,24 +2026,30 @@ public final class LatinIME extends InputMethodService implements KeyboardAction // old suggestions. Also, if we are showing the "add to dictionary" hint, we need to // revert to suggestions - although it is unclear how we can come here if it's displayed. if (suggestedWords.size() > 1 || typedWord.length() <= 1 - || !suggestedWords.mTypedWordValid + || suggestedWords.mTypedWordValid || mSuggestionStripView.isShowingAddToDictionaryHint()) { return suggestedWords; } else { - SuggestedWords previousSuggestions = mSuggestionStripView.getSuggestions(); - if (previousSuggestions == mCurrentSettings.mSuggestPuncList) { - previousSuggestions = SuggestedWords.EMPTY; - } - final ArrayList<SuggestedWords.SuggestedWordInfo> typedWordAndPreviousSuggestions = - SuggestedWords.getTypedWordAndPreviousSuggestions( - typedWord, previousSuggestions); - return new SuggestedWords(typedWordAndPreviousSuggestions, - false /* typedWordValid */, - false /* hasAutoCorrectionCandidate */, - false /* isPunctuationSuggestions */, - true /* isObsoleteSuggestions */, - false /* isPrediction */); + return getOlderSuggestions(typedWord); + } + } + + private SuggestedWords getOlderSuggestions(final String typedWord) { + SuggestedWords previousSuggestions = mSuggestionStripView.getSuggestions(); + if (previousSuggestions == mCurrentSettings.mSuggestPuncList) { + previousSuggestions = SuggestedWords.EMPTY; } + if (typedWord == null) { + return previousSuggestions; + } + final ArrayList<SuggestedWords.SuggestedWordInfo> typedWordAndPreviousSuggestions = + SuggestedWords.getTypedWordAndPreviousSuggestions(typedWord, previousSuggestions); + return new SuggestedWords(typedWordAndPreviousSuggestions, + false /* typedWordValid */, + false /* hasAutoCorrectionCandidate */, + false /* isPunctuationSuggestions */, + true /* isObsoleteSuggestions */, + false /* isPrediction */); } private void showSuggestionStrip(final SuggestedWords suggestedWords, final String typedWord) { diff --git a/java/src/com/android/inputmethod/latin/PositionalInfoForUserDictPendingAddition.java b/java/src/com/android/inputmethod/latin/PositionalInfoForUserDictPendingAddition.java new file mode 100644 index 000000000..8a2d22256 --- /dev/null +++ b/java/src/com/android/inputmethod/latin/PositionalInfoForUserDictPendingAddition.java @@ -0,0 +1,98 @@ +/* + * Copyright (C) 2012 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.view.inputmethod.EditorInfo; + +/** + * Holder class for data about a word already committed but that may still be edited. + * + * When the user chooses to add a word to the user dictionary by pressing the appropriate + * suggestion, a dialog is presented to give a chance to edit the word before it is actually + * registered as a user dictionary word. If the word is actually modified, the IME needs to + * go back and replace the word that was committed with the amended version. + * The word we need to replace with will only be known after it's actually committed, so + * the IME needs to take a note of what it has to replace and where it is. + * This class encapsulates this data. + */ +public class PositionalInfoForUserDictPendingAddition { + final private String mOriginalWord; + final private int mCursorPos; // Position of the cursor after the word + final private EditorInfo mEditorInfo; // On what binding this has been added + private String mActualWordBeingAdded; + + public PositionalInfoForUserDictPendingAddition(final String word, final int cursorPos, + final EditorInfo editorInfo) { + mOriginalWord = word; + mCursorPos = cursorPos; + mEditorInfo = editorInfo; + } + + public void setActualWordBeingAdded(final String actualWordBeingAdded) { + mActualWordBeingAdded = actualWordBeingAdded; + } + + /** + * Try to replace the string at the remembered position with the actual word being added. + * + * After the user validated the word being added, the IME has to replace the old version + * (which has been committed in the text view) with the amended version if it's different. + * This method tries to do that, but may fail because the IME is not yet ready to do so - + * for example, it is still waiting for the new string, or it is waiting to return to the text + * view in which the amendment should be made. In these cases, we should keep the data + * and wait until all conditions are met. + * This method returns true if the replacement has been successfully made and this data + * can be forgotten; it returns false if the replacement can't be made yet and we need to + * keep this until a later time. + * The IME knows about the actual word being added through a callback called by the + * user dictionary facility of the device. When this callback comes, the keyboard may still + * be connected to the edition dialog, or it may have already returned to the original text + * field. Replacement has to work in both cases. + * Accordingly, this method is called at two different points in time : upon getting the + * event that a new word was added to the user dictionary, and upon starting up in a + * new text field. + * @param connection The RichInputConnection through which to contact the editor. + * @param editorInfo Information pertaining to the editor we are currently in. + * @param currentCursorPosition The current cursor position, for checking purposes. + * @return true if the edit has been successfully made, false if we need to try again later + */ + public boolean tryReplaceWithActualWord(final RichInputConnection connection, + final EditorInfo editorInfo, final int currentCursorPosition) { + // If we still don't know the actual word being added, we need to try again later. + if (null == mActualWordBeingAdded) return false; + // The entered text and the registered text were the same anyway : we can + // return success right away even if focus has not returned yet to the text field we + // want to amend. + if (mActualWordBeingAdded.equals(mOriginalWord)) return true; + // Not the same text field : we need to try again later. This happens when the addition + // is reported by the user dictionary provider before the focus has moved back to the + // original text view, so the IME is still in the text view of the dialog and has no way to + // edit the original text view at this time. + if (!mEditorInfo.packageName.equals(editorInfo.packageName) + || mEditorInfo.fieldId != editorInfo.fieldId) { + return false; + } + // Same text field, but not the same cursor position : we give up, so we return success + // so that it won't be tried again + if (currentCursorPosition != mCursorPos) return true; + // We have made all the checks : do the replacement and report success + connection.setComposingRegion(currentCursorPosition - mOriginalWord.length(), + currentCursorPosition); + connection.commitText(mActualWordBeingAdded, mActualWordBeingAdded.length()); + return true; + } +} diff --git a/java/src/com/android/inputmethod/latin/RichInputConnection.java b/java/src/com/android/inputmethod/latin/RichInputConnection.java index 952e03a99..e9c81dab0 100644 --- a/java/src/com/android/inputmethod/latin/RichInputConnection.java +++ b/java/src/com/android/inputmethod/latin/RichInputConnection.java @@ -46,7 +46,7 @@ public final class RichInputConnection { private static final boolean DEBUG_PREVIOUS_TEXT = false; private static final boolean DEBUG_BATCH_NESTING = false; // Provision for a long word pair and a separator - private static final int LOOKBACK_CHARACTER_NUM = BinaryDictionary.MAX_WORD_LENGTH * 2 + 1; + private static final int LOOKBACK_CHARACTER_NUM = Constants.Dictionary.MAX_WORD_LENGTH * 2 + 1; private static final Pattern spaceRegex = Pattern.compile("\\s+"); private static final int INVALID_CURSOR_POSITION = -1; @@ -331,6 +331,24 @@ public final class RichInputConnection { } } + public void setComposingRegion(final int start, final int end) { + if (DEBUG_BATCH_NESTING) checkBatchEdit(); + if (DEBUG_PREVIOUS_TEXT) checkConsistencyForDebug(); + mCurrentCursorPosition = end; + final CharSequence textBeforeCursor = + getTextBeforeCursor(DEFAULT_TEXT_CACHE_SIZE + (end - start), 0); + final int indexOfStartOfComposingText = + Math.max(textBeforeCursor.length() - (end - start), 0); + mComposingText.append(textBeforeCursor.subSequence(indexOfStartOfComposingText, + textBeforeCursor.length())); + mCommittedTextBeforeComposingText.setLength(0); + mCommittedTextBeforeComposingText.append( + textBeforeCursor.subSequence(0, indexOfStartOfComposingText)); + if (null != mIC) { + mIC.setComposingRegion(start, end); + } + } + public void setComposingText(final CharSequence text, final int i) { if (DEBUG_BATCH_NESTING) checkBatchEdit(); if (DEBUG_PREVIOUS_TEXT) checkConsistencyForDebug(); diff --git a/java/src/com/android/inputmethod/latin/SuggestionSpanPickedNotificationReceiver.java b/java/src/com/android/inputmethod/latin/SuggestionSpanPickedNotificationReceiver.java index d188fc5ef..ed6fc03f9 100644 --- a/java/src/com/android/inputmethod/latin/SuggestionSpanPickedNotificationReceiver.java +++ b/java/src/com/android/inputmethod/latin/SuggestionSpanPickedNotificationReceiver.java @@ -16,11 +16,10 @@ package com.android.inputmethod.latin; -import com.android.inputmethod.compat.SuggestionSpanUtils; - import android.content.BroadcastReceiver; import android.content.Context; import android.content.Intent; +import android.text.style.SuggestionSpan; import android.util.Log; public final class SuggestionSpanPickedNotificationReceiver extends BroadcastReceiver { @@ -30,12 +29,12 @@ public final class SuggestionSpanPickedNotificationReceiver extends BroadcastRec @Override public void onReceive(Context context, Intent intent) { - if (SuggestionSpanUtils.ACTION_SUGGESTION_PICKED.equals(intent.getAction())) { + if (SuggestionSpan.ACTION_SUGGESTION_PICKED.equals(intent.getAction())) { if (DBG) { final String before = intent.getStringExtra( - SuggestionSpanUtils.SUGGESTION_SPAN_PICKED_BEFORE); + SuggestionSpan.SUGGESTION_SPAN_PICKED_BEFORE); final String after = intent.getStringExtra( - SuggestionSpanUtils.SUGGESTION_SPAN_PICKED_AFTER); + SuggestionSpan.SUGGESTION_SPAN_PICKED_AFTER); Log.d(TAG, "Received notification picked: " + before + "," + after); } } diff --git a/java/src/com/android/inputmethod/latin/UserBinaryDictionary.java b/java/src/com/android/inputmethod/latin/UserBinaryDictionary.java index 00c3cbe0a..ddae5ac48 100644 --- a/java/src/com/android/inputmethod/latin/UserBinaryDictionary.java +++ b/java/src/com/android/inputmethod/latin/UserBinaryDictionary.java @@ -18,10 +18,12 @@ package com.android.inputmethod.latin; import android.content.ContentProviderClient; import android.content.ContentResolver; +import android.content.ContentUris; import android.content.Context; import android.content.Intent; import android.database.ContentObserver; import android.database.Cursor; +import android.net.Uri; import android.provider.UserDictionary.Words; import android.text.TextUtils; @@ -87,8 +89,25 @@ public class UserBinaryDictionary extends ExpandableBinaryDictionary { mObserver = new ContentObserver(null) { @Override - public void onChange(boolean self) { + public void onChange(final boolean self) { + // This hook is deprecated as of API level 16, but should still be supported for + // cases where the IME is running on an older version of the platform. + onChange(self, null); + } + // The following hook is only available as of API level 16, and as such it will only + // work on JellyBean+ devices. On older versions of the platform, the hook + // above will be called instead. + @Override + public void onChange(final boolean self, final Uri uri) { setRequiresReload(true); + // We want to report back to Latin IME in case the user just entered the word. + // If the user changed the word in the dialog box, then we want to replace + // what was entered in the text field. + if (null == uri || !(context instanceof LatinIME)) return; + final long changedRowId = ContentUris.parseId(uri); + if (-1 == changedRowId) return; // Unknown content... Not sure why we're here + final String changedWord = getChangedWordForUri(uri); + ((LatinIME)context).onWordAddedToUserDictionary(changedWord); } }; cres.registerContentObserver(Words.CONTENT_URI, true, mObserver); @@ -96,6 +115,19 @@ public class UserBinaryDictionary extends ExpandableBinaryDictionary { loadDictionary(); } + private String getChangedWordForUri(final Uri uri) { + final Cursor cursor = mContext.getContentResolver().query(uri, + PROJECTION_QUERY, null, null, null); + if (cursor == null) return null; + try { + if (!cursor.moveToFirst()) return null; + final int indexWord = cursor.getColumnIndex(Words.WORD); + return cursor.getString(indexWord); + } finally { + cursor.close(); + } + } + @Override public synchronized void close() { if (mObserver != null) { diff --git a/java/src/com/android/inputmethod/latin/UserHistoryDictionary.java b/java/src/com/android/inputmethod/latin/UserHistoryDictionary.java index f30a60af2..31a0f83f1 100644 --- a/java/src/com/android/inputmethod/latin/UserHistoryDictionary.java +++ b/java/src/com/android/inputmethod/latin/UserHistoryDictionary.java @@ -147,8 +147,8 @@ public final class UserHistoryDictionary extends ExpandableDictionary { * The second word may not be null (a NullPointerException would be thrown). */ public int addToUserHistory(final String word1, final String word2, final boolean isValid) { - if (word2.length() >= BinaryDictionary.MAX_WORD_LENGTH || - (word1 != null && word1.length() >= BinaryDictionary.MAX_WORD_LENGTH)) { + if (word2.length() >= Constants.Dictionary.MAX_WORD_LENGTH || + (word1 != null && word1.length() >= Constants.Dictionary.MAX_WORD_LENGTH)) { return -1; } if (mBigramListLock.tryLock()) { @@ -239,8 +239,8 @@ public final class UserHistoryDictionary extends ExpandableDictionary { @Override public void setBigram(final String word1, final String word2, final int frequency) { - if (word1.length() < BinaryDictionary.MAX_WORD_LENGTH - && word2.length() < BinaryDictionary.MAX_WORD_LENGTH) { + if (word1.length() < Constants.Dictionary.MAX_WORD_LENGTH + && word2.length() < Constants.Dictionary.MAX_WORD_LENGTH) { profTotal++; if (DBG_SAVE_RESTORE) { Log.d(TAG, "load bigram: " + word1 + "," + word2 + "," + frequency); diff --git a/java/src/com/android/inputmethod/latin/WordComposer.java b/java/src/com/android/inputmethod/latin/WordComposer.java index daff442f3..4f1759079 100644 --- a/java/src/com/android/inputmethod/latin/WordComposer.java +++ b/java/src/com/android/inputmethod/latin/WordComposer.java @@ -25,7 +25,7 @@ import java.util.Arrays; * A place to store the currently composing word with information such as adjacent key codes as well */ public final class WordComposer { - private static final int N = BinaryDictionary.MAX_WORD_LENGTH; + private static final int MAX_WORD_LENGTH = Constants.Dictionary.MAX_WORD_LENGTH; public static final int CAPS_MODE_OFF = 0; // 1 is shift bit, 2 is caps bit, 4 is auto bit but this is just a convention as these bits @@ -36,7 +36,7 @@ public final class WordComposer { public static final int CAPS_MODE_AUTO_SHIFT_LOCKED = 0x7; private int[] mPrimaryKeyCodes; - private final InputPointers mInputPointers = new InputPointers(N); + private final InputPointers mInputPointers = new InputPointers(MAX_WORD_LENGTH); private final StringBuilder mTypedWord; private String mAutoCorrection; private boolean mIsResumed; @@ -55,8 +55,8 @@ public final class WordComposer { private boolean mIsFirstCharCapitalized; public WordComposer() { - mPrimaryKeyCodes = new int[N]; - mTypedWord = new StringBuilder(N); + mPrimaryKeyCodes = new int[MAX_WORD_LENGTH]; + mTypedWord = new StringBuilder(MAX_WORD_LENGTH); mAutoCorrection = null; mTrailingSingleQuotesCount = 0; mIsResumed = false; @@ -111,7 +111,7 @@ public final class WordComposer { // TODO: make sure that the index should not exceed MAX_WORD_LENGTH public int getCodeAt(int index) { - if (index >= BinaryDictionary.MAX_WORD_LENGTH) { + if (index >= MAX_WORD_LENGTH) { return -1; } return mPrimaryKeyCodes[index]; @@ -134,7 +134,7 @@ public final class WordComposer { final int newIndex = size(); mTypedWord.appendCodePoint(primaryCode); refreshSize(); - if (newIndex < BinaryDictionary.MAX_WORD_LENGTH) { + if (newIndex < MAX_WORD_LENGTH) { mPrimaryKeyCodes[newIndex] = primaryCode >= Constants.CODE_SPACE ? Character.toLowerCase(primaryCode) : primaryCode; // In the batch input mode, the {@code mInputPointers} holds batch input points and @@ -347,7 +347,7 @@ public final class WordComposer { // or a DECIDED_WORD we may cancel the commit later; otherwise, we should deactivate // the last composed word to ensure this does not happen. final int[] primaryKeyCodes = mPrimaryKeyCodes; - mPrimaryKeyCodes = new int[N]; + mPrimaryKeyCodes = new int[MAX_WORD_LENGTH]; final LastComposedWord lastComposedWord = new LastComposedWord(primaryKeyCodes, mInputPointers, mTypedWord.toString(), committedWord, separatorString, prevWord); diff --git a/java/src/com/android/inputmethod/latin/suggestions/SuggestionStripView.java b/java/src/com/android/inputmethod/latin/suggestions/SuggestionStripView.java index d7b514e8a..14bb95b3c 100644 --- a/java/src/com/android/inputmethod/latin/suggestions/SuggestionStripView.java +++ b/java/src/com/android/inputmethod/latin/suggestions/SuggestionStripView.java @@ -71,7 +71,7 @@ import java.util.ArrayList; public final class SuggestionStripView extends RelativeLayout implements OnClickListener, OnLongClickListener { public interface Listener { - public boolean addWordToUserDictionary(String word); + public void addWordToUserDictionary(String word); public void pickSuggestionManually(int index, String word); } |