diff options
Diffstat (limited to 'java/src')
26 files changed, 364 insertions, 112 deletions
diff --git a/java/src/com/android/inputmethod/keyboard/EmojiLayoutParams.java b/java/src/com/android/inputmethod/keyboard/EmojiLayoutParams.java index 71790b7d6..ceb44e79f 100644 --- a/java/src/com/android/inputmethod/keyboard/EmojiLayoutParams.java +++ b/java/src/com/android/inputmethod/keyboard/EmojiLayoutParams.java @@ -56,7 +56,7 @@ public class EmojiLayoutParams { - (mKeyVerticalGap - mBottomPadding) / 2; mEmojiPagerHeight = defaultKeyboardHeight - mEmojiActionBarHeight - mEmojiCategoryPageIdViewHeight; - mEmojiPagerBottomMargin = mKeyVerticalGap / 2; + mEmojiPagerBottomMargin = 0; mEmojiKeyboardHeight = mEmojiPagerHeight - mEmojiPagerBottomMargin - 1; } diff --git a/java/src/com/android/inputmethod/keyboard/EmojiKeyboardView.java b/java/src/com/android/inputmethod/keyboard/EmojiPalettesView.java index eb48d01f6..85ae50092 100644 --- a/java/src/com/android/inputmethod/keyboard/EmojiKeyboardView.java +++ b/java/src/com/android/inputmethod/keyboard/EmojiPalettesView.java @@ -60,8 +60,8 @@ import java.util.HashMap; import java.util.concurrent.ConcurrentHashMap; /** - * View class to implement Emoji keyboards. - * The Emoji keyboard consists of group of views {@link R.layout#emoji_keyboard_view}. + * View class to implement Emoji palettes. + * The Emoji keyboard consists of group of views {@link R.layout#emoji_palettes_view}. * <ol> * <li> Emoji category tabs. * <li> Delete button. @@ -70,16 +70,16 @@ import java.util.concurrent.ConcurrentHashMap; * </ol> * Because of the above reasons, this class doesn't extend {@link KeyboardView}. */ -public final class EmojiKeyboardView extends LinearLayout implements OnTabChangeListener, +public final class EmojiPalettesView extends LinearLayout implements OnTabChangeListener, ViewPager.OnPageChangeListener, View.OnClickListener, ScrollKeyboardView.OnKeyClickListener { - private static final String TAG = EmojiKeyboardView.class.getSimpleName(); + private static final String TAG = EmojiPalettesView.class.getSimpleName(); private final int mKeyBackgroundId; private final int mEmojiFunctionalKeyBackgroundId; private final KeyboardLayoutSet mLayoutSet; private final ColorStateList mTabLabelColor; private final DeleteKeyOnTouchListener mDeleteKeyOnTouchListener; - private EmojiKeyboardAdapter mEmojiKeyboardAdapter; + private EmojiPalettesAdapter mEmojiPalettesAdapter; private TabHost mTabHost; private ViewPager mEmojiPager; @@ -378,11 +378,11 @@ public final class EmojiKeyboardView extends LinearLayout implements OnTabChange private final EmojiCategory mEmojiCategory; - public EmojiKeyboardView(final Context context, final AttributeSet attrs) { - this(context, attrs, R.attr.emojiKeyboardViewStyle); + public EmojiPalettesView(final Context context, final AttributeSet attrs) { + this(context, attrs, R.attr.emojiPalettesViewStyle); } - public EmojiKeyboardView(final Context context, final AttributeSet attrs, final int defStyle) { + public EmojiPalettesView(final Context context, final AttributeSet attrs, final int defStyle) { super(context, attrs, defStyle); final TypedArray keyboardViewAttr = context.obtainStyledAttributes(attrs, R.styleable.KeyboardView, defStyle, R.style.KeyboardView); @@ -391,11 +391,11 @@ public final class EmojiKeyboardView extends LinearLayout implements OnTabChange mEmojiFunctionalKeyBackgroundId = keyboardViewAttr.getResourceId( R.styleable.KeyboardView_keyBackgroundEmojiFunctional, 0); keyboardViewAttr.recycle(); - final TypedArray emojiKeyboardViewAttr = context.obtainStyledAttributes(attrs, - R.styleable.EmojiKeyboardView, defStyle, R.style.EmojiKeyboardView); - mTabLabelColor = emojiKeyboardViewAttr.getColorStateList( - R.styleable.EmojiKeyboardView_emojiTabLabelColor); - emojiKeyboardViewAttr.recycle(); + final TypedArray emojiPalettesViewAttr = context.obtainStyledAttributes(attrs, + R.styleable.EmojiPalettesView, defStyle, R.style.EmojiPalettesView); + mTabLabelColor = emojiPalettesViewAttr.getColorStateList( + R.styleable.EmojiPalettesView_emojiTabLabelColor); + emojiPalettesViewAttr.recycle(); final KeyboardLayoutSet.Builder builder = new KeyboardLayoutSet.Builder( context, null /* editorInfo */); final Resources res = context.getResources(); @@ -453,10 +453,10 @@ public final class EmojiKeyboardView extends LinearLayout implements OnTabChange mTabHost.setOnTabChangedListener(this); mTabHost.getTabWidget().setStripEnabled(true); - mEmojiKeyboardAdapter = new EmojiKeyboardAdapter(mEmojiCategory, mLayoutSet, this); + mEmojiPalettesAdapter = new EmojiPalettesAdapter(mEmojiCategory, mLayoutSet, this); mEmojiPager = (ViewPager)findViewById(R.id.emoji_keyboard_pager); - mEmojiPager.setAdapter(mEmojiKeyboardAdapter); + mEmojiPager.setAdapter(mEmojiPalettesAdapter); mEmojiPager.setOnPageChangeListener(this); mEmojiPager.setOffscreenPageLimit(0); final Resources res = getResources(); @@ -484,10 +484,10 @@ public final class EmojiKeyboardView extends LinearLayout implements OnTabChange spaceKey.setTag(Constants.CODE_SPACE); spaceKey.setOnClickListener(this); emojiLp.setKeyProperties(spaceKey); - final ImageView sendKey = (ImageView)findViewById(R.id.emoji_keyboard_send); - sendKey.setBackgroundResource(mEmojiFunctionalKeyBackgroundId); - sendKey.setTag(Constants.CODE_ENTER); - sendKey.setOnClickListener(this); + final ImageView alphabetKey2 = (ImageView)findViewById(R.id.emoji_keyboard_alphabet2); + alphabetKey2.setBackgroundResource(mEmojiFunctionalKeyBackgroundId); + alphabetKey2.setTag(Constants.CODE_SWITCH_ALPHA_SYMBOL); + alphabetKey2.setOnClickListener(this); } @Override @@ -551,7 +551,7 @@ public final class EmojiKeyboardView extends LinearLayout implements OnTabChange @Override public void onKeyClick(final Key key) { - mEmojiKeyboardAdapter.addRecentKey(key); + mEmojiPalettesAdapter.addRecentKey(key); mEmojiCategory.saveLastTypedCategoryPage(); final int code = key.getCode(); if (code == Constants.CODE_OUTPUT_TEXT) { @@ -580,10 +580,18 @@ public final class EmojiKeyboardView extends LinearLayout implements OnTabChange } private void setCurrentCategoryId(final int categoryId, final boolean force) { - if (mEmojiCategory.getCurrentCategoryId() == categoryId && !force) { + final int oldCategoryId = mEmojiCategory.getCurrentCategoryId(); + if (oldCategoryId == categoryId && !force) { return; } + if (oldCategoryId == CATEGORY_ID_RECENTS) { + // Needs to save pending updates for recent keys when we get out of the recents + // category because we don't want to move the recent emojis around while the user + // is in the recents category. + mEmojiPalettesAdapter.flushPendingRecentKeys(); + } + mEmojiCategory.setCurrentCategoryId(categoryId); final int newTabId = mEmojiCategory.getTabIdFromCategoryId(categoryId); final int newCategoryPageId = mEmojiCategory.getPageIdFromCategoryId(categoryId); @@ -596,7 +604,7 @@ public final class EmojiKeyboardView extends LinearLayout implements OnTabChange } } - private static class EmojiKeyboardAdapter extends PagerAdapter { + private static class EmojiPalettesAdapter extends PagerAdapter { private final ScrollKeyboardView.OnKeyClickListener mListener; private final DynamicGridKeyboard mRecentsKeyboard; private final SparseArray<ScrollKeyboardView> mActiveKeyboardView = @@ -604,7 +612,7 @@ public final class EmojiKeyboardView extends LinearLayout implements OnTabChange private final EmojiCategory mEmojiCategory; private int mActivePosition = 0; - public EmojiKeyboardAdapter(final EmojiCategory emojiCategory, + public EmojiPalettesAdapter(final EmojiCategory emojiCategory, final KeyboardLayoutSet layoutSet, final ScrollKeyboardView.OnKeyClickListener listener) { mEmojiCategory = emojiCategory; @@ -612,8 +620,18 @@ public final class EmojiKeyboardView extends LinearLayout implements OnTabChange mRecentsKeyboard = mEmojiCategory.getKeyboard(CATEGORY_ID_RECENTS, 0); } + public void flushPendingRecentKeys() { + mRecentsKeyboard.flushPendingRecentKeys(); + final KeyboardView recentKeyboardView = + mActiveKeyboardView.get(mEmojiCategory.getRecentTabId()); + if (recentKeyboardView != null) { + recentKeyboardView.invalidateAllKeys(); + } + } + public void addRecentKey(final Key key) { if (mEmojiCategory.isInRecentTab()) { + mRecentsKeyboard.addPendingKey(key); return; } mRecentsKeyboard.addKeyFirst(key); @@ -644,6 +662,12 @@ public final class EmojiKeyboardView extends LinearLayout implements OnTabChange @Override public Object instantiateItem(final ViewGroup container, final int position) { + final ScrollKeyboardView oldKeyboardView = mActiveKeyboardView.get(position); + if (oldKeyboardView != null) { + oldKeyboardView.deallocateMemory(); + // This may be redundant but wanted to be safer.. + mActiveKeyboardView.remove(position); + } final Keyboard keyboard = mEmojiCategory.getKeyboardFromPagePosition(position); final LayoutInflater inflater = LayoutInflater.from(container.getContext()); diff --git a/java/src/com/android/inputmethod/keyboard/KeyboardSwitcher.java b/java/src/com/android/inputmethod/keyboard/KeyboardSwitcher.java index ad6e2c0f2..4fc1082f1 100644 --- a/java/src/com/android/inputmethod/keyboard/KeyboardSwitcher.java +++ b/java/src/com/android/inputmethod/keyboard/KeyboardSwitcher.java @@ -68,7 +68,7 @@ public final class KeyboardSwitcher implements KeyboardState.SwitchActions { private InputView mCurrentInputView; private View mMainKeyboardFrame; private MainKeyboardView mKeyboardView; - private EmojiKeyboardView mEmojiKeyboardView; + private EmojiPalettesView mEmojiPalettesView; private LatinIME mLatinIME; private Resources mResources; @@ -169,7 +169,7 @@ public final class KeyboardSwitcher implements KeyboardState.SwitchActions { } private void setKeyboard(final Keyboard keyboard) { - // Make {@link MainKeyboardView} visible and hide {@link EmojiKeyboardView}. + // Make {@link MainKeyboardView} visible and hide {@link EmojiPalettesView}. setMainKeyboardFrame(); final MainKeyboardView keyboardView = mKeyboardView; final Keyboard oldKeyboard = keyboardView.getKeyboard(); @@ -259,14 +259,14 @@ public final class KeyboardSwitcher implements KeyboardState.SwitchActions { private void setMainKeyboardFrame() { mMainKeyboardFrame.setVisibility(View.VISIBLE); - mEmojiKeyboardView.setVisibility(View.GONE); + mEmojiPalettesView.setVisibility(View.GONE); } // Implements {@link KeyboardState.SwitchActions}. @Override public void setEmojiKeyboard() { mMainKeyboardFrame.setVisibility(View.GONE); - mEmojiKeyboardView.setVisibility(View.VISIBLE); + mEmojiPalettesView.setVisibility(View.VISIBLE); } // Implements {@link KeyboardState.SwitchActions}. @@ -315,7 +315,7 @@ public final class KeyboardSwitcher implements KeyboardState.SwitchActions { } public boolean isShowingEmojiKeyboard() { - return mEmojiKeyboardView != null && mEmojiKeyboardView.getVisibility() == View.VISIBLE; + return mEmojiPalettesView != null && mEmojiPalettesView.getVisibility() == View.VISIBLE; } public boolean isShowingMoreKeysPanel() { @@ -327,7 +327,7 @@ public final class KeyboardSwitcher implements KeyboardState.SwitchActions { public View getVisibleKeyboardView() { if (isShowingEmojiKeyboard()) { - return mEmojiKeyboardView; + return mEmojiPalettesView; } return mKeyboardView; } @@ -345,15 +345,15 @@ public final class KeyboardSwitcher implements KeyboardState.SwitchActions { mCurrentInputView = (InputView)LayoutInflater.from(mThemeContext).inflate( R.layout.input_view, null); mMainKeyboardFrame = mCurrentInputView.findViewById(R.id.main_keyboard_frame); - mEmojiKeyboardView = (EmojiKeyboardView)mCurrentInputView.findViewById( + mEmojiPalettesView = (EmojiPalettesView)mCurrentInputView.findViewById( R.id.emoji_keyboard_view); mKeyboardView = (MainKeyboardView) mCurrentInputView.findViewById(R.id.keyboard_view); mKeyboardView.setHardwareAcceleratedDrawingEnabled(isHardwareAcceleratedDrawingEnabled); mKeyboardView.setKeyboardActionListener(mLatinIME); - mEmojiKeyboardView.setHardwareAcceleratedDrawingEnabled( + mEmojiPalettesView.setHardwareAcceleratedDrawingEnabled( isHardwareAcceleratedDrawingEnabled); - mEmojiKeyboardView.setKeyboardActionListener(mLatinIME); + mEmojiPalettesView.setKeyboardActionListener(mLatinIME); // This always needs to be set since the accessibility state can // potentially change without the input view being re-created. diff --git a/java/src/com/android/inputmethod/keyboard/internal/DynamicGridKeyboard.java b/java/src/com/android/inputmethod/keyboard/internal/DynamicGridKeyboard.java index 0dd71e2ec..09766ac6c 100644 --- a/java/src/com/android/inputmethod/keyboard/internal/DynamicGridKeyboard.java +++ b/java/src/com/android/inputmethod/keyboard/internal/DynamicGridKeyboard.java @@ -20,7 +20,7 @@ import android.content.SharedPreferences; import android.text.TextUtils; import android.util.Log; -import com.android.inputmethod.keyboard.EmojiKeyboardView; +import com.android.inputmethod.keyboard.EmojiPalettesView; import com.android.inputmethod.keyboard.Key; import com.android.inputmethod.keyboard.Keyboard; import com.android.inputmethod.latin.settings.Settings; @@ -39,15 +39,16 @@ public class DynamicGridKeyboard extends Keyboard { private static final String TAG = DynamicGridKeyboard.class.getSimpleName(); private static final int TEMPLATE_KEY_CODE_0 = 0x30; private static final int TEMPLATE_KEY_CODE_1 = 0x31; + private final Object mLock = new Object(); private final SharedPreferences mPrefs; - private final int mLeftPadding; private final int mHorizontalStep; private final int mVerticalStep; private final int mColumnsNum; private final int mMaxKeyCount; private final boolean mIsRecents; private final ArrayDeque<GridKey> mGridKeys = CollectionUtils.newArrayDeque(); + private final ArrayDeque<Key> mPendingKeys = CollectionUtils.newArrayDeque(); private Key[] mCachedGridKeys; @@ -56,12 +57,11 @@ public class DynamicGridKeyboard extends Keyboard { super(templateKeyboard); final Key key0 = getTemplateKey(TEMPLATE_KEY_CODE_0); final Key key1 = getTemplateKey(TEMPLATE_KEY_CODE_1); - mLeftPadding = key0.getX(); mHorizontalStep = Math.abs(key1.getX() - key0.getX()); mVerticalStep = key0.getHeight() + mVerticalGap; mColumnsNum = mBaseWidth / mHorizontalStep; mMaxKeyCount = maxKeyCount; - mIsRecents = categoryId == EmojiKeyboardView.CATEGORY_ID_RECENTS; + mIsRecents = categoryId == EmojiPalettesView.CATEGORY_ID_RECENTS; mPrefs = prefs; } @@ -74,6 +74,21 @@ public class DynamicGridKeyboard extends Keyboard { throw new RuntimeException("Can't find template key: code=" + code); } + public void addPendingKey(final Key usedKey) { + synchronized (mLock) { + mPendingKeys.addLast(usedKey); + } + } + + public void flushPendingRecentKeys() { + synchronized (mLock) { + while (!mPendingKeys.isEmpty()) { + addKey(mPendingKeys.pollFirst(), true); + } + saveRecentKeys(); + } + } + public void addKeyFirst(final Key usedKey) { addKey(usedKey, true); if (mIsRecents) { @@ -89,7 +104,7 @@ public class DynamicGridKeyboard extends Keyboard { if (usedKey == null) { return; } - synchronized (mGridKeys) { + synchronized (mLock) { mCachedGridKeys = null; final GridKey key = new GridKey(usedKey); while (mGridKeys.remove(key)) { @@ -105,9 +120,11 @@ public class DynamicGridKeyboard extends Keyboard { } int index = 0; for (final GridKey gridKey : mGridKeys) { - final int keyX = getKeyX(index); - final int keyY = getKeyY(index); - gridKey.updateCorrdinates(keyX, keyY); + final int keyX0 = getKeyX0(index); + final int keyY0 = getKeyY0(index); + final int keyX1 = getKeyX1(index); + final int keyY1 = getKeyY1(index); + gridKey.updateCorrdinates(keyX0, keyY0, keyX1, keyY1); index++; } } @@ -155,19 +172,29 @@ public class DynamicGridKeyboard extends Keyboard { } } - private int getKeyX(final int index) { + private int getKeyX0(final int index) { final int column = index % mColumnsNum; - return column * mHorizontalStep + mLeftPadding; + return column * mHorizontalStep; + } + + private int getKeyX1(final int index) { + final int column = index % mColumnsNum + 1; + return column * mHorizontalStep; } - private int getKeyY(final int index) { + private int getKeyY0(final int index) { final int row = index / mColumnsNum; - return row * mVerticalStep + mTopPadding; + return row * mVerticalStep; + } + + private int getKeyY1(final int index) { + final int row = index / mColumnsNum + 1; + return row * mVerticalStep; } @Override public Key[] getKeys() { - synchronized (mGridKeys) { + synchronized (mLock) { if (mCachedGridKeys != null) { return mCachedGridKeys; } @@ -190,10 +217,10 @@ public class DynamicGridKeyboard extends Keyboard { super(originalKey); } - public void updateCorrdinates(final int x, final int y) { - mCurrentX = x; - mCurrentY = y; - getHitBox().set(x, y, x + getWidth(), y + getHeight()); + public void updateCorrdinates(final int x0, final int y0, final int x1, final int y1) { + mCurrentX = x0; + mCurrentY = y0; + getHitBox().set(x0, y0, x1, y1); } @Override diff --git a/java/src/com/android/inputmethod/keyboard/internal/KeyboardState.java b/java/src/com/android/inputmethod/keyboard/internal/KeyboardState.java index 506dfa751..9f9fdaa6f 100644 --- a/java/src/com/android/inputmethod/keyboard/internal/KeyboardState.java +++ b/java/src/com/android/inputmethod/keyboard/internal/KeyboardState.java @@ -178,8 +178,6 @@ public final class KeyboardState { if (!state.mIsAlphabetShiftLocked) { setShifted(state.mShiftMode); } - // TODO: is this the right place to do this? Should we do this in setShift* instead? - mSwitchActions.requestUpdatingShiftState(); } else { mPrevMainKeyboardWasShiftLocked = state.mIsAlphabetShiftLocked; } diff --git a/java/src/com/android/inputmethod/keyboard/internal/KeyboardTextsSet.java b/java/src/com/android/inputmethod/keyboard/internal/KeyboardTextsSet.java index 684cf632b..d219e8195 100644 --- a/java/src/com/android/inputmethod/keyboard/internal/KeyboardTextsSet.java +++ b/java/src/com/android/inputmethod/keyboard/internal/KeyboardTextsSet.java @@ -251,6 +251,7 @@ public final class KeyboardTextsSet { /* 146 */ "more_keys_for_single_quote", /* 147 */ "more_keys_for_double_quote", /* 148 */ "more_keys_for_tablet_double_quote", + /* 149 */ "emoji_key_as_more_key", }; private static final String EMPTY = ""; @@ -439,6 +440,7 @@ public final class KeyboardTextsSet { /* 146 */ "!fixedColumnOrder!5,!text/single_quotes,!text/single_angle_quotes", /* 147 */ "!fixedColumnOrder!5,!text/double_quotes,!text/double_angle_quotes", /* 148 */ "!fixedColumnOrder!6,!text/double_quotes,!text/single_quotes,!text/double_angle_quotes,!text/single_angle_quotes", + /* 149 */ "!icon/emoji_key|!code/key_emoji", }; /* Language af: Afrikaans */ diff --git a/java/src/com/android/inputmethod/latin/AbstractDictionaryWriter.java b/java/src/com/android/inputmethod/latin/AbstractDictionaryWriter.java index 4a0ce3735..463d09344 100644 --- a/java/src/com/android/inputmethod/latin/AbstractDictionaryWriter.java +++ b/java/src/com/android/inputmethod/latin/AbstractDictionaryWriter.java @@ -41,8 +41,17 @@ abstract public class AbstractDictionaryWriter extends Dictionary { abstract public void clear(); + /** + * Add a unigram with an optional shortcut to the dictionary. + * @param word The word to add. + * @param shortcutTarget A shortcut target for this word, or null if none. + * @param frequency The frequency for this unigram. + * @param shortcutFreq The frequency of the shortcut (0~15, with 15 = whitelist). Ignored + * if shortcutTarget is null. + * @param isNotAWord true if this is not a word, i.e. shortcut only. + */ abstract public void addUnigramWord(final String word, final String shortcutTarget, - final int frequency, final boolean isNotAWord); + final int frequency, final int shortcutFreq, final boolean isNotAWord); // TODO: Remove lastModifiedTime after making binary dictionary support forgetting curve. abstract public void addBigramWords(final String word0, final String word1, diff --git a/java/src/com/android/inputmethod/latin/BinaryDictionary.java b/java/src/com/android/inputmethod/latin/BinaryDictionary.java index 541e69788..fd296988e 100644 --- a/java/src/com/android/inputmethod/latin/BinaryDictionary.java +++ b/java/src/com/android/inputmethod/latin/BinaryDictionary.java @@ -52,6 +52,10 @@ public final class BinaryDictionary extends Dictionary { public static final String UNIGRAM_COUNT_QUERY = "UNIGRAM_COUNT"; @UsedForTesting public static final String BIGRAM_COUNT_QUERY = "BIGRAM_COUNT"; + @UsedForTesting + public static final String MAX_UNIGRAM_COUNT_QUERY = "MAX_UNIGRAM_COUNT"; + @UsedForTesting + public static final String MAX_BIGRAM_COUNT_QUERY = "MAX_BIGRAM_COUNT"; private long mNativeDict; private final Locale mLocale; diff --git a/java/src/com/android/inputmethod/latin/ContactsBinaryDictionary.java b/java/src/com/android/inputmethod/latin/ContactsBinaryDictionary.java index ffeb92784..47891c6b7 100644 --- a/java/src/com/android/inputmethod/latin/ContactsBinaryDictionary.java +++ b/java/src/com/android/inputmethod/latin/ContactsBinaryDictionary.java @@ -127,7 +127,7 @@ public class ContactsBinaryDictionary extends ExpandableBinaryDictionary { if (DEBUG) { Log.d(TAG, "loadAccountVocabulary: " + word); } - super.addWord(word, null /* shortcut */, FREQUENCY_FOR_CONTACTS, + super.addWord(word, null /* shortcut */, FREQUENCY_FOR_CONTACTS, 0 /* shortcutFreq */, false /* isNotAWord */); } } @@ -213,7 +213,7 @@ public class ContactsBinaryDictionary extends ExpandableBinaryDictionary { Log.d(TAG, "addName " + name + ", " + word + ", " + prevWord); } super.addWord(word, null /* shortcut */, FREQUENCY_FOR_CONTACTS, - false /* isNotAWord */); + 0 /* shortcutFreq */, false /* isNotAWord */); if (!TextUtils.isEmpty(prevWord)) { if (mUseFirstLastBigrams) { super.addBigram(prevWord, word, FREQUENCY_FOR_CONTACTS_BIGRAM, diff --git a/java/src/com/android/inputmethod/latin/DictionaryWriter.java b/java/src/com/android/inputmethod/latin/DictionaryWriter.java index 84abfa66d..3df2a2b63 100644 --- a/java/src/com/android/inputmethod/latin/DictionaryWriter.java +++ b/java/src/com/android/inputmethod/latin/DictionaryWriter.java @@ -62,13 +62,13 @@ public class DictionaryWriter extends AbstractDictionaryWriter { // considering performance regression. @Override public void addUnigramWord(final String word, final String shortcutTarget, final int frequency, - final boolean isNotAWord) { + final int shortcutFreq, final boolean isNotAWord) { if (shortcutTarget == null) { 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)); + shortcutTargets.add(new WeightedString(shortcutTarget, shortcutFreq)); mFusionDictionary.add(word, frequency, shortcutTargets, isNotAWord); } } diff --git a/java/src/com/android/inputmethod/latin/ExpandableBinaryDictionary.java b/java/src/com/android/inputmethod/latin/ExpandableBinaryDictionary.java index c79a4ff90..eb8650e6f 100644 --- a/java/src/com/android/inputmethod/latin/ExpandableBinaryDictionary.java +++ b/java/src/com/android/inputmethod/latin/ExpandableBinaryDictionary.java @@ -261,10 +261,16 @@ abstract public class ExpandableBinaryDictionary extends Dictionary { /** * Adds a word unigram to the dictionary. Used for loading a dictionary. + * @param word The word to add. + * @param shortcutTarget A shortcut target for this word, or null if none. + * @param frequency The frequency for this unigram. + * @param shortcutFreq The frequency of the shortcut (0~15, with 15 = whitelist). Ignored + * if shortcutTarget is null. + * @param isNotAWord true if this is not a word, i.e. shortcut only. */ protected void addWord(final String word, final String shortcutTarget, - final int frequency, final boolean isNotAWord) { - mDictionaryWriter.addUnigramWord(word, shortcutTarget, frequency, isNotAWord); + final int frequency, final int shortcutFreq, final boolean isNotAWord) { + mDictionaryWriter.addUnigramWord(word, shortcutTarget, frequency, shortcutFreq, isNotAWord); } /** @@ -313,7 +319,7 @@ abstract public class ExpandableBinaryDictionary extends Dictionary { * Dynamically adds a word unigram to the dictionary. May overwrite an existing entry. */ protected void addWordDynamically(final String word, final String shortcutTarget, - final int frequency, final boolean isNotAWord) { + final int frequency, final int shortcutFreq, final boolean isNotAWord) { if (!mIsUpdatable) { Log.w(TAG, "addWordDynamically is called for non-updatable dictionary: " + mFilename); return; @@ -326,7 +332,8 @@ abstract public class ExpandableBinaryDictionary extends Dictionary { mBinaryDictionary.addUnigramWord(word, frequency); } else { // TODO: Remove. - mDictionaryWriter.addUnigramWord(word, shortcutTarget, frequency, isNotAWord); + mDictionaryWriter.addUnigramWord(word, shortcutTarget, frequency, shortcutFreq, + isNotAWord); } } }); diff --git a/java/src/com/android/inputmethod/latin/ExpandableDictionary.java b/java/src/com/android/inputmethod/latin/ExpandableDictionary.java index d491f988a..95c9bcab9 100644 --- a/java/src/com/android/inputmethod/latin/ExpandableDictionary.java +++ b/java/src/com/android/inputmethod/latin/ExpandableDictionary.java @@ -156,15 +156,36 @@ public class ExpandableDictionary extends Dictionary { return Constants.DICTIONARY_MAX_WORD_LENGTH; } - public void addWord(final String word, final String shortcutTarget, final int frequency) { + /** + * Add a word with an optional shortcut to the dictionary. + * @param word The word to add. + * @param shortcutTarget A shortcut target for this word, or null if none. + * @param frequency The frequency for this unigram. + * @param shortcutFreq The frequency of the shortcut (0~15, with 15 = whitelist). Ignored + * if shortcutTarget is null. + */ + public void addWord(final String word, final String shortcutTarget, final int frequency, + final int shortcutFreq) { if (word.length() >= Constants.DICTIONARY_MAX_WORD_LENGTH) { return; } - addWordRec(mRoots, word, 0, shortcutTarget, frequency, null); + addWordRec(mRoots, word, 0, shortcutTarget, frequency, shortcutFreq, null); } + /** + * Add a word, recursively searching for its correct place in the trie tree. + * @param children The node to recursively search for addition. Initially, the root of the tree. + * @param word The word to add. + * @param depth The current depth in the tree. + * @param shortcutTarget A shortcut target for this word, or null if none. + * @param frequency The frequency for this unigram. + * @param shortcutFreq The frequency of the shortcut (0~15, with 15 = whitelist). Ignored + * if shortcutTarget is null. + * @param parentNode The parent node, for up linking. Initially null, as the root has no parent. + */ private void addWordRec(final NodeArray children, final String word, final int depth, - final String shortcutTarget, final int frequency, final Node parentNode) { + final String shortcutTarget, final int frequency, final int shortcutFreq, + final Node parentNode) { final int wordLength = word.length(); if (wordLength <= depth) return; final char c = word.charAt(depth); @@ -204,7 +225,8 @@ public class ExpandableDictionary extends Dictionary { if (childNode.mChildren == null) { childNode.mChildren = new NodeArray(); } - addWordRec(childNode.mChildren, word, depth + 1, shortcutTarget, frequency, childNode); + addWordRec(childNode.mChildren, word, depth + 1, shortcutTarget, frequency, shortcutFreq, + childNode); } @Override diff --git a/java/src/com/android/inputmethod/latin/LatinIME.java b/java/src/com/android/inputmethod/latin/LatinIME.java index 0f3d28976..0e93590a3 100644 --- a/java/src/com/android/inputmethod/latin/LatinIME.java +++ b/java/src/com/android/inputmethod/latin/LatinIME.java @@ -605,8 +605,24 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen } private void initSuggest() { - final Locale subtypeLocale = mSubtypeSwitcher.getCurrentSubtypeLocale(); - final String localeStr = subtypeLocale.toString(); + final Locale switcherSubtypeLocale = mSubtypeSwitcher.getCurrentSubtypeLocale(); + final String switcherLocaleStr = switcherSubtypeLocale.toString(); + final Locale subtypeLocale; + final String localeStr; + if (TextUtils.isEmpty(switcherLocaleStr)) { + // This happens in very rare corner cases - for example, immediately after a switch + // to LatinIME has been requested, about a frame later another switch happens. In this + // case, we are about to go down but we still don't know it, however the system tells + // us there is no current subtype so the locale is the empty string. Take the best + // possible guess instead -- it's bound to have no consequences, and we have no way + // of knowing anyway. + Log.e(TAG, "System is reporting no current subtype."); + subtypeLocale = getResources().getConfiguration().locale; + localeStr = subtypeLocale.toString(); + } else { + subtypeLocale = switcherSubtypeLocale; + localeStr = switcherLocaleStr; + } final Suggest newSuggest = new Suggest(this /* Context */, subtypeLocale, this /* SuggestInitializationListener */); @@ -792,6 +808,7 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen @SuppressWarnings("deprecation") private void onStartInputViewInternal(final EditorInfo editorInfo, final boolean restarting) { super.onStartInputView(editorInfo, restarting); + mRichImm.clearSubtypeCaches(); final KeyboardSwitcher switcher = mKeyboardSwitcher; final MainKeyboardView mainKeyboardView = switcher.getMainKeyboardView(); // If we are starting input in a different text field from before, we'll have to reload @@ -887,12 +904,17 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen // Sometimes, while rotating, for some reason the framework tells the app we are not // connected to it and that means we can't refresh the cache. In this case, schedule a // refresh later. + final boolean canReachInputConnection; if (!mConnection.resetCachesUponCursorMoveAndReturnSuccess(editorInfo.initialSelStart, false /* shouldFinishComposition */)) { // We try resetting the caches up to 5 times before giving up. mHandler.postResetCaches(isDifferentTextField, 5 /* remainingTries */); + canReachInputConnection = false; } else { - if (isDifferentTextField) mHandler.postResumeSuggestions(); + if (isDifferentTextField) { + mHandler.postResumeSuggestions(); + } + canReachInputConnection = true; } if (isDifferentTextField) { @@ -904,7 +926,11 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen suggest.setAutoCorrectionThreshold(currentSettingsValues.mAutoCorrectionThreshold); } - switcher.loadKeyboard(editorInfo, currentSettingsValues); + if (canReachInputConnection) { + // If we can't reach the input connection, we don't want to call loadKeyboard yet. + // It will be done in #retryResetCaches. + switcher.loadKeyboard(editorInfo, currentSettingsValues); + } } else if (restarting) { // TODO: Come up with a more comprehensive way to reset the keyboard layout when // a keyboard layout set doesn't get reloaded in this method. @@ -1033,7 +1059,6 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen // Should do the following in onFinishInputInternal but until JB MR2 it's not called :( if (mWordComposer.isComposingWord()) mConnection.finishComposingText(); resetComposingState(true /* alsoResetLastComposedWord */); - mRichImm.clearSubtypeCaches(); // Notify ResearchLogger if (ProductionFlag.USES_DEVELOPMENT_ONLY_DIAGNOSTICS) { ResearchLogger.latinIME_onFinishInputViewInternal(finishingInput, mLastSelectionStart, @@ -1438,11 +1463,21 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen if (!settingsValues.mCorrectionEnabled) return false; if (!settingsValues.mUseDoubleSpacePeriod) return false; if (!mHandler.isAcceptingDoubleSpacePeriod()) return false; - final CharSequence lastThree = mConnection.getTextBeforeCursor(3, 0); - if (lastThree != null && lastThree.length() == 3 - && canBeFollowedByDoubleSpacePeriod(lastThree.charAt(0)) - && lastThree.charAt(1) == Constants.CODE_SPACE - && lastThree.charAt(2) == Constants.CODE_SPACE) { + // We only do this when we see two spaces and an accepted code point before the cursor. + // The code point may be a surrogate pair but the two spaces may not, so we need 4 chars. + final CharSequence lastThree = mConnection.getTextBeforeCursor(4, 0); + if (null == lastThree) return false; + final int length = lastThree.length(); + if (length < 3) return false; + if (lastThree.charAt(length - 1) != Constants.CODE_SPACE) return false; + if (lastThree.charAt(length - 2) != Constants.CODE_SPACE) return false; + // We know there are spaces in pos -1 and -2, and we have at least three chars. + // If we have only three chars, isSurrogatePairs can't return true as charAt(1) is a space, + // so this is fine. + final int firstCodePoint = + Character.isSurrogatePair(lastThree.charAt(0), lastThree.charAt(1)) ? + Character.codePointAt(lastThree, 0) : lastThree.charAt(length - 3); + if (canBeFollowedByDoubleSpacePeriod(firstCodePoint)) { mHandler.cancelDoubleSpacePeriodTimer(); mConnection.deleteSurroundingText(2, 0); final String textToInsert = ". "; @@ -1467,7 +1502,8 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen || codePoint == Constants.CODE_CLOSING_SQUARE_BRACKET || codePoint == Constants.CODE_CLOSING_CURLY_BRACKET || codePoint == Constants.CODE_CLOSING_ANGLE_BRACKET - || codePoint == Constants.CODE_PLUS; + || codePoint == Constants.CODE_PLUS + || Character.getType(codePoint) == Character.OTHER_SYMBOL; } // Callback for the {@link SuggestionStripView}, to call when the "add to dictionary" hint is @@ -2928,11 +2964,13 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen if (!mConnection.resetCachesUponCursorMoveAndReturnSuccess(mLastSelectionStart, false)) { if (0 < remainingTries) { mHandler.postResetCaches(tryResumeSuggestions, remainingTries - 1); + return; } - return; + // If remainingTries is 0, we should stop waiting for new tries, but it's still + // better to load the keyboard (less things will be broken). } tryFixLyingCursorPosition(); - mKeyboardSwitcher.updateShiftState(); + mKeyboardSwitcher.loadKeyboard(getCurrentInputEditorInfo(), mSettings.getCurrent()); if (tryResumeSuggestions) mHandler.postResumeSuggestions(); } diff --git a/java/src/com/android/inputmethod/latin/Suggest.java b/java/src/com/android/inputmethod/latin/Suggest.java index 9fd1f53a2..c270d47d0 100644 --- a/java/src/com/android/inputmethod/latin/Suggest.java +++ b/java/src/com/android/inputmethod/latin/Suggest.java @@ -286,14 +286,16 @@ public final class Suggest { // the word *would* have been auto-corrected. if (!isCorrectionEnabled || !allowsToBeAutoCorrected || !wordComposer.isComposingWord() || suggestionsSet.isEmpty() || wordComposer.hasDigits() - || wordComposer.isMostlyCaps() || wordComposer.isResumed() - || !hasMainDictionary()) { + || wordComposer.isMostlyCaps() || wordComposer.isResumed() || !hasMainDictionary() + || SuggestedWordInfo.KIND_SHORTCUT == suggestionsSet.first().mKind) { // If we don't have a main dictionary, we never want to auto-correct. The reason for // this is, the user may have a contact whose name happens to match a valid word in // their language, and it will unexpectedly auto-correct. For example, if the user // types in English with no dictionary and has a "Will" in their contact list, "will" // would always auto-correct to "Will" which is unwanted. Hence, no main dict => no // auto-correct. + // Also, shortcuts should never auto-correct unless they are whitelist entries. + // TODO: we may want to have shortcut-only entries auto-correct in the future. hasAutoCorrection = false; } else { hasAutoCorrection = AutoCorrectionUtils.suggestionExceedsAutoCorrectionThreshold( diff --git a/java/src/com/android/inputmethod/latin/UserBinaryDictionary.java b/java/src/com/android/inputmethod/latin/UserBinaryDictionary.java index 864a17375..15b3d8d02 100644 --- a/java/src/com/android/inputmethod/latin/UserBinaryDictionary.java +++ b/java/src/com/android/inputmethod/latin/UserBinaryDictionary.java @@ -47,6 +47,9 @@ public class UserBinaryDictionary extends ExpandableBinaryDictionary { private static final String USER_DICTIONARY_ALL_LANGUAGES = ""; private static final int HISTORICAL_DEFAULT_USER_DICTIONARY_FREQUENCY = 250; private static final int LATINIME_DEFAULT_USER_DICTIONARY_FREQUENCY = 160; + // Shortcut frequency is 0~15, with 15 = whitelist. We don't want user dictionary entries + // to auto-correct, so we set this to the highest frequency that won't, i.e. 14. + private static final int USER_DICT_SHORTCUT_FREQUENCY = 14; // TODO: use Words.SHORTCUT when we target JellyBean or above final static String SHORTCUT = "shortcut"; @@ -243,10 +246,12 @@ 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, false /* isNotAWord */); + super.addWord(word, null, adjustedFrequency, 0 /* shortcutFreq */, + false /* isNotAWord */); } if (null != shortcut && shortcut.length() < MAX_WORD_LENGTH) { - super.addWord(shortcut, word, adjustedFrequency, true /* isNotAWord */); + super.addWord(shortcut, word, adjustedFrequency, USER_DICT_SHORTCUT_FREQUENCY, + true /* isNotAWord */); } cursor.moveToNext(); } diff --git a/java/src/com/android/inputmethod/latin/makedict/DictUpdater.java b/java/src/com/android/inputmethod/latin/makedict/DictUpdater.java index 709ea3310..c4f7ec91f 100644 --- a/java/src/com/android/inputmethod/latin/makedict/DictUpdater.java +++ b/java/src/com/android/inputmethod/latin/makedict/DictUpdater.java @@ -16,6 +16,7 @@ package com.android.inputmethod.latin.makedict; +import com.android.inputmethod.annotations.UsedForTesting; import com.android.inputmethod.latin.makedict.FusionDictionary.WeightedString; import java.io.IOException; @@ -24,6 +25,7 @@ import java.util.ArrayList; /** * An interface of a binary dictionary updater. */ +@UsedForTesting public interface DictUpdater extends DictDecoder { /** @@ -31,6 +33,7 @@ public interface DictUpdater extends DictDecoder { * * @param word the word to be deleted. */ + @UsedForTesting public void deleteWord(final String word) throws IOException, UnsupportedFormatException; /** @@ -43,6 +46,7 @@ public interface DictUpdater extends DictDecoder { * @param isBlackListEntry whether this should be a blacklist entry. */ // TODO: Support batch insertion. + @UsedForTesting public void insertWord(final String word, final int frequency, final ArrayList<WeightedString> bigramStrings, final ArrayList<WeightedString> shortcuts, final boolean isNotAWord, diff --git a/java/src/com/android/inputmethod/latin/makedict/FusionDictionary.java b/java/src/com/android/inputmethod/latin/makedict/FusionDictionary.java index be653feec..3bb218bea 100644 --- a/java/src/com/android/inputmethod/latin/makedict/FusionDictionary.java +++ b/java/src/com/android/inputmethod/latin/makedict/FusionDictionary.java @@ -367,10 +367,11 @@ public final class FusionDictionary implements Iterable<Word> { * Helper method to convert a String to an int array. */ static int[] getCodePoints(final String word) { - // TODO: this is a copy-paste of the contents of StringUtils.toCodePointArray, + // TODO: this is a copy-paste of the old contents of StringUtils.toCodePointArray, // which is not visible from the makedict package. Factor this code. + final int length = word.length(); + if (length <= 0) return new int[] {}; final char[] characters = word.toCharArray(); - final int length = characters.length; final int[] codePoints = new int[Character.codePointCount(characters, 0, length)]; int codePoint = Character.codePointAt(characters, 0); int dsti = 0; diff --git a/java/src/com/android/inputmethod/latin/makedict/Ver3DictUpdater.java b/java/src/com/android/inputmethod/latin/makedict/Ver3DictUpdater.java index fa7ae310a..07adda625 100644 --- a/java/src/com/android/inputmethod/latin/makedict/Ver3DictUpdater.java +++ b/java/src/com/android/inputmethod/latin/makedict/Ver3DictUpdater.java @@ -57,7 +57,7 @@ public class Ver3DictUpdater extends Ver3DictDecoder implements DictUpdater { public void deleteWord(final String word) throws IOException, UnsupportedFormatException { if (mOutStream == null) openStreamAndBuffer(); mDictBuffer.position(0); - super.readHeader(); + readHeader(); final int wordPos = getTerminalPosition(word); if (wordPos != FormatSpec.NOT_VALID_WORD) { mDictBuffer.position(wordPos); diff --git a/java/src/com/android/inputmethod/latin/makedict/Ver4DictDecoder.java b/java/src/com/android/inputmethod/latin/makedict/Ver4DictDecoder.java index bab24e301..53729075f 100644 --- a/java/src/com/android/inputmethod/latin/makedict/Ver4DictDecoder.java +++ b/java/src/com/android/inputmethod/latin/makedict/Ver4DictDecoder.java @@ -48,7 +48,7 @@ public class Ver4DictDecoder extends AbstractDictDecoder { private final File mDictDirectory; private final DictionaryBufferFactory mBufferFactory; - private DictBuffer mDictBuffer; + protected DictBuffer mDictBuffer; private DictBuffer mFrequencyBuffer; private DictBuffer mTerminalAddressTableBuffer; private DictBuffer mBigramBuffer; diff --git a/java/src/com/android/inputmethod/latin/makedict/Ver4DictUpdater.java b/java/src/com/android/inputmethod/latin/makedict/Ver4DictUpdater.java new file mode 100644 index 000000000..3d8f186ba --- /dev/null +++ b/java/src/com/android/inputmethod/latin/makedict/Ver4DictUpdater.java @@ -0,0 +1,59 @@ +/* + * 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.makedict; + +import com.android.inputmethod.annotations.UsedForTesting; +import com.android.inputmethod.latin.makedict.FusionDictionary.WeightedString; + +import java.io.File; +import java.io.IOException; +import java.util.ArrayList; + +/** + * An implementation of DictUpdater for version 4 binary dictionary. + */ +@UsedForTesting +public class Ver4DictUpdater extends Ver4DictDecoder implements DictUpdater { + + @UsedForTesting + public Ver4DictUpdater(final File dictDirectory, final int factoryType) { + // DictUpdater must have an updatable DictBuffer. + super(dictDirectory, ((factoryType & MASK_DICTBUFFER) == USE_BYTEARRAY) + ? USE_BYTEARRAY : USE_WRITABLE_BYTEBUFFER); + } + + @Override + public void deleteWord(final String word) throws IOException, UnsupportedFormatException { + if (mDictBuffer == null) openDictBuffer(); + readHeader(); + final int wordPos = getTerminalPosition(word); + if (wordPos != FormatSpec.NOT_VALID_WORD) { + mDictBuffer.position(wordPos); + final int flags = PtNodeReader.readPtNodeOptionFlags(mDictBuffer); + mDictBuffer.position(wordPos); + mDictBuffer.put((byte) DynamicBinaryDictIOUtils.markAsDeleted(flags)); + } + } + + @Override + public void insertWord(final String word, final int frequency, + final ArrayList<WeightedString> bigramStrings, final ArrayList<WeightedString> shortcuts, + final boolean isNotAWord, final boolean isBlackListEntry) + throws IOException, UnsupportedFormatException { + // TODO: Implement this method. + } +} diff --git a/java/src/com/android/inputmethod/latin/personalization/DecayingExpandableBinaryDictionaryBase.java b/java/src/com/android/inputmethod/latin/personalization/DecayingExpandableBinaryDictionaryBase.java index c8b62b6c8..a1e36006b 100644 --- a/java/src/com/android/inputmethod/latin/personalization/DecayingExpandableBinaryDictionaryBase.java +++ b/java/src/com/android/inputmethod/latin/personalization/DecayingExpandableBinaryDictionaryBase.java @@ -138,7 +138,7 @@ public abstract class DecayingExpandableBinaryDictionaryBase extends ExpandableB final int frequency = ENABLE_BINARY_DICTIONARY_DYNAMIC_UPDATE ? (isValid ? FREQUENCY_FOR_WORDS_IN_DICTS : FREQUENCY_FOR_WORDS_NOT_IN_DICTS) : FREQUENCY_FOR_TYPED; - addWordDynamically(word1, null /* the "shortcut" parameter is null */, frequency, + addWordDynamically(word1, null /* shortcutTarget */, frequency, 0 /* shortcutFreq */, false /* isNotAWord */); // Do not insert a word as a bigram of itself if (word1.equals(word0)) { @@ -171,11 +171,11 @@ public abstract class DecayingExpandableBinaryDictionaryBase extends ExpandableB final OnAddWordListener listener = new OnAddWordListener() { @Override public void setUnigram(final String word, final String shortcutTarget, - final int frequency) { + final int frequency, final int shortcutFreq) { if (DBG_SAVE_RESTORE) { Log.d(TAG, "load unigram: " + word + "," + frequency); } - addWord(word, shortcutTarget, frequency, false /* isNotAWord */); + addWord(word, shortcutTarget, frequency, shortcutFreq, false /* isNotAWord */); ++profTotalCount[0]; } diff --git a/java/src/com/android/inputmethod/latin/personalization/DynamicPersonalizationDictionaryWriter.java b/java/src/com/android/inputmethod/latin/personalization/DynamicPersonalizationDictionaryWriter.java index 039b25337..6f152bb91 100644 --- a/java/src/com/android/inputmethod/latin/personalization/DynamicPersonalizationDictionaryWriter.java +++ b/java/src/com/android/inputmethod/latin/personalization/DynamicPersonalizationDictionaryWriter.java @@ -75,15 +75,21 @@ public class DynamicPersonalizationDictionaryWriter extends AbstractDictionaryWr /** * Adds a word unigram to the fusion dictionary. Call updateBinaryDictionary when all changes * are done to update the binary dictionary. + * @param word The word to add. + * @param shortcutTarget A shortcut target for this word, or null if none. + * @param frequency The frequency for this unigram. + * @param shortcutFreq The frequency of the shortcut (0~15, with 15 = whitelist). Ignored + * if shortcutTarget is null. + * @param isNotAWord true if this is not a word, i.e. shortcut only. */ @Override public void addUnigramWord(final String word, final String shortcutTarget, final int frequency, - final boolean isNotAWord) { + final int shortcutFreq, final boolean isNotAWord) { if (mBigramList.size() > mMaxHistoryBigrams * 2) { // Too many entries: just stop adding new vocabulary and wait next refresh. return; } - mExpandableDictionary.addWord(word, shortcutTarget, frequency); + mExpandableDictionary.addWord(word, shortcutTarget, frequency, shortcutFreq); mBigramList.addBigram(null, word, (byte)frequency); } diff --git a/java/src/com/android/inputmethod/latin/settings/SettingsValues.java b/java/src/com/android/inputmethod/latin/settings/SettingsValues.java index ee322e91b..2abcdc7fa 100644 --- a/java/src/com/android/inputmethod/latin/settings/SettingsValues.java +++ b/java/src/com/android/inputmethod/latin/settings/SettingsValues.java @@ -45,8 +45,9 @@ import java.util.Locale; */ public final class SettingsValues { private static final String TAG = SettingsValues.class.getSimpleName(); - // "floatNegativeInfinity" is a special marker string for Float.NEGATIVE_INFINITE - // currently used for auto-correction + // "floatMaxValue" and "floatNegativeInfinity" are special marker strings for + // Float.NEGATIVE_INFINITE and Float.MAX_VALUE. Currently used for auto-correction settings. + private static final String FLOAT_MAX_VALUE_MARKER_STRING = "floatMaxValue"; private static final String FLOAT_NEGATIVE_INFINITY_MARKER_STRING = "floatNegativeInfinity"; // From resources: @@ -343,24 +344,28 @@ public final class SettingsValues { final String[] autoCorrectionThresholdValues = res.getStringArray( R.array.auto_correction_threshold_values); // When autoCorrectionThreshold is greater than 1.0, it's like auto correction is off. - float autoCorrectionThreshold = Float.MAX_VALUE; + final float autoCorrectionThreshold; try { final int arrayIndex = Integer.valueOf(currentAutoCorrectionSetting); if (arrayIndex >= 0 && arrayIndex < autoCorrectionThresholdValues.length) { final String val = autoCorrectionThresholdValues[arrayIndex]; - if (FLOAT_NEGATIVE_INFINITY_MARKER_STRING.equals(val)) { + if (FLOAT_MAX_VALUE_MARKER_STRING.equals(val)) { + autoCorrectionThreshold = Float.MAX_VALUE; + } else if (FLOAT_NEGATIVE_INFINITY_MARKER_STRING.equals(val)) { autoCorrectionThreshold = Float.NEGATIVE_INFINITY; } else { autoCorrectionThreshold = Float.parseFloat(val); } + } else { + autoCorrectionThreshold = Float.MAX_VALUE; } - } catch (NumberFormatException e) { + } catch (final NumberFormatException e) { // Whenever the threshold settings are correct, never come here. - autoCorrectionThreshold = Float.MAX_VALUE; Log.w(TAG, "Cannot load auto correction threshold setting." + " currentAutoCorrectionSetting: " + currentAutoCorrectionSetting + ", autoCorrectionThresholdValues: " + Arrays.toString(autoCorrectionThresholdValues), e); + return Float.MAX_VALUE; } return autoCorrectionThreshold; } diff --git a/java/src/com/android/inputmethod/latin/spellcheck/AndroidSpellCheckerService.java b/java/src/com/android/inputmethod/latin/spellcheck/AndroidSpellCheckerService.java index eb6d7c106..503b18b1b 100644 --- a/java/src/com/android/inputmethod/latin/spellcheck/AndroidSpellCheckerService.java +++ b/java/src/com/android/inputmethod/latin/spellcheck/AndroidSpellCheckerService.java @@ -204,10 +204,20 @@ public final class AndroidSpellCheckerService extends SpellCheckerService return AndroidSpellCheckerSessionFactory.newInstance(this); } - public static SuggestionsInfo getNotInDictEmptySuggestions() { - return new SuggestionsInfo(0, EMPTY_STRING_ARRAY); + /** + * Returns an empty SuggestionsInfo with flags signaling the word is not in the dictionary. + * @param reportAsTypo whether this should include the flag LOOKS_LIKE_TYPO, for red underline. + * @return the empty SuggestionsInfo with the appropriate flags set. + */ + public static SuggestionsInfo getNotInDictEmptySuggestions(final boolean reportAsTypo) { + return new SuggestionsInfo(reportAsTypo ? SuggestionsInfo.RESULT_ATTR_LOOKS_LIKE_TYPO : 0, + EMPTY_STRING_ARRAY); } + /** + * Returns an empty suggestionInfo with flags signaling the word is in the dictionary. + * @return the empty SuggestionsInfo with the appropriate flags set. + */ public static SuggestionsInfo getInDictEmptySuggestions() { return new SuggestionsInfo(SuggestionsInfo.RESULT_ATTR_IN_THE_DICTIONARY, EMPTY_STRING_ARRAY); diff --git a/java/src/com/android/inputmethod/latin/spellcheck/AndroidWordLevelSpellCheckerSession.java b/java/src/com/android/inputmethod/latin/spellcheck/AndroidWordLevelSpellCheckerSession.java index 69f9a467f..d6e5b75ad 100644 --- a/java/src/com/android/inputmethod/latin/spellcheck/AndroidWordLevelSpellCheckerSession.java +++ b/java/src/com/android/inputmethod/latin/spellcheck/AndroidWordLevelSpellCheckerSession.java @@ -161,6 +161,12 @@ public abstract class AndroidWordLevelSpellCheckerSession extends Session { } } + private static final int CHECKABILITY_CHECKABLE = 0; + private static final int CHECKABILITY_TOO_MANY_NON_LETTERS = 1; + private static final int CHECKABILITY_CONTAINS_PERIOD = 2; + private static final int CHECKABILITY_EMAIL_OR_URL = 3; + private static final int CHECKABILITY_FIRST_LETTER_UNCHECKABLE = 4; + private static final int CHECKABILITY_TOO_SHORT = 5; /** * Finds out whether a particular string should be filtered out of spell checking. * @@ -171,10 +177,10 @@ public abstract class AndroidWordLevelSpellCheckerSession extends Session { * * @param text the string to evaluate. * @param script the identifier for the script this spell checker recognizes - * @return true if we should filter this text out, false otherwise + * @return one of the FILTER_OUT_* constants above. */ - private static boolean shouldFilterOut(final String text, final int script) { - if (TextUtils.isEmpty(text) || text.length() <= 1) return true; + private static int getCheckabilityInScript(final String text, final int script) { + if (TextUtils.isEmpty(text) || text.length() <= 1) return CHECKABILITY_TOO_SHORT; // TODO: check if an equivalent processing can't be done more quickly with a // compiled regexp. @@ -182,7 +188,7 @@ public abstract class AndroidWordLevelSpellCheckerSession extends Session { final int firstCodePoint = text.codePointAt(0); // Filter out words that don't start with a letter or an apostrophe if (!isLetterCheckableByLanguage(firstCodePoint, script) - && '\'' != firstCodePoint) return true; + && '\'' != firstCodePoint) return CHECKABILITY_FIRST_LETTER_UNCHECKABLE; // Filter contents final int length = text.length(); @@ -193,13 +199,21 @@ public abstract class AndroidWordLevelSpellCheckerSession extends Session { // Any word containing a SLASH is probably either an ad-hoc combination of two // words or a URI - in either case we don't want to spell check that if (Constants.CODE_COMMERCIAL_AT == codePoint || Constants.CODE_SLASH == codePoint) { - return true; + return CHECKABILITY_EMAIL_OR_URL; + } + // If the string contains a period, native returns strange suggestions (it seems + // to return suggestions for everything up to the period only and to ignore the + // rest), so we suppress lookup if there is a period. + // TODO: investigate why native returns these suggestions and remove this code. + if (Constants.CODE_PERIOD == codePoint) { + return CHECKABILITY_CONTAINS_PERIOD; } if (isLetterCheckableByLanguage(codePoint, script)) ++letterCount; } // Guestimate heuristic: perform spell checking if at least 3/4 of the characters // in this word are letters - return (letterCount * 4 < length * 3); + return (letterCount * 4 < length * 3) + ? CHECKABILITY_TOO_MANY_NON_LETTERS : CHECKABILITY_CHECKABLE; } /** @@ -256,16 +270,20 @@ public abstract class AndroidWordLevelSpellCheckerSession extends Session { cachedSuggestionsParams.mFlags, cachedSuggestionsParams.mSuggestions); } - if (shouldFilterOut(inText, mScript)) { + final int checkability = getCheckabilityInScript(inText, mScript); + if (CHECKABILITY_CHECKABLE != checkability) { DictAndKeyboard dictInfo = null; try { dictInfo = mDictionaryPool.pollWithDefaultTimeout(); if (!DictionaryPool.isAValidDictionary(dictInfo)) { - return AndroidSpellCheckerService.getNotInDictEmptySuggestions(); + return AndroidSpellCheckerService.getNotInDictEmptySuggestions( + false /* reportAsTypo */); } return dictInfo.mDictionary.isValidWord(inText) ? AndroidSpellCheckerService.getInDictEmptySuggestions() - : AndroidSpellCheckerService.getNotInDictEmptySuggestions(); + : AndroidSpellCheckerService.getNotInDictEmptySuggestions( + CHECKABILITY_CONTAINS_PERIOD == checkability + /* reportAsTypo */); } finally { if (null != dictInfo) { if (!mDictionaryPool.offer(dictInfo)) { @@ -290,7 +308,8 @@ public abstract class AndroidWordLevelSpellCheckerSession extends Session { try { dictInfo = mDictionaryPool.pollWithDefaultTimeout(); if (!DictionaryPool.isAValidDictionary(dictInfo)) { - return AndroidSpellCheckerService.getNotInDictEmptySuggestions(); + return AndroidSpellCheckerService.getNotInDictEmptySuggestions( + false /* reportAsTypo */); } final WordComposer composer = new WordComposer(); final int length = text.length(); @@ -351,7 +370,8 @@ public abstract class AndroidWordLevelSpellCheckerSession extends Session { throw e; } else { Log.e(TAG, "Exception while spellcheking", e); - return AndroidSpellCheckerService.getNotInDictEmptySuggestions(); + return AndroidSpellCheckerService.getNotInDictEmptySuggestions( + false /* reportAsTypo */); } } } diff --git a/java/src/com/android/inputmethod/latin/utils/UserHistoryDictIOUtils.java b/java/src/com/android/inputmethod/latin/utils/UserHistoryDictIOUtils.java index ea32a74ff..635afe7cc 100644 --- a/java/src/com/android/inputmethod/latin/utils/UserHistoryDictIOUtils.java +++ b/java/src/com/android/inputmethod/latin/utils/UserHistoryDictIOUtils.java @@ -49,7 +49,16 @@ public final class UserHistoryDictIOUtils { private static final String LAST_UPDATED_TIME_KEY = "date"; public interface OnAddWordListener { - public void setUnigram(final String word, final String shortcutTarget, final int frequency); + /** + * Callback to be notified when a word is added to the dictionary. + * @param word The added word. + * @param shortcutTarget A shortcut target for this word, or null if none. + * @param frequency The frequency for this word. + * @param shortcutFreq The frequency of the shortcut (0~15, with 15 = whitelist). + * Unspecified if shortcutTarget is null - do not rely on its value. + */ + public void setUnigram(final String word, final String shortcutTarget, final int frequency, + final int shortcutFreq); public void setBigram(final String word1, final String word2, final int frequency); } @@ -153,7 +162,7 @@ public final class UserHistoryDictIOUtils { for (Entry<Integer, String> entry : unigrams.entrySet()) { final String word1 = entry.getValue(); final int unigramFrequency = frequencies.get(entry.getKey()); - to.setUnigram(word1, null, unigramFrequency); + to.setUnigram(word1, null /* shortcutTarget */, unigramFrequency, 0 /* shortcutFreq */); final ArrayList<PendingAttribute> attrList = bigrams.get(entry.getKey()); if (attrList != null) { for (final PendingAttribute attr : attrList) { |