diff options
Diffstat (limited to 'java/src')
38 files changed, 477 insertions, 624 deletions
diff --git a/java/src/com/android/inputmethod/keyboard/EmojiPalettesView.java b/java/src/com/android/inputmethod/keyboard/EmojiPalettesView.java index 4c53b528f..7fff46aaf 100644 --- a/java/src/com/android/inputmethod/keyboard/EmojiPalettesView.java +++ b/java/src/com/android/inputmethod/keyboard/EmojiPalettesView.java @@ -25,7 +25,6 @@ import android.content.res.Resources; import android.content.res.TypedArray; import android.graphics.Color; import android.graphics.Rect; -import android.graphics.Typeface; import android.os.Build; import android.os.CountDownTimer; import android.preference.PreferenceManager; @@ -49,6 +48,8 @@ import android.widget.TextView; import com.android.inputmethod.keyboard.internal.DynamicGridKeyboard; import com.android.inputmethod.keyboard.internal.EmojiLayoutParams; import com.android.inputmethod.keyboard.internal.EmojiPageKeyboardView; +import com.android.inputmethod.keyboard.internal.KeyDrawParams; +import com.android.inputmethod.keyboard.internal.KeyVisualAttributes; import com.android.inputmethod.latin.Constants; import com.android.inputmethod.latin.R; import com.android.inputmethod.latin.SubtypeSwitcher; @@ -634,20 +635,23 @@ public final class EmojiPalettesView extends LinearLayout implements OnTabChange // TODO: } - // Hack: These parameters are hacky. - public void startEmojiPalettes(final String switchToAlphaLabel, final int switchToAlphaColor, - final float switchToAlphaSize, final Typeface switchToAlphaTypeface) { + private static void setupAlphabetKey(final TextView alphabetKey, final String label, + final KeyDrawParams params) { + alphabetKey.setText(label); + alphabetKey.setTextColor(params.mTextColor); + alphabetKey.setTextSize(TypedValue.COMPLEX_UNIT_PX, params.mLabelSize); + alphabetKey.setTypeface(params.mTypeface); + } + + public void startEmojiPalettes(final String switchToAlphaLabel, + final KeyVisualAttributes keyVisualAttr) { if (DEBUG_PAGER) { Log.d(TAG, "allocate emoji palettes memory " + mCurrentPagerPosition); } - mAlphabetKeyLeft.setText(switchToAlphaLabel); - mAlphabetKeyLeft.setTextColor(switchToAlphaColor); - mAlphabetKeyLeft.setTextSize(TypedValue.COMPLEX_UNIT_PX, switchToAlphaSize); - mAlphabetKeyLeft.setTypeface(switchToAlphaTypeface); - mAlphabetKeyRight.setText(switchToAlphaLabel); - mAlphabetKeyRight.setTextColor(switchToAlphaColor); - mAlphabetKeyRight.setTextSize(TypedValue.COMPLEX_UNIT_PX, switchToAlphaSize); - mAlphabetKeyRight.setTypeface(switchToAlphaTypeface); + final KeyDrawParams params = new KeyDrawParams(); + params.updateParams(mEmojiLayoutParams.getActionBarHeight(), keyVisualAttr); + setupAlphabetKey(mAlphabetKeyLeft, switchToAlphaLabel, params); + setupAlphabetKey(mAlphabetKeyRight, switchToAlphaLabel, params); mEmojiPager.setAdapter(mEmojiPalettesAdapter); mEmojiPager.setCurrentItem(mCurrentPagerPosition); } diff --git a/java/src/com/android/inputmethod/keyboard/Key.java b/java/src/com/android/inputmethod/keyboard/Key.java index ceda9ee9a..816a94300 100644 --- a/java/src/com/android/inputmethod/keyboard/Key.java +++ b/java/src/com/android/inputmethod/keyboard/Key.java @@ -137,8 +137,6 @@ public class Key implements Comparable<Key> { private final OptionalAttributes mOptionalAttributes; - private static final int DEFAULT_TEXT_COLOR = 0xFFFFFFFF; - private static final class OptionalAttributes { /** Text to output when pressed. This can be multiple characters, like ".com" */ public final String mOutputText; @@ -585,22 +583,7 @@ public class Key implements Comparable<Key> { } public final int selectTextColor(final KeyDrawParams params) { - if (isShiftedLetterActivated()) { - return params.mTextInactivatedColor; - } - if (params.mTextColorStateList == null) { - return DEFAULT_TEXT_COLOR; - } - final int[] state; - // TODO: Hack!!!!!!!! Consider having a new attribute for the functional text labels. - // Currently, we distinguish "input key" from "functional key" by checking the - // length of the label( > 1) and "functional" attributes (= true). - if (mLabel != null && mLabel.length() > 1) { - state = getCurrentDrawableState(); - } else { - state = KEY_STATE_NORMAL; - } - return params.mTextColorStateList.getColorForState(state, DEFAULT_TEXT_COLOR); + return isShiftedLetterActivated() ? params.mTextInactivatedColor : params.mTextColor; } public final int selectHintTextSize(final KeyDrawParams params) { @@ -733,10 +716,14 @@ public class Key implements Comparable<Key> { return (attrs != null) ? attrs.mAltCode : CODE_UNSPECIFIED; } + public int getIconId() { + return mIconId; + } + public Drawable getIcon(final KeyboardIconsSet iconSet, final int alpha) { final OptionalAttributes attrs = mOptionalAttributes; final int disabledIconId = (attrs != null) ? attrs.mDisabledIconId : ICON_UNDEFINED; - final int iconId = mEnabled ? mIconId : disabledIconId; + final int iconId = mEnabled ? getIconId() : disabledIconId; final Drawable icon = iconSet.getIconDrawable(iconId); if (icon != null) { icon.setAlpha(alpha); @@ -748,7 +735,7 @@ public class Key implements Comparable<Key> { final OptionalAttributes attrs = mOptionalAttributes; final int previewIconId = (attrs != null) ? attrs.mPreviewIconId : ICON_UNDEFINED; return previewIconId != ICON_UNDEFINED - ? iconSet.getIconDrawable(previewIconId) : iconSet.getIconDrawable(mIconId); + ? iconSet.getIconDrawable(previewIconId) : iconSet.getIconDrawable(getIconId()); } public int getWidth() { diff --git a/java/src/com/android/inputmethod/keyboard/KeyDetector.java b/java/src/com/android/inputmethod/keyboard/KeyDetector.java index 03d9defa0..87368d4ef 100644 --- a/java/src/com/android/inputmethod/keyboard/KeyDetector.java +++ b/java/src/com/android/inputmethod/keyboard/KeyDetector.java @@ -86,6 +86,9 @@ public class KeyDetector { * @return the key that the touch point hits. */ public Key detectHitKey(final int x, final int y) { + if (mKeyboard == null) { + return null; + } final int touchX = getTouchX(x); final int touchY = getTouchY(y); diff --git a/java/src/com/android/inputmethod/keyboard/KeyboardSwitcher.java b/java/src/com/android/inputmethod/keyboard/KeyboardSwitcher.java index 6215e2710..2711d249b 100644 --- a/java/src/com/android/inputmethod/keyboard/KeyboardSwitcher.java +++ b/java/src/com/android/inputmethod/keyboard/KeyboardSwitcher.java @@ -19,7 +19,6 @@ package com.android.inputmethod.keyboard; import android.content.Context; import android.content.SharedPreferences; import android.content.res.Resources; -import android.graphics.Paint; import android.preference.PreferenceManager; import android.util.Log; import android.view.ContextThemeWrapper; @@ -31,7 +30,7 @@ import com.android.inputmethod.accessibility.AccessibleKeyboardViewProxy; import com.android.inputmethod.compat.InputMethodServiceCompatUtils; import com.android.inputmethod.keyboard.KeyboardLayoutSet.KeyboardLayoutSetException; import com.android.inputmethod.keyboard.internal.KeyboardState; -import com.android.inputmethod.latin.Constants; +import com.android.inputmethod.keyboard.internal.KeyboardTextsSet; import com.android.inputmethod.latin.InputView; import com.android.inputmethod.latin.LatinIME; import com.android.inputmethod.latin.LatinImeLogger; @@ -46,7 +45,7 @@ import com.android.inputmethod.latin.utils.ResourceUtils; public final class KeyboardSwitcher implements KeyboardState.SwitchActions { private static final String TAG = KeyboardSwitcher.class.getSimpleName(); - static final class KeyboardTheme { + public static final class KeyboardTheme { public final int mThemeId; public final int mStyleId; @@ -81,8 +80,9 @@ public final class KeyboardSwitcher implements KeyboardState.SwitchActions { private KeyboardState mState; private KeyboardLayoutSet mKeyboardLayoutSet; + // TODO: The following {@link KeyboardTextsSet} should be in {@link KeyboardLayoutSet}. + private final KeyboardTextsSet mKeyboardTextsSet = new KeyboardTextsSet(); private SettingsValues mCurrentSettingsValues; - private Key mSwitchToAlphaKey; /** mIsAutoCorrectionActive indicates that auto corrected word will be input instead of * what user actually typed. */ @@ -163,8 +163,7 @@ public final class KeyboardSwitcher implements KeyboardState.SwitchActions { mCurrentSettingsValues = settingsValues; try { mState.onLoadKeyboard(); - final Keyboard symbols = mKeyboardLayoutSet.getKeyboard(KeyboardId.ELEMENT_SYMBOLS); - mSwitchToAlphaKey = symbols.getKey(Constants.CODE_SWITCH_ALPHA_SYMBOL); + mKeyboardTextsSet.setLocale(mSubtypeSwitcher.getCurrentSubtypeLocale()); } catch (KeyboardLayoutSetException e) { Log.w(TAG, "loading keyboard failed: " + e.mKeyboardId, e.getCause()); LatinImeLogger.logOnException(e.mKeyboardId.toString(), e.getCause()); @@ -290,10 +289,9 @@ public final class KeyboardSwitcher implements KeyboardState.SwitchActions { @Override public void setEmojiKeyboard() { mMainKeyboardFrame.setVisibility(View.GONE); - final Paint paint = mKeyboardView.newLabelPaint(mSwitchToAlphaKey); mEmojiPalettesView.startEmojiPalettes( - mSwitchToAlphaKey.getLabel(), paint.getColor(), paint.getTextSize(), - paint.getTypeface()); + mKeyboardTextsSet.getText(KeyboardTextsSet.SWITCH_TO_ALPHA_KEY_LABEL), + mKeyboardView.getKeyVisualAttribute()); mEmojiPalettesView.setVisibility(View.VISIBLE); } @@ -342,10 +340,6 @@ public final class KeyboardSwitcher implements KeyboardState.SwitchActions { mState.onCodeInput(code, mLatinIME.getCurrentAutoCapsState()); } - private boolean isShowingMainKeyboard() { - return null != mKeyboardView && mKeyboardView.isShown(); - } - public boolean isShowingEmojiPalettes() { return mEmojiPalettesView != null && mEmojiPalettesView.isShown(); } @@ -378,10 +372,6 @@ public final class KeyboardSwitcher implements KeyboardState.SwitchActions { } } - public boolean isShowingMainKeyboardOrEmojiPalettes() { - return isShowingMainKeyboard() || isShowingEmojiPalettes(); - } - public View onCreateInputView(final boolean isHardwareAcceleratedDrawingEnabled) { if (mKeyboardView != null) { mKeyboardView.closing(); diff --git a/java/src/com/android/inputmethod/keyboard/KeyboardView.java b/java/src/com/android/inputmethod/keyboard/KeyboardView.java index dd3ab9cce..18e51d392 100644 --- a/java/src/com/android/inputmethod/keyboard/KeyboardView.java +++ b/java/src/com/android/inputmethod/keyboard/KeyboardView.java @@ -146,6 +146,10 @@ public class KeyboardView extends View { mPaint.setAntiAlias(true); } + public KeyVisualAttributes getKeyVisualAttribute() { + return mKeyVisualAttributes; + } + private static void blendAlpha(final Paint paint, final int alpha) { final int color = paint.getColor(); paint.setARGB((paint.getAlpha() * alpha) / Constants.Color.ALPHA_OPAQUE, diff --git a/java/src/com/android/inputmethod/keyboard/internal/EmojiLayoutParams.java b/java/src/com/android/inputmethod/keyboard/internal/EmojiLayoutParams.java index 12e063261..d57ea5a94 100644 --- a/java/src/com/android/inputmethod/keyboard/internal/EmojiLayoutParams.java +++ b/java/src/com/android/inputmethod/keyboard/internal/EmojiLayoutParams.java @@ -73,9 +73,13 @@ public class EmojiLayoutParams { ll.setLayoutParams(lp); } + public int getActionBarHeight() { + return mEmojiActionBarHeight - mBottomPadding; + } + public void setActionBarProperties(final LinearLayout ll) { final LinearLayout.LayoutParams lp = (LinearLayout.LayoutParams) ll.getLayoutParams(); - lp.height = mEmojiActionBarHeight - mBottomPadding; + lp.height = getActionBarHeight(); ll.setLayoutParams(lp); } diff --git a/java/src/com/android/inputmethod/keyboard/internal/KeyDrawParams.java b/java/src/com/android/inputmethod/keyboard/internal/KeyDrawParams.java index b528b692e..1716fa049 100644 --- a/java/src/com/android/inputmethod/keyboard/internal/KeyDrawParams.java +++ b/java/src/com/android/inputmethod/keyboard/internal/KeyDrawParams.java @@ -16,7 +16,6 @@ package com.android.inputmethod.keyboard.internal; -import android.content.res.ColorStateList; import android.graphics.Typeface; import com.android.inputmethod.latin.utils.ResourceUtils; @@ -33,7 +32,7 @@ public final class KeyDrawParams { public int mHintLabelSize; public int mPreviewTextSize; - public ColorStateList mTextColorStateList; + public int mTextColor; public int mTextInactivatedColor; public int mTextShadowColor; public int mHintLetterColor; @@ -58,7 +57,7 @@ public final class KeyDrawParams { mHintLabelSize = copyFrom.mHintLabelSize; mPreviewTextSize = copyFrom.mPreviewTextSize; - mTextColorStateList = copyFrom.mTextColorStateList; + mTextColor = copyFrom.mTextColor; mTextInactivatedColor = copyFrom.mTextInactivatedColor; mTextShadowColor = copyFrom.mTextShadowColor; mHintLetterColor = copyFrom.mHintLetterColor; @@ -90,8 +89,8 @@ public final class KeyDrawParams { attr.mShiftedLetterHintRatio, mShiftedLetterHintSize); mHintLabelSize = selectTextSize(keyHeight, attr.mHintLabelRatio, mHintLabelSize); mPreviewTextSize = selectTextSize(keyHeight, attr.mPreviewTextRatio, mPreviewTextSize); - mTextColorStateList = - attr.mTextColorStateList != null ? attr.mTextColorStateList : mTextColorStateList; + + mTextColor = selectColor(attr.mTextColor, mTextColor); mTextInactivatedColor = selectColor(attr.mTextInactivatedColor, mTextInactivatedColor); mTextShadowColor = selectColor(attr.mTextShadowColor, mTextShadowColor); mHintLetterColor = selectColor(attr.mHintLetterColor, mHintLetterColor); diff --git a/java/src/com/android/inputmethod/keyboard/internal/KeyVisualAttributes.java b/java/src/com/android/inputmethod/keyboard/internal/KeyVisualAttributes.java index c3e0aa685..df386fce4 100644 --- a/java/src/com/android/inputmethod/keyboard/internal/KeyVisualAttributes.java +++ b/java/src/com/android/inputmethod/keyboard/internal/KeyVisualAttributes.java @@ -16,7 +16,6 @@ package com.android.inputmethod.keyboard.internal; -import android.content.res.ColorStateList; import android.content.res.TypedArray; import android.graphics.Typeface; import android.util.SparseIntArray; @@ -38,7 +37,7 @@ public final class KeyVisualAttributes { public final float mHintLabelRatio; public final float mPreviewTextRatio; - public final ColorStateList mTextColorStateList; + public final int mTextColor; public final int mTextInactivatedColor; public final int mTextShadowColor; public final int mHintLetterColor; @@ -119,7 +118,7 @@ public final class KeyVisualAttributes { mPreviewTextRatio = ResourceUtils.getFraction(keyAttr, R.styleable.Keyboard_Key_keyPreviewTextRatio); - mTextColorStateList = keyAttr.getColorStateList(R.styleable.Keyboard_Key_keyTextColor); + mTextColor = keyAttr.getColor(R.styleable.Keyboard_Key_keyTextColor, 0); mTextInactivatedColor = keyAttr.getColor( R.styleable.Keyboard_Key_keyTextInactivatedColor, 0); mTextShadowColor = keyAttr.getColor(R.styleable.Keyboard_Key_keyTextShadowColor, 0); diff --git a/java/src/com/android/inputmethod/keyboard/internal/KeyboardIconsSet.java b/java/src/com/android/inputmethod/keyboard/internal/KeyboardIconsSet.java index da8bf7d69..79d088f2e 100644 --- a/java/src/com/android/inputmethod/keyboard/internal/KeyboardIconsSet.java +++ b/java/src/com/android/inputmethod/keyboard/internal/KeyboardIconsSet.java @@ -102,7 +102,7 @@ public final class KeyboardIconsSet { return isValidIconId(iconId) ? ICON_NAMES[iconId] : "unknown<" + iconId + ">"; } - static int getIconId(final String name) { + public static int getIconId(final String name) { Integer iconId = sNameToIdsMap.get(name); if (iconId != null) { return iconId; diff --git a/java/src/com/android/inputmethod/keyboard/internal/KeyboardTextsSet.java b/java/src/com/android/inputmethod/keyboard/internal/KeyboardTextsSet.java index 89221ba24..976038c10 100644 --- a/java/src/com/android/inputmethod/keyboard/internal/KeyboardTextsSet.java +++ b/java/src/com/android/inputmethod/keyboard/internal/KeyboardTextsSet.java @@ -29,6 +29,8 @@ import java.util.Locale; public final class KeyboardTextsSet { public static final String PREFIX_TEXT = "!text/"; + public static final String SWITCH_TO_ALPHA_KEY_LABEL = "label_to_alpha_key"; + private static final char BACKSLASH = Constants.CODE_BACKSLASH; private static final int MAX_STRING_REFERENCE_INDIRECTION = 10; diff --git a/java/src/com/android/inputmethod/keyboard/internal/KeyboardTextsTable.java b/java/src/com/android/inputmethod/keyboard/internal/KeyboardTextsTable.java index 96acb1551..ed2db07a8 100644 --- a/java/src/com/android/inputmethod/keyboard/internal/KeyboardTextsTable.java +++ b/java/src/com/android/inputmethod/keyboard/internal/KeyboardTextsTable.java @@ -90,7 +90,7 @@ public final class KeyboardTextsTable { /* 8:20 */ "more_keys_for_s", /* 9:20 */ "more_keys_for_n", /* 10:20 */ "label_to_alpha_key", - /* 11:15 */ "more_keys_for_y", + /* 11:14 */ "more_keys_for_y", /* 12:13 */ "more_keys_for_d", /* 13:12 */ "more_keys_for_z", /* 14:10 */ "more_keys_for_t", @@ -3585,7 +3585,7 @@ public final class KeyboardTextsTable { private static final Object[] LANGUAGES_AND_TEXTS = { // "locale", TEXT_ARRAY, /* numberOfNonNullText/lengthOf_TEXT_ARRAY localeName */ "DEFAULT", LANGUAGE_DEFAULT, /* 171/171 default */ - "af", LANGUAGE_af, /* 8/ 12 Afrikaans */ + "af", LANGUAGE_af, /* 7/ 12 Afrikaans */ "ar", LANGUAGE_ar, /* 58/110 Arabic */ "az", LANGUAGE_az_AZ, /* 8/ 17 Azerbaijani (Azerbaijan) */ "be", LANGUAGE_be_BY, /* 10/ 33 Belarusian (Belarus) */ diff --git a/java/src/com/android/inputmethod/latin/AbstractDictionaryWriter.java b/java/src/com/android/inputmethod/latin/AbstractDictionaryWriter.java deleted file mode 100644 index 1c6a14efe..000000000 --- a/java/src/com/android/inputmethod/latin/AbstractDictionaryWriter.java +++ /dev/null @@ -1,72 +0,0 @@ -/* - * Copyright (C) 2013 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.inputmethod.latin; - -import android.util.Log; - -import com.android.inputmethod.latin.makedict.DictEncoder; -import com.android.inputmethod.latin.makedict.UnsupportedFormatException; -import com.android.inputmethod.latin.makedict.Ver4DictEncoder; -import com.android.inputmethod.latin.utils.FileUtils; - -import java.io.File; -import java.io.IOException; -import java.util.Map; - -abstract public class AbstractDictionaryWriter { - /** Used for Log actions from this class */ - private static final String TAG = AbstractDictionaryWriter.class.getSimpleName(); - - public AbstractDictionaryWriter() { - } - - 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 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, - final int frequency, final boolean isValid, final long lastModifiedTime); - - abstract public void removeBigramWords(final String word0, final String word1); - - abstract protected void writeDictionary(final DictEncoder dictEncoder, - final Map<String, String> attributeMap) throws IOException, UnsupportedFormatException; - - public void write(final File file, final Map<String, String> attributeMap) { - try { - FileUtils.deleteRecursively(file); - file.mkdir(); - final DictEncoder dictEncoder = new Ver4DictEncoder(file); - writeDictionary(dictEncoder, attributeMap); - } catch (IOException e) { - Log.e(TAG, "IO exception while writing file", e); - } catch (UnsupportedFormatException e) { - Log.e(TAG, "Unsupported format", e); - } - } -} diff --git a/java/src/com/android/inputmethod/latin/BinaryDictionary.java b/java/src/com/android/inputmethod/latin/BinaryDictionary.java index c450a1d4f..851ecc042 100644 --- a/java/src/com/android/inputmethod/latin/BinaryDictionary.java +++ b/java/src/com/android/inputmethod/latin/BinaryDictionary.java @@ -28,9 +28,9 @@ import com.android.inputmethod.latin.makedict.FormatSpec; import com.android.inputmethod.latin.makedict.FusionDictionary.DictionaryOptions; import com.android.inputmethod.latin.makedict.UnsupportedFormatException; import com.android.inputmethod.latin.makedict.WordProperty; +import com.android.inputmethod.latin.personalization.PersonalizationHelper; import com.android.inputmethod.latin.settings.NativeSuggestOptions; import com.android.inputmethod.latin.utils.CollectionUtils; -import com.android.inputmethod.latin.utils.FileUtils; import com.android.inputmethod.latin.utils.JniUtils; import com.android.inputmethod.latin.utils.LanguageModelParam; import com.android.inputmethod.latin.utils.StringUtils; @@ -508,7 +508,9 @@ public final class BinaryDictionary extends Dictionary { */ @UsedForTesting public static int setCurrentTimeForTest(final int currentTime) { - return setCurrentTimeForTestNative(currentTime); + final int currentNativeTimestamp = setCurrentTimeForTestNative(currentTime); + PersonalizationHelper.currentTimeChangedForTesting(currentNativeTimestamp); + return currentNativeTimestamp; } @UsedForTesting diff --git a/java/src/com/android/inputmethod/latin/BinaryDictionaryGetter.java b/java/src/com/android/inputmethod/latin/BinaryDictionaryGetter.java index a7008379f..acbd919cd 100644 --- a/java/src/com/android/inputmethod/latin/BinaryDictionaryGetter.java +++ b/java/src/com/android/inputmethod/latin/BinaryDictionaryGetter.java @@ -229,7 +229,7 @@ final public class BinaryDictionaryGetter { private static boolean hackCanUseDictionaryFile(final Locale locale, final File f) { try { // Read the version of the file - final DictDecoder dictDecoder = FormatSpec.getDictDecoder(f); + final DictDecoder dictDecoder = FormatSpec.getDictDecoder(f, 0, f.length()); final DictionaryHeader header = dictDecoder.readHeader(); final String version = header.mDictionaryOptions.mAttributes.get(VERSION_KEY); diff --git a/java/src/com/android/inputmethod/latin/ContactsBinaryDictionary.java b/java/src/com/android/inputmethod/latin/ContactsBinaryDictionary.java index ae9bdf3fc..c2941e424 100644 --- a/java/src/com/android/inputmethod/latin/ContactsBinaryDictionary.java +++ b/java/src/com/android/inputmethod/latin/ContactsBinaryDictionary.java @@ -78,7 +78,7 @@ public class ContactsBinaryDictionary extends ExpandableBinaryDictionary { public ContactsBinaryDictionary(final Context context, final Locale locale, final File dictFile) { super(context, getDictName(NAME, locale, dictFile), locale, Dictionary.TYPE_CONTACTS, - false /* isUpdatable */, dictFile); + dictFile); mLocale = locale; mUseFirstLastBigrams = useFirstLastBigramsForLocale(locale); registerObserver(context); @@ -114,14 +114,14 @@ public class ContactsBinaryDictionary extends ExpandableBinaryDictionary { } @Override - public void loadDictionaryAsync() { - loadDeviceAccountsEmailAddresses(); - loadDictionaryAsyncForUri(ContactsContract.Profile.CONTENT_URI); + public void loadInitialContentsLocked() { + loadDeviceAccountsEmailAddressesLocked(); + loadDictionaryForUriLocked(ContactsContract.Profile.CONTENT_URI); // TODO: Switch this URL to the newer ContactsContract too - loadDictionaryAsyncForUri(Contacts.CONTENT_URI); + loadDictionaryForUriLocked(Contacts.CONTENT_URI); } - private void loadDeviceAccountsEmailAddresses() { + private void loadDeviceAccountsEmailAddressesLocked() { final List<String> accountVocabulary = AccountUtils.getDeviceAccountsEmailAddresses(mContext); if (accountVocabulary == null || accountVocabulary.isEmpty()) { @@ -131,12 +131,14 @@ public class ContactsBinaryDictionary extends ExpandableBinaryDictionary { if (DEBUG) { Log.d(TAG, "loadAccountVocabulary: " + word); } - super.addWord(word, null /* shortcut */, FREQUENCY_FOR_CONTACTS, 0 /* shortcutFreq */, - false /* isNotAWord */); + runGCIfRequiredLocked(true /* mindsBlockByGC */); + addWordDynamicallyLocked(word, FREQUENCY_FOR_CONTACTS, null /* shortcut */, + 0 /* shortcutFreq */, false /* isNotAWord */, false /* isBlacklisted */, + BinaryDictionary.NOT_A_VALID_TIMESTAMP); } } - private void loadDictionaryAsyncForUri(final Uri uri) { + private void loadDictionaryForUriLocked(final Uri uri) { Cursor cursor = null; try { cursor = mContext.getContentResolver().query(uri, PROJECTION, null, null, null); @@ -145,7 +147,7 @@ public class ContactsBinaryDictionary extends ExpandableBinaryDictionary { } if (cursor.moveToFirst()) { sContactCountAtLastRebuild = getContactCount(); - addWords(cursor); + addWordsLocked(cursor); } } catch (final SQLiteException e) { Log.e(TAG, "SQLiteException in the remote Contacts process.", e); @@ -166,12 +168,12 @@ public class ContactsBinaryDictionary extends ExpandableBinaryDictionary { return false; } - private void addWords(final Cursor cursor) { + private void addWordsLocked(final Cursor cursor) { int count = 0; while (!cursor.isAfterLast() && count < MAX_CONTACT_COUNT) { String name = cursor.getString(INDEX_NAME); if (isValidName(name)) { - addName(name); + addNameLocked(name); ++count; } else { if (DEBUG_DUMP) { @@ -207,7 +209,7 @@ public class ContactsBinaryDictionary extends ExpandableBinaryDictionary { * Adds the words in a name (e.g., firstname/lastname) to the binary dictionary along with their * bigrams depending on locale. */ - private void addName(final String name) { + private void addNameLocked(final String name) { int len = StringUtils.codePointCount(name); String prevWord = null; // TODO: Better tokenization for non-Latin writing systems @@ -226,13 +228,15 @@ public class ContactsBinaryDictionary extends ExpandableBinaryDictionary { if (DEBUG) { Log.d(TAG, "addName " + name + ", " + word + ", " + prevWord); } - super.addWord(word, null /* shortcut */, FREQUENCY_FOR_CONTACTS, - 0 /* shortcutFreq */, false /* isNotAWord */); - if (!TextUtils.isEmpty(prevWord)) { - if (mUseFirstLastBigrams) { - super.addBigram(prevWord, word, FREQUENCY_FOR_CONTACTS_BIGRAM, - 0 /* lastModifiedTime */); - } + runGCIfRequiredLocked(true /* mindsBlockByGC */); + addWordDynamicallyLocked(word, FREQUENCY_FOR_CONTACTS, + null /* shortcut */, 0 /* shortcutFreq */, false /* isNotAWord */, + false /* isBlacklisted */, BinaryDictionary.NOT_A_VALID_TIMESTAMP); + if (!TextUtils.isEmpty(prevWord) && mUseFirstLastBigrams) { + runGCIfRequiredLocked(true /* mindsBlockByGC */); + addBigramDynamicallyLocked(prevWord, word, + FREQUENCY_FOR_CONTACTS_BIGRAM, + BinaryDictionary.NOT_A_VALID_TIMESTAMP); } prevWord = word; } @@ -258,12 +262,12 @@ public class ContactsBinaryDictionary extends ExpandableBinaryDictionary { } @Override - protected boolean needsToReloadBeforeWriting() { + protected boolean needsToReloadAfterCreation() { return true; } @Override - protected boolean hasContentChanged() { + protected boolean haveContentsChanged() { final long startTime = SystemClock.uptimeMillis(); final int contactCount = getContactCount(); if (contactCount > MAX_CONTACT_COUNT) { @@ -291,7 +295,7 @@ public class ContactsBinaryDictionary extends ExpandableBinaryDictionary { if (cursor.moveToFirst()) { while (!cursor.isAfterLast()) { String name = cursor.getString(INDEX_NAME); - if (isValidName(name) && !isNameInDictionary(name)) { + if (isValidName(name) && !isNameInDictionaryLocked(name)) { if (DEBUG) { Log.d(TAG, "Contact name missing: " + name + " (runtime = " + (SystemClock.uptimeMillis() - startTime) + " ms)"); @@ -321,7 +325,7 @@ public class ContactsBinaryDictionary extends ExpandableBinaryDictionary { /** * Checks if the words in a name are in the current binary dictionary. */ - private boolean isNameInDictionary(final String name) { + private boolean isNameInDictionaryLocked(final String name) { int len = StringUtils.codePointCount(name); String prevWord = null; for (int i = 0; i < len; i++) { @@ -332,11 +336,11 @@ public class ContactsBinaryDictionary extends ExpandableBinaryDictionary { final int wordLen = StringUtils.codePointCount(word); if (wordLen < MAX_WORD_LENGTH && wordLen > 1) { if (!TextUtils.isEmpty(prevWord) && mUseFirstLastBigrams) { - if (!super.isValidBigramLocked(prevWord, word)) { + if (!isValidBigramLocked(prevWord, word)) { return false; } } else { - if (!super.isValidWordLocked(word)) { + if (!isValidWordLocked(word)) { return false; } } diff --git a/java/src/com/android/inputmethod/latin/DictionaryFacilitatorForSuggest.java b/java/src/com/android/inputmethod/latin/DictionaryFacilitatorForSuggest.java index 14e1baf41..d6178fcee 100644 --- a/java/src/com/android/inputmethod/latin/DictionaryFacilitatorForSuggest.java +++ b/java/src/com/android/inputmethod/latin/DictionaryFacilitatorForSuggest.java @@ -28,6 +28,7 @@ import com.android.inputmethod.latin.personalization.PersonalizationHelper; import com.android.inputmethod.latin.personalization.UserHistoryDictionary; import com.android.inputmethod.latin.settings.SettingsValues; import com.android.inputmethod.latin.utils.CollectionUtils; +import com.android.inputmethod.latin.utils.ExecutorUtils; import com.android.inputmethod.latin.utils.LanguageModelParam; import java.io.File; @@ -35,6 +36,7 @@ import java.util.ArrayList; import java.util.HashMap; import java.util.HashSet; import java.util.Locale; +import java.util.Map; import java.util.Set; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.CountDownLatch; @@ -137,7 +139,8 @@ public class DictionaryFacilitatorForSuggest { @UsedForTesting public DictionaryFacilitatorForSuggest(final Context context, final Locale locale, - final ArrayList<String> dictionaryTypes, final HashMap<String, File> dictionaryFiles) { + final ArrayList<String> dictionaryTypes, final HashMap<String, File> dictionaryFiles, + final Map<String, Map<String, String>> additionalDictAttributes) { mContext = context; mLocale = locale; mLatchForWaitingLoadingMainDictionary = new CountDownLatch(0); @@ -154,6 +157,10 @@ public class DictionaryFacilitatorForSuggest { userHistoryDictionary.reloadDictionaryIfRequired(); userHistoryDictionary.waitAllTasksForTests(); setUserHistoryDictionary(userHistoryDictionary); + if (additionalDictAttributes.containsKey(dictType)) { + userHistoryDictionary.clearAndFlushDictionaryWithAdditionalAttributes( + additionalDictAttributes.get(dictType)); + } } else if (dictType.equals(Dictionary.TYPE_PERSONALIZATION)) { final PersonalizationDictionary personalizationDictionary = PersonalizationHelper.getPersonalizationDictionary(context, locale); @@ -162,6 +169,10 @@ public class DictionaryFacilitatorForSuggest { personalizationDictionary.reloadDictionaryIfRequired(); personalizationDictionary.waitAllTasksForTests(); setPersonalizationDictionary(personalizationDictionary); + if (additionalDictAttributes.containsKey(dictType)) { + personalizationDictionary.clearAndFlushDictionaryWithAdditionalAttributes( + additionalDictAttributes.get(dictType)); + } } else if (dictType.equals(Dictionary.TYPE_USER)) { final File file = dictionaryFiles.get(dictType); final UserBinaryDictionary userDictionary = new UserBinaryDictionary( @@ -204,8 +215,7 @@ public class DictionaryFacilitatorForSuggest { if (listener != null) { listener.onUpdateMainDictionaryAvailability(hasMainDictionary()); } - new Thread("InitializeBinaryDictionary") { - @Override + ExecutorUtils.getExecutor("InitializeBinaryDictionary").execute(new Runnable() { public void run() { final DictionaryCollection newMainDict = DictionaryFactory.createMainDictionaryFromManager(context, locale); @@ -215,7 +225,7 @@ public class DictionaryFacilitatorForSuggest { } mLatchForWaitingLoadingMainDictionary.countDown(); } - }.start(); + }); } // The main dictionary could have been loaded asynchronously. Don't cache the return value diff --git a/java/src/com/android/inputmethod/latin/DictionaryWriter.java b/java/src/com/android/inputmethod/latin/DictionaryWriter.java deleted file mode 100644 index b931c66d1..000000000 --- a/java/src/com/android/inputmethod/latin/DictionaryWriter.java +++ /dev/null @@ -1,92 +0,0 @@ -/* - * Copyright (C) 2013 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.inputmethod.latin; - -import com.android.inputmethod.latin.makedict.DictEncoder; -import com.android.inputmethod.latin.makedict.FormatSpec; -import com.android.inputmethod.latin.makedict.FusionDictionary; -import com.android.inputmethod.latin.makedict.FusionDictionary.PtNodeArray; -import com.android.inputmethod.latin.makedict.FusionDictionary.WeightedString; -import com.android.inputmethod.latin.makedict.ProbabilityInfo; -import com.android.inputmethod.latin.makedict.UnsupportedFormatException; -import com.android.inputmethod.latin.utils.CollectionUtils; - -import java.io.IOException; -import java.util.ArrayList; -import java.util.HashMap; -import java.util.Map; - -/** - * An in memory dictionary for memorizing entries and writing a binary dictionary. - */ -public class DictionaryWriter extends AbstractDictionaryWriter { - private static final int BINARY_DICT_VERSION = FormatSpec.VERSION4; - private static final FormatSpec.FormatOptions FORMAT_OPTIONS = - new FormatSpec.FormatOptions(BINARY_DICT_VERSION, false /* hasTimestamp */); - - private FusionDictionary mFusionDictionary; - - public DictionaryWriter() { - clear(); - } - - @Override - public void clear() { - final HashMap<String, String> attributes = CollectionUtils.newHashMap(); - mFusionDictionary = new FusionDictionary(new PtNodeArray(), - new FusionDictionary.DictionaryOptions(attributes)); - } - - /** - * Adds a word unigram to the fusion dictionary. - */ - // TODO: Create "cache dictionary" to cache fresh words for frequently updated dictionaries, - // considering performance regression. - @Override - public void addUnigramWord(final String word, final String shortcutTarget, - final int probability, final int shortcutProbability, final boolean isNotAWord) { - if (shortcutTarget == null) { - mFusionDictionary.add(word, new ProbabilityInfo(probability), 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, shortcutProbability)); - mFusionDictionary.add(word, new ProbabilityInfo(probability), shortcutTargets, - isNotAWord); - } - } - - @Override - public void addBigramWords(final String word0, final String word1, final int probability, - final boolean isValid, final long lastModifiedTime) { - mFusionDictionary.setBigram(word0, word1, new ProbabilityInfo(probability)); - } - - @Override - public void removeBigramWords(final String word0, final String word1) { - // This class don't support removing bigram words. - } - - @Override - protected void writeDictionary(final DictEncoder dictEncoder, - final Map<String, String> attributeMap) throws IOException, UnsupportedFormatException { - for (final Map.Entry<String, String> entry : attributeMap.entrySet()) { - mFusionDictionary.addOptionAttribute(entry.getKey(), entry.getValue()); - } - dictEncoder.writeDictionary(mFusionDictionary, FORMAT_OPTIONS); - } -} diff --git a/java/src/com/android/inputmethod/latin/ExpandableBinaryDictionary.java b/java/src/com/android/inputmethod/latin/ExpandableBinaryDictionary.java index f9ab9419b..26545acbd 100644 --- a/java/src/com/android/inputmethod/latin/ExpandableBinaryDictionary.java +++ b/java/src/com/android/inputmethod/latin/ExpandableBinaryDictionary.java @@ -29,9 +29,9 @@ import com.android.inputmethod.latin.SuggestedWords.SuggestedWordInfo; import com.android.inputmethod.latin.utils.AsyncResultHolder; import com.android.inputmethod.latin.utils.CollectionUtils; import com.android.inputmethod.latin.utils.CombinedFormatUtils; +import com.android.inputmethod.latin.utils.ExecutorUtils; import com.android.inputmethod.latin.utils.FileUtils; import com.android.inputmethod.latin.utils.LanguageModelParam; -import com.android.inputmethod.latin.utils.PrioritizedSerialExecutor; import java.io.File; import java.util.ArrayList; @@ -64,6 +64,9 @@ abstract public class ExpandableBinaryDictionary extends Dictionary { private static final int TIMEOUT_FOR_READ_OPS_IN_MILLISECONDS = 100; private static final int TIMEOUT_FOR_READ_OPS_FOR_TESTS_IN_MILLISECONDS = 10000; + private static final int DEFAULT_MAX_UNIGRAM_COUNT = 10000; + private static final int DEFAULT_MAX_BIGRAM_COUNT = 10000; + /** * The maximum length of a word in this dictionary. */ @@ -80,9 +83,6 @@ abstract public class ExpandableBinaryDictionary extends Dictionary { private static final ConcurrentHashMap<String, DictionaryUpdateController> sDictNameDictionaryUpdateControllerMap = CollectionUtils.newConcurrentHashMap(); - private static final ConcurrentHashMap<String, PrioritizedSerialExecutor> - sDictNameExecutorMap = CollectionUtils.newConcurrentHashMap(); - /** The application context. */ protected final Context mContext; @@ -92,10 +92,6 @@ abstract public class ExpandableBinaryDictionary extends Dictionary { */ private BinaryDictionary mBinaryDictionary; - // TODO: Remove and handle dictionaries in native code. - /** The in-memory dictionary used to generate the binary dictionary. */ - protected AbstractDictionaryWriter mDictionaryWriter; - /** * The name of this dictionary, used as a part of the filename for storing the binary * dictionary. Multiple dictionary instances with the same name is supported, with access @@ -106,9 +102,6 @@ abstract public class ExpandableBinaryDictionary extends Dictionary { /** Dictionary locale */ private final Locale mLocale; - /** Whether to support dynamically updating the dictionary */ - private final boolean mIsUpdatable; - /** Dictionary file */ private final File mDictFile; @@ -128,23 +121,22 @@ abstract public class ExpandableBinaryDictionary extends Dictionary { new AtomicReference<Runnable>(); /** - * Abstract method for loading the unigrams and bigrams of a given dictionary in a background - * thread. + * Abstract method for loading initial contents of a given dictionary. */ - protected abstract void loadDictionaryAsync(); + protected abstract void loadInitialContentsLocked(); /** - * Indicates that the source dictionary content has changed and a rebuild of the binary file is - * required. If it returns false, the next reload will only read the current binary dictionary - * from file. Note that the shared binary dictionary is locked when this is called. + * Indicates that the source dictionary contents have changed and a rebuild of the binary file + * is required. If it returns false, the next reload will only read the current binary + * dictionary from file. Note that the shared binary dictionary is locked when this is called. */ - protected abstract boolean hasContentChanged(); + protected abstract boolean haveContentsChanged(); private boolean matchesExpectedBinaryDictFormatVersionForThisType(final int formatVersion) { return formatVersion == FormatSpec.VERSION4; } - public boolean isValidDictionary() { + public boolean isValidDictionaryLocked() { return mBinaryDictionary.isValidDictionary(); } @@ -164,42 +156,6 @@ abstract public class ExpandableBinaryDictionary extends Dictionary { } /** - * Gets the executor for the given dictionary name. - */ - private static PrioritizedSerialExecutor getExecutor(final String dictName) { - PrioritizedSerialExecutor executor = sDictNameExecutorMap.get(dictName); - if (executor == null) { - synchronized(sDictNameExecutorMap) { - executor = new PrioritizedSerialExecutor(); - sDictNameExecutorMap.put(dictName, executor); - } - } - return executor; - } - - /** - * Shutdowns all executors and removes all executors from the executor map for testing. - */ - @UsedForTesting - public static void shutdownAllExecutors() { - synchronized(sDictNameExecutorMap) { - for (final PrioritizedSerialExecutor executor : sDictNameExecutorMap.values()) { - executor.shutdown(); - sDictNameExecutorMap.remove(executor); - } - } - } - - private static AbstractDictionaryWriter getDictionaryWriter( - final boolean isDynamicPersonalizationDictionary) { - if (isDynamicPersonalizationDictionary) { - return null; - } else { - return new DictionaryWriter(); - } - } - - /** * Creates a new expandable binary dictionary. * * @param context The application context of the parent. @@ -207,24 +163,18 @@ abstract public class ExpandableBinaryDictionary extends Dictionary { * name is supported. * @param locale the dictionary locale. * @param dictType the dictionary type, as a human-readable string - * @param isUpdatable whether to support dynamically updating the dictionary. Please note that - * dynamic dictionary has negative effects on memory space and computation time. * @param dictFile dictionary file path. if null, use default dictionary path based on * dictionary type. */ public ExpandableBinaryDictionary(final Context context, final String dictName, - final Locale locale, final String dictType, final boolean isUpdatable, - final File dictFile) { + final Locale locale, final String dictType, final File dictFile) { super(dictType); mDictName = dictName; mContext = context; mLocale = locale; - mIsUpdatable = isUpdatable; mDictFile = getDictFile(context, dictName, dictFile); mBinaryDictionary = null; mDictNameDictionaryUpdateController = getDictionaryUpdateController(dictName); - // Currently, only dynamic personalization dictionary is updatable. - mDictionaryWriter = getDictionaryWriter(isUpdatable); } public static File getDictFile(final Context context, final String dictName, @@ -243,20 +193,7 @@ abstract public class ExpandableBinaryDictionary extends Dictionary { */ @Override public void close() { - getExecutor(mDictName).execute(new Runnable() { - @Override - public void run() { - if (mBinaryDictionary!= null) { - mBinaryDictionary.close(); - mBinaryDictionary = null; - } - } - }); - } - - protected void closeBinaryDictionary() { - // Ensure that no other threads are accessing the local binary dictionary. - getExecutor(mDictName).execute(new Runnable() { + ExecutorUtils.getExecutor(mDictName).execute(new Runnable() { @Override public void run() { if (mBinaryDictionary != null) { @@ -273,6 +210,10 @@ abstract public class ExpandableBinaryDictionary extends Dictionary { attributeMap.put(DictionaryHeader.DICTIONARY_LOCALE_KEY, mLocale.toString()); attributeMap.put(DictionaryHeader.DICTIONARY_VERSION_KEY, String.valueOf(TimeUnit.MILLISECONDS.toSeconds(System.currentTimeMillis()))); + attributeMap.put(DictionaryHeader.MAX_UNIGRAM_COUNT_KEY, + String.valueOf(DEFAULT_MAX_UNIGRAM_COUNT)); + attributeMap.put(DictionaryHeader.MAX_BIGRAM_COUNT_KEY, + String.valueOf(DEFAULT_MAX_BIGRAM_COUNT)); return attributeMap; } @@ -286,65 +227,52 @@ abstract public class ExpandableBinaryDictionary extends Dictionary { mBinaryDictionary = null; } + private void createBinaryDictionaryLocked() { + BinaryDictionary.createEmptyDictFile(mDictFile.getAbsolutePath(), + DICTIONARY_FORMAT_VERSION, mLocale, getHeaderAttributeMap()); + } + + private void openBinaryDictionaryLocked() { + mBinaryDictionary = new BinaryDictionary( + mDictFile.getAbsolutePath(), 0 /* offset */, mDictFile.length(), + true /* useFullEditDistance */, mLocale, mDictType, true /* isUpdatable */); + } + protected void clear() { - getExecutor(mDictName).execute(new Runnable() { + ExecutorUtils.getExecutor(mDictName).execute(new Runnable() { @Override public void run() { - if (mDictionaryWriter == null) { - removeBinaryDictionaryLocked(); - BinaryDictionary.createEmptyDictFile(mDictFile.getAbsolutePath(), - DICTIONARY_FORMAT_VERSION, mLocale, getHeaderAttributeMap()); - mBinaryDictionary = new BinaryDictionary( - mDictFile.getAbsolutePath(), 0 /* offset */, mDictFile.length(), - true /* useFullEditDistance */, mLocale, mDictType, mIsUpdatable); - } else { - mDictionaryWriter.clear(); - } + removeBinaryDictionaryLocked(); + createBinaryDictionaryLocked(); + openBinaryDictionaryLocked(); } }); } /** - * 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 int shortcutFreq, final boolean isNotAWord) { - mDictionaryWriter.addUnigramWord(word, shortcutTarget, frequency, shortcutFreq, isNotAWord); - } - - /** - * Adds a word bigram in the dictionary. Used for loading a dictionary. - */ - protected void addBigram(final String prevWord, final String word, final int frequency, - final long lastModifiedTime) { - mDictionaryWriter.addBigramWords(prevWord, word, frequency, true /* isValid */, - lastModifiedTime); - } - - /** * Check whether GC is needed and run GC if required. */ protected void runGCIfRequired(final boolean mindsBlockByGC) { - getExecutor(mDictName).execute(new Runnable() { + ExecutorUtils.getExecutor(mDictName).execute(new Runnable() { @Override public void run() { - runGCIfRequiredInternalLocked(mindsBlockByGC); + runGCAfterAllPrioritizedTasksIfRequiredLocked(mindsBlockByGC); } }); } - private void runGCIfRequiredInternalLocked(final boolean mindsBlockByGC) { - // Calls to needsToRunGC() need to be serialized. + protected void runGCIfRequiredLocked(final boolean mindsBlockByGC) { + if (mBinaryDictionary.needsToRunGC(mindsBlockByGC)) { + mBinaryDictionary.flushWithGC(); + } + } + + private void runGCAfterAllPrioritizedTasksIfRequiredLocked(final boolean mindsBlockByGC) { + // needsToRunGC() have to be called with lock. if (mBinaryDictionary.needsToRunGC(mindsBlockByGC)) { if (setProcessingLargeTaskIfNot()) { // Run GC after currently existing time sensitive operations. - getExecutor(mDictName).executePrioritized(new Runnable() { + ExecutorUtils.getExecutor(mDictName).executePrioritized(new Runnable() { @Override public void run() { try { @@ -364,52 +292,50 @@ abstract public class ExpandableBinaryDictionary extends Dictionary { protected void addWordDynamically(final String word, final int frequency, final String shortcutTarget, final int shortcutFreq, final boolean isNotAWord, final boolean isBlacklisted, final int timestamp) { - if (!mIsUpdatable) { - Log.w(TAG, "addWordDynamically is called for non-updatable dictionary: " + mDictName); - return; - } - getExecutor(mDictName).execute(new Runnable() { + ExecutorUtils.getExecutor(mDictName).execute(new Runnable() { @Override public void run() { - runGCIfRequiredInternalLocked(true /* mindsBlockByGC */); - mBinaryDictionary.addUnigramWord(word, frequency, shortcutTarget, shortcutFreq, + runGCAfterAllPrioritizedTasksIfRequiredLocked(true /* mindsBlockByGC */); + addWordDynamicallyLocked(word, frequency, shortcutTarget, shortcutFreq, isNotAWord, isBlacklisted, timestamp); } }); } + protected void addWordDynamicallyLocked(final String word, final int frequency, + final String shortcutTarget, final int shortcutFreq, final boolean isNotAWord, + final boolean isBlacklisted, final int timestamp) { + mBinaryDictionary.addUnigramWord(word, frequency, shortcutTarget, shortcutFreq, + isNotAWord, isBlacklisted, timestamp); + } + /** * Dynamically adds a word bigram in the dictionary. May overwrite an existing entry. */ protected void addBigramDynamically(final String word0, final String word1, final int frequency, final int timestamp) { - if (!mIsUpdatable) { - Log.w(TAG, "addBigramDynamically is called for non-updatable dictionary: " - + mDictName); - return; - } - getExecutor(mDictName).execute(new Runnable() { + ExecutorUtils.getExecutor(mDictName).execute(new Runnable() { @Override public void run() { - runGCIfRequiredInternalLocked(true /* mindsBlockByGC */); - mBinaryDictionary.addBigramWords(word0, word1, frequency, timestamp); + runGCAfterAllPrioritizedTasksIfRequiredLocked(true /* mindsBlockByGC */); + addBigramDynamicallyLocked(word0, word1, frequency, timestamp); } }); } + protected void addBigramDynamicallyLocked(final String word0, final String word1, + final int frequency, final int timestamp) { + mBinaryDictionary.addBigramWords(word0, word1, frequency, timestamp); + } + /** * Dynamically remove a word bigram in the dictionary. */ protected void removeBigramDynamically(final String word0, final String word1) { - if (!mIsUpdatable) { - Log.w(TAG, "removeBigramDynamically is called for non-updatable dictionary: " - + mDictName); - return; - } - getExecutor(mDictName).execute(new Runnable() { + ExecutorUtils.getExecutor(mDictName).execute(new Runnable() { @Override public void run() { - runGCIfRequiredInternalLocked(true /* mindsBlockByGC */); + runGCAfterAllPrioritizedTasksIfRequiredLocked(true /* mindsBlockByGC */); mBinaryDictionary.removeBigramWords(word0, word1); } }); @@ -425,12 +351,7 @@ abstract public class ExpandableBinaryDictionary extends Dictionary { protected void addMultipleDictionaryEntriesDynamically( final ArrayList<LanguageModelParam> languageModelParams, final AddMultipleDictionaryEntriesCallback callback) { - if (!mIsUpdatable) { - Log.w(TAG, "addMultipleDictionaryEntriesDynamically is called for non-updatable " + - "dictionary: " + mDictName); - return; - } - getExecutor(mDictName).execute(new Runnable() { + ExecutorUtils.getExecutor(mDictName).execute(new Runnable() { @Override public void run() { final boolean locked = setProcessingLargeTaskIfNot(); @@ -461,7 +382,7 @@ abstract public class ExpandableBinaryDictionary extends Dictionary { } final AsyncResultHolder<ArrayList<SuggestedWordInfo>> holder = new AsyncResultHolder<ArrayList<SuggestedWordInfo>>(); - getExecutor(mDictName).executePrioritized(new Runnable() { + ExecutorUtils.getExecutor(mDictName).executePrioritized(new Runnable() { @Override public void run() { if (mBinaryDictionary == null) { @@ -492,15 +413,11 @@ abstract public class ExpandableBinaryDictionary extends Dictionary { @Override public boolean isValidWord(final String word) { reloadDictionaryIfRequired(); - return isValidWordInner(word); - } - - protected boolean isValidWordInner(final String word) { if (processingLargeTask()) { return false; } final AsyncResultHolder<Boolean> holder = new AsyncResultHolder<Boolean>(); - getExecutor(mDictName).executePrioritized(new Runnable() { + ExecutorUtils.getExecutor(mDictName).executePrioritized(new Runnable() { @Override public void run() { holder.set(isValidWordLocked(word)); @@ -532,7 +449,7 @@ abstract public class ExpandableBinaryDictionary extends Dictionary { * Loads the current binary dictionary from internal storage. Assumes the dictionary file * exists. */ - private void loadBinaryDictionary() { + private void loadBinaryDictionaryLocked() { if (DEBUG) { Log.d(TAG, "Loading binary dictionary: " + mDictName + " request=" + mDictNameDictionaryUpdateController.mLastUpdateRequestTime + " update=" @@ -548,65 +465,40 @@ abstract public class ExpandableBinaryDictionary extends Dictionary { } catch (InterruptedException e) { } } - - final String filename = mDictFile.getAbsolutePath(); - final long length = mDictFile.length(); - - // Build the new binary dictionary - final BinaryDictionary newBinaryDictionary = new BinaryDictionary(filename, 0 /* offset */, - length, true /* useFullEditDistance */, null, mDictType, mIsUpdatable); - - // Ensure all threads accessing the current dictionary have finished before - // swapping in the new one. - // TODO: Ensure multi-thread assignment of mBinaryDictionary. final BinaryDictionary oldBinaryDictionary = mBinaryDictionary; - getExecutor(mDictName).executePrioritized(new Runnable() { - @Override - public void run() { - mBinaryDictionary = newBinaryDictionary; - if (oldBinaryDictionary != null) { - oldBinaryDictionary.close(); - } - } - }); + openBinaryDictionaryLocked(); + if (oldBinaryDictionary != null) { + oldBinaryDictionary.close(); + } } /** * Abstract method for checking if it is required to reload the dictionary before writing * a binary dictionary. */ - abstract protected boolean needsToReloadBeforeWriting(); + abstract protected boolean needsToReloadAfterCreation(); /** - * Writes a new binary dictionary based on the contents of the fusion dictionary. + * Create a new binary dictionary and load initial contents. */ - private void writeBinaryDictionary() { + private void createNewDictionaryLocked() { if (DEBUG) { Log.d(TAG, "Generating binary dictionary: " + mDictName + " request=" + mDictNameDictionaryUpdateController.mLastUpdateRequestTime + " update=" + mDictNameDictionaryUpdateController.mLastUpdateTime); } - if (needsToReloadBeforeWriting()) { - mDictionaryWriter.clear(); - loadDictionaryAsync(); - mDictionaryWriter.write(mDictFile, getHeaderAttributeMap()); + removeBinaryDictionaryLocked(); + createBinaryDictionaryLocked(); + openBinaryDictionaryLocked(); + loadInitialContentsLocked(); + mBinaryDictionary.flushWithGC(); + } + + private void flushDictionaryLocked() { + if (mBinaryDictionary.needsToRunGC(false /* mindsBlockByGC */)) { + mBinaryDictionary.flushWithGC(); } else { - if (mBinaryDictionary == null || !isValidDictionary() - // TODO: remove the check below - || !matchesExpectedBinaryDictFormatVersionForThisType( - mBinaryDictionary.getFormatVersion())) { - if (mDictFile.exists() && !FileUtils.deleteRecursively(mDictFile)) { - Log.e(TAG, "Can't remove a file: " + mDictFile.getName()); - } - BinaryDictionary.createEmptyDictFile(mDictFile.getAbsolutePath(), - DICTIONARY_FORMAT_VERSION, mLocale, getHeaderAttributeMap()); - } else { - if (mBinaryDictionary.needsToRunGC(false /* mindsBlockByGC */)) { - mBinaryDictionary.flushWithGC(); - } else { - mBinaryDictionary.flush(); - } - } + mBinaryDictionary.flush(); } } @@ -662,57 +554,43 @@ abstract public class ExpandableBinaryDictionary extends Dictionary { private final void reloadDictionary() { // Ensure that only one thread attempts to read or write to the shared binary dictionary // file at the same time. - getExecutor(mDictName).execute(new Runnable() { + ExecutorUtils.getExecutor(mDictName).execute(new Runnable() { @Override public void run() { try { final long time = System.currentTimeMillis(); - final boolean dictionaryFileExists = dictionaryFileExists(); - if (mDictNameDictionaryUpdateController.isOutOfDate() - || !dictionaryFileExists) { - // If the shared dictionary file does not exist or is out of date, the - // first instance that acquires the lock will generate a new one. - if (hasContentChanged() || !dictionaryFileExists) { - // If the source content has changed or the dictionary does not exist, - // rebuild the binary dictionary. Empty dictionaries are supported (in - // the case where loadDictionaryAsync() adds nothing) in order to - // provide a uniform framework. - mDictNameDictionaryUpdateController.mLastUpdateTime = time; - writeBinaryDictionary(); - loadBinaryDictionary(); - } else { - // If not, the reload request was unnecessary so revert - // LastUpdateRequestTime to LastUpdateTime. - mDictNameDictionaryUpdateController.mLastUpdateRequestTime = - mDictNameDictionaryUpdateController.mLastUpdateTime; - } + final boolean openedDictIsOutOfDate = + mDictNameDictionaryUpdateController.isOutOfDate(); + if (!dictionaryFileExists() + || (openedDictIsOutOfDate && haveContentsChanged())) { + // If the shared dictionary file does not exist or is out of date and + // contents have been updated, the first instance that acquires the lock + // will generate a new one + mDictNameDictionaryUpdateController.mLastUpdateTime = time; + createNewDictionaryLocked(); + } else if (openedDictIsOutOfDate) { + // If not, the reload request was unnecessary so revert + // LastUpdateRequestTime to LastUpdateTime. + mDictNameDictionaryUpdateController.mLastUpdateRequestTime = + mDictNameDictionaryUpdateController.mLastUpdateTime; } else if (mBinaryDictionary == null || mPerInstanceDictionaryUpdateController.mLastUpdateTime < mDictNameDictionaryUpdateController.mLastUpdateTime) { // Otherwise, if the local dictionary is older than the shared dictionary, // load the shared dictionary. - loadBinaryDictionary(); + loadBinaryDictionaryLocked(); } - // If we just loaded the binary dictionary, then mBinaryDictionary is not - // up-to-date yet so it's useless to test it right away. Schedule the check - // for right after it's loaded instead. - getExecutor(mDictName).executePrioritized(new Runnable() { - @Override - public void run() { - if (mBinaryDictionary != null && !(isValidDictionary() - // TODO: remove the check below - && matchesExpectedBinaryDictFormatVersionForThisType( - mBinaryDictionary.getFormatVersion()))) { - // Binary dictionary or its format version is not valid. Regenerate - // the dictionary file. writeBinaryDictionary will remove the - // existing files if appropriate. - mDictNameDictionaryUpdateController.mLastUpdateTime = time; - writeBinaryDictionary(); - loadBinaryDictionary(); - } - mPerInstanceDictionaryUpdateController.mLastUpdateTime = time; - } - }); + if (mBinaryDictionary != null && !(isValidDictionaryLocked() + // TODO: remove the check below + && matchesExpectedBinaryDictFormatVersionForThisType( + mBinaryDictionary.getFormatVersion()))) { + // Binary dictionary or its format version is not valid. Regenerate + // the dictionary file. writeBinaryDictionary will remove the + // existing files if appropriate. + mDictNameDictionaryUpdateController.mLastUpdateTime = time; + createNewDictionaryLocked(); + } + mPerInstanceDictionaryUpdateController.mLastUpdateTime = time; } finally { mDictNameDictionaryUpdateController.mProcessingLargeTask.set(false); } @@ -726,17 +604,17 @@ abstract public class ExpandableBinaryDictionary extends Dictionary { } /** - * Generate binary dictionary using DictionaryWriter. + * Flush binary dictionary to dictionary file. */ protected void asyncFlushBinaryDictionary() { final Runnable newTask = new Runnable() { @Override public void run() { - writeBinaryDictionary(); + flushDictionaryLocked(); } }; final Runnable oldTask = mUnfinishedFlushingTask.getAndSet(newTask); - getExecutor(mDictName).replaceAndExecute(oldTask, newTask); + ExecutorUtils.getExecutor(mDictName).replaceAndExecute(oldTask, newTask); } /** @@ -757,7 +635,7 @@ abstract public class ExpandableBinaryDictionary extends Dictionary { @UsedForTesting public boolean isInUnderlyingBinaryDictionaryForTests(final String word) { final AsyncResultHolder<Boolean> holder = new AsyncResultHolder<Boolean>(); - getExecutor(mDictName).execute(new Runnable() { + ExecutorUtils.getExecutor(mDictName).execute(new Runnable() { @Override public void run() { if (mDictType == Dictionary.TYPE_USER_HISTORY) { @@ -771,7 +649,7 @@ abstract public class ExpandableBinaryDictionary extends Dictionary { @UsedForTesting public void waitAllTasksForTests() { final CountDownLatch countDownLatch = new CountDownLatch(1); - getExecutor(mDictName).execute(new Runnable() { + ExecutorUtils.getExecutor(mDictName).execute(new Runnable() { @Override public void run() { countDownLatch.countDown(); @@ -787,7 +665,7 @@ abstract public class ExpandableBinaryDictionary extends Dictionary { @UsedForTesting public void dumpAllWordsForDebug() { reloadDictionaryIfRequired(); - getExecutor(mDictName).execute(new Runnable() { + ExecutorUtils.getExecutor(mDictName).execute(new Runnable() { @Override public void run() { Log.d(TAG, "Dump dictionary: " + mDictName); diff --git a/java/src/com/android/inputmethod/latin/LatinIME.java b/java/src/com/android/inputmethod/latin/LatinIME.java index 793c1c4ac..4c2454c32 100644 --- a/java/src/com/android/inputmethod/latin/LatinIME.java +++ b/java/src/com/android/inputmethod/latin/LatinIME.java @@ -172,7 +172,11 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen } public void onCreate() { - final Resources res = getOwnerInstance().getResources(); + final LatinIME latinIme = getOwnerInstance(); + if (latinIme == null) { + return; + } + final Resources res = latinIme.getResources(); mDelayUpdateSuggestions = res.getInteger(R.integer.config_delay_update_suggestions); mDelayUpdateShiftState = res.getInteger(R.integer.config_delay_update_shift_state); mDoubleSpacePeriodTimeout = @@ -182,6 +186,9 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen @Override public void handleMessage(final Message msg) { final LatinIME latinIme = getOwnerInstance(); + if (latinIme == null) { + return; + } final KeyboardSwitcher switcher = latinIme.mKeyboardSwitcher; switch (msg.what) { case MSG_UPDATE_SUGGESTION_STRIP: @@ -239,7 +246,11 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen } public void postResumeSuggestions() { - if (!getOwnerInstance().mSettings.getCurrent().isSuggestionStripVisible()) { + final LatinIME latinIme = getOwnerInstance(); + if (latinIme == null) { + return; + } + if (!latinIme.mSettings.getCurrent().isSuggestionStripVisible()) { return; } removeMessages(MSG_RESUME_SUGGESTIONS); @@ -326,6 +337,9 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen resetPendingImsCallback(); mIsOrientationChanging = true; final LatinIME latinIme = getOwnerInstance(); + if (latinIme == null) { + return; + } if (latinIme.isInputViewShown()) { latinIme.mKeyboardSwitcher.saveKeyboardState(); } @@ -362,8 +376,10 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen mPendingSuccessiveImsCallback = true; } final LatinIME latinIme = getOwnerInstance(); - executePendingImsCallback(latinIme, editorInfo, restarting); - latinIme.onStartInputInternal(editorInfo, restarting); + if (latinIme != null) { + executePendingImsCallback(latinIme, editorInfo, restarting); + latinIme.onStartInputInternal(editorInfo, restarting); + } } } @@ -381,9 +397,11 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen PENDING_IMS_CALLBACK_DURATION); } final LatinIME latinIme = getOwnerInstance(); - executePendingImsCallback(latinIme, editorInfo, restarting); - latinIme.onStartInputViewInternal(editorInfo, restarting); - mAppliedEditorInfo = editorInfo; + if (latinIme != null) { + executePendingImsCallback(latinIme, editorInfo, restarting); + latinIme.onStartInputViewInternal(editorInfo, restarting); + mAppliedEditorInfo = editorInfo; + } } } @@ -393,8 +411,10 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen mHasPendingFinishInputView = true; } else { final LatinIME latinIme = getOwnerInstance(); - latinIme.onFinishInputViewInternal(finishingInput); - mAppliedEditorInfo = null; + if (latinIme != null) { + latinIme.onFinishInputViewInternal(finishingInput); + mAppliedEditorInfo = null; + } } } @@ -404,8 +424,10 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen mHasPendingFinishInput = true; } else { final LatinIME latinIme = getOwnerInstance(); - executePendingImsCallback(latinIme, null, false); - latinIme.onFinishInputInternal(); + if (latinIme != null) { + executePendingImsCallback(latinIme, null, false); + latinIme.onFinishInputInternal(); + } } } } @@ -857,7 +879,7 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen } // This will set the punctuation suggestions if next word suggestion is off; // otherwise it will clear the suggestion strip. - setNeutralSuggestionStripInternal(false /* needsInputViewShown */); + setNeutralSuggestionStripInternal(); mHandler.cancelUpdateSuggestionStrip(); mHandler.cancelDoubleSpacePeriodTimer(); @@ -1022,23 +1044,18 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen null /* rawSuggestions */, false /* typedWordValid */, false /* willAutoCorrect */, false /* isObsoleteSuggestions */, false /* isPrediction */); // When in fullscreen mode, show completions generated by the application forcibly - setSuggestedWords(suggestedWords, true /* isSuggestionStripVisible */, - true /* needsInputViewShown */); + setSuggestedWords(suggestedWords, true /* isSuggestionStripVisible */); if (ProductionFlag.USES_DEVELOPMENT_ONLY_DIAGNOSTICS) { ResearchLogger.latinIME_onDisplayCompletions(applicationSpecifiedCompletions); } } - private void setSuggestionStripShownInternal(final boolean isSuggestionStripVisible, - final boolean needsInputViewShown) { + private void setSuggestionStripShownInternal(final boolean isSuggestionStripVisible) { // TODO: Modify this if we support suggestions with hard keyboard if (!onEvaluateInputViewShown() || !hasSuggestionStripView()) { return; } - final boolean inputViewShown = mKeyboardSwitcher.isShowingMainKeyboardOrEmojiPalettes(); - final boolean shouldShowSuggestions = isSuggestionStripVisible - && (needsInputViewShown ? inputViewShown : true); - if (shouldShowSuggestions) { + if (isSuggestionStripVisible) { mSuggestionStripView.setVisibility(View.VISIBLE); } else { mSuggestionStripView.setVisibility(isFullscreenMode() ? View.GONE : View.INVISIBLE); @@ -1325,8 +1342,7 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen // Nothing to do so far. } - // TODO[IL]: Define a clear interface for this - public boolean isSuggestionStripVisible() { + private boolean isSuggestionStripVisible() { if (!hasSuggestionStripView()) { return false; } @@ -1350,7 +1366,6 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen return currentSettings.isSuggestionsRequested(); } - @Override public boolean hasSuggestionStripView() { return null != mSuggestionStripView; } @@ -1370,7 +1385,7 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen // TODO[IL]: Define a clear interface for this public void setSuggestedWords(final SuggestedWords suggestedWords, - final boolean isSuggestionStripVisible, final boolean needsInputViewShown) { + final boolean isSuggestionStripVisible) { mInputLogic.setSuggestedWords(suggestedWords); if (!hasSuggestionStripView()) { return; @@ -1390,7 +1405,7 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen SubtypeLocaleUtils.isRtlLanguage(mSubtypeSwitcher.getCurrentSubtype())); } mKeyboardSwitcher.onAutoCorrectionStateChanged(suggestedWords.mWillAutoCorrect); - setSuggestionStripShownInternal(isSuggestionStripVisible, needsInputViewShown); + setSuggestionStripShownInternal(isSuggestionStripVisible); } // TODO[IL]: Move this out of LatinIME. @@ -1476,8 +1491,7 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen setNeutralSuggestionStrip(); } else { mInputLogic.mWordComposer.setAutoCorrection(autoCorrection); - setSuggestedWords( - suggestedWords, isSuggestionStripVisible(), true /* needsInputViewShown */); + setSuggestedWords(suggestedWords, isSuggestionStripVisible()); } // Cache the auto-correction in accessibility code so we can speak it if the user // touches a key that will insert it. @@ -1506,14 +1520,14 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen // punctuation suggestions (if it's disabled). @Override public void setNeutralSuggestionStrip() { - setNeutralSuggestionStripInternal(true /* needsInputViewShown */); + setNeutralSuggestionStripInternal(); } - private void setNeutralSuggestionStripInternal(final boolean needsInputViewShown) { + private void setNeutralSuggestionStripInternal() { final SettingsValues currentSettings = mSettings.getCurrent(); final SuggestedWords neutralSuggestions = currentSettings.mBigramPredictionEnabled ? SuggestedWords.EMPTY : currentSettings.mSpacingAndPunctuations.mSuggestPuncList; - setSuggestedWords(neutralSuggestions, isSuggestionStripVisible(), needsInputViewShown); + setSuggestedWords(neutralSuggestions, isSuggestionStripVisible()); } // TODO: Make this private diff --git a/java/src/com/android/inputmethod/latin/RichInputConnection.java b/java/src/com/android/inputmethod/latin/RichInputConnection.java index 30b20a335..323256d1c 100644 --- a/java/src/com/android/inputmethod/latin/RichInputConnection.java +++ b/java/src/com/android/inputmethod/latin/RichInputConnection.java @@ -483,12 +483,16 @@ public final class RichInputConnection { * * @param start the character index where the selection should start. * @param end the character index where the selection should end. - * @return Returns true on success, false if the input connection is no longer valid either when - * setting the selection or when retrieving the text cache at that point. + * @return Returns true on success, false on failure: either the input connection is no longer + * valid when setting the selection or when retrieving the text cache at that point, or + * invalid arguments were passed. */ public boolean setSelection(final int start, final int end) { if (DEBUG_BATCH_NESTING) checkBatchEdit(); if (DEBUG_PREVIOUS_TEXT) checkConsistencyForDebug(); + if (start < 0 || end < 0) { + return false; + } mExpectedSelStart = start; mExpectedSelEnd = end; if (null != mIC) { diff --git a/java/src/com/android/inputmethod/latin/Suggest.java b/java/src/com/android/inputmethod/latin/Suggest.java index 499a98d65..1747eeeda 100644 --- a/java/src/com/android/inputmethod/latin/Suggest.java +++ b/java/src/com/android/inputmethod/latin/Suggest.java @@ -134,12 +134,17 @@ public final class Suggest { mDictionaryFacilitator.getSuggestions(wordComposerForLookup, prevWordForBigram, proximityInfo, blockOffensiveWords, additionalFeaturesOptions, SESSION_TYPING, suggestionsSet, rawSuggestions); + + final boolean isFirstCharCapitalized = wordComposer.isFirstCharCapitalized(); + final boolean isAllUpperCase = wordComposer.isAllUpperCase(); final String firstSuggestion; final String whitelistedWord; if (suggestionsSet.isEmpty()) { whitelistedWord = firstSuggestion = null; } else { - final SuggestedWordInfo firstSuggestedWordInfo = suggestionsSet.first(); + final SuggestedWordInfo firstSuggestedWordInfo = getTransformedSuggestedWordInfo( + suggestionsSet.first(), mLocale, isAllUpperCase, isFirstCharCapitalized, + trailingSingleQuotesCount); firstSuggestion = firstSuggestedWordInfo.mWord; if (SuggestedWordInfo.KIND_WHITELIST != firstSuggestedWordInfo.mKind) { whitelistedWord = null; @@ -148,6 +153,8 @@ public final class Suggest { } } + final boolean isPrediction = !wordComposer.isComposingWord(); + // We allow auto-correction if we have a whitelisted word, or if the word is not a valid // word of more than 1 char, except if the first suggestion is the same as the typed string // because in this case if it's strong enough to auto-correct that will mistakenly designate @@ -155,10 +162,10 @@ public final class Suggest { // TODO: stop relying on indices to find where is the auto-correction in the suggested // words, and correct this test. final boolean allowsToBeAutoCorrected = (null != whitelistedWord - && !whitelistedWord.equals(consideredWord)) + && !whitelistedWord.equals(typedWord)) || (consideredWord.length() > 1 && !mDictionaryFacilitator.isValidWord( consideredWord, wordComposer.isFirstCharCapitalized()) - && !consideredWord.equals(firstSuggestion)); + && !typedWord.equals(firstSuggestion)); final boolean hasAutoCorrection; // TODO: using isCorrectionEnabled here is not very good. It's probably useless, because @@ -166,7 +173,7 @@ public final class Suggest { // same time, it feels wrong that the SuggestedWord object includes information about // the current settings. It may also be useful to know, when the setting is off, whether // the word *would* have been auto-corrected. - if (!isCorrectionEnabled || !allowsToBeAutoCorrected || !wordComposer.isComposingWord() + if (!isCorrectionEnabled || !allowsToBeAutoCorrected || isPrediction || suggestionsSet.isEmpty() || wordComposer.hasDigits() || wordComposer.isMostlyCaps() || wordComposer.isResumed() || !mDictionaryFacilitator.hasMainDictionary() @@ -188,8 +195,6 @@ public final class Suggest { final ArrayList<SuggestedWordInfo> suggestionsContainer = CollectionUtils.newArrayList(suggestionsSet); final int suggestionsCount = suggestionsContainer.size(); - final boolean isFirstCharCapitalized = wordComposer.isFirstCharCapitalized(); - final boolean isAllUpperCase = wordComposer.isAllUpperCase(); if (isFirstCharCapitalized || isAllUpperCase || 0 != trailingSingleQuotesCount) { for (int i = 0; i < suggestionsCount; ++i) { final SuggestedWordInfo wordInfo = suggestionsContainer.get(i); @@ -226,10 +231,9 @@ public final class Suggest { // TODO: this first argument is lying. If this is a whitelisted word which is an // actual word, it says typedWordValid = false, which looks wrong. We should either // rename the attribute or change the value. - !allowsToBeAutoCorrected /* typedWordValid */, + !isPrediction && !allowsToBeAutoCorrected /* typedWordValid */, hasAutoCorrection, /* willAutoCorrect */ - false /* isObsoleteSuggestions */, - !wordComposer.isComposingWord() /* isPrediction */, sequenceNumber)); + false /* isObsoleteSuggestions */, isPrediction, sequenceNumber)); } // Retrieves suggestions for the batch input diff --git a/java/src/com/android/inputmethod/latin/SuggestedWords.java b/java/src/com/android/inputmethod/latin/SuggestedWords.java index 46df3e88c..06bc90c97 100644 --- a/java/src/com/android/inputmethod/latin/SuggestedWords.java +++ b/java/src/com/android/inputmethod/latin/SuggestedWords.java @@ -69,7 +69,7 @@ public class SuggestedWords { final boolean isPrediction, final int sequenceNumber) { this(suggestedWordInfoList, rawSuggestions, - suggestedWordInfoList.isEmpty() ? null + (suggestedWordInfoList.isEmpty() || isPrediction) ? null : suggestedWordInfoList.get(INDEX_OF_TYPED_WORD).mWord, typedWordValid, willAutoCorrect, isObsoleteSuggestions, isPrediction, sequenceNumber); diff --git a/java/src/com/android/inputmethod/latin/UserBinaryDictionary.java b/java/src/com/android/inputmethod/latin/UserBinaryDictionary.java index 3e3cbf063..8078ab541 100644 --- a/java/src/com/android/inputmethod/latin/UserBinaryDictionary.java +++ b/java/src/com/android/inputmethod/latin/UserBinaryDictionary.java @@ -86,8 +86,7 @@ public class UserBinaryDictionary extends ExpandableBinaryDictionary { public UserBinaryDictionary(final Context context, final Locale locale, final boolean alsoUseMoreRestrictiveLocales, final File dictFile) { - super(context, getDictName(NAME, locale, dictFile), locale, Dictionary.TYPE_USER, - false /* isUpdatable */, dictFile); + super(context, getDictName(NAME, locale, dictFile), locale, Dictionary.TYPE_USER, dictFile); if (null == locale) throw new NullPointerException(); // Catch the error earlier final String localeStr = locale.toString(); if (SubtypeLocaleUtils.NO_LANGUAGE.equals(localeStr)) { @@ -130,7 +129,7 @@ public class UserBinaryDictionary extends ExpandableBinaryDictionary { } @Override - public void loadDictionaryAsync() { + public void loadInitialContentsLocked() { // Split the locale. For example "en" => ["en"], "de_DE" => ["de", "DE"], // "en_US_foo_bar_qux" => ["en", "US", "foo_bar_qux"] because of the limit of 3. // This is correct for locale processing. @@ -182,7 +181,7 @@ public class UserBinaryDictionary extends ExpandableBinaryDictionary { try { cursor = mContext.getContentResolver().query( Words.CONTENT_URI, PROJECTION_QUERY, request.toString(), requestArguments, null); - addWords(cursor); + addWordsLocked(cursor); } catch (final SQLiteException e) { Log.e(TAG, "SQLiteException in the remote User dictionary process.", e); } finally { @@ -236,7 +235,7 @@ public class UserBinaryDictionary extends ExpandableBinaryDictionary { } } - private void addWords(final Cursor cursor) { + private void addWordsLocked(final Cursor cursor) { final boolean hasShortcutColumn = Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN; if (cursor == null) return; if (cursor.moveToFirst()) { @@ -250,12 +249,16 @@ 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, 0 /* shortcutFreq */, - false /* isNotAWord */); - } - if (null != shortcut && shortcut.length() < MAX_WORD_LENGTH) { - super.addWord(shortcut, word, adjustedFrequency, USER_DICT_SHORTCUT_FREQUENCY, - true /* isNotAWord */); + runGCIfRequiredLocked(true /* mindsBlockByGC */); + addWordDynamicallyLocked(word, adjustedFrequency, null /* shortcutTarget */, + 0 /* shortcutFreq */, false /* isNotAWord */, + false /* isBlacklisted */, BinaryDictionary.NOT_A_VALID_TIMESTAMP); + if (null != shortcut && shortcut.length() < MAX_WORD_LENGTH) { + runGCIfRequiredLocked(true /* mindsBlockByGC */); + addWordDynamicallyLocked(shortcut, adjustedFrequency, word, + USER_DICT_SHORTCUT_FREQUENCY, true /* isNotAWord */, + false /* isBlacklisted */, BinaryDictionary.NOT_A_VALID_TIMESTAMP); + } } cursor.moveToNext(); } @@ -263,12 +266,12 @@ public class UserBinaryDictionary extends ExpandableBinaryDictionary { } @Override - protected boolean hasContentChanged() { + protected boolean haveContentsChanged() { return true; } @Override - protected boolean needsToReloadBeforeWriting() { + protected boolean needsToReloadAfterCreation() { return true; } } diff --git a/java/src/com/android/inputmethod/latin/inputlogic/InputLogic.java b/java/src/com/android/inputmethod/latin/inputlogic/InputLogic.java index 045d06f0e..f2f9f1e68 100644 --- a/java/src/com/android/inputmethod/latin/inputlogic/InputLogic.java +++ b/java/src/com/android/inputmethod/latin/inputlogic/InputLogic.java @@ -1360,10 +1360,12 @@ public final class InputLogic { }}); } else { // We found suggestion spans in the word. We'll create the SuggestedWords out of - // them, and make willAutoCorrect false. + // them, and make willAutoCorrect false. We make typedWordValid false, because the + // color of the word in the suggestion strip changes according to this parameter, + // and false gives the correct color. final SuggestedWords suggestedWords = new SuggestedWords(suggestions, null /* rawSuggestions */, typedWord, - true /* typedWordValid */, false /* willAutoCorrect */, + false /* typedWordValid */, false /* willAutoCorrect */, false /* isObsoleteSuggestions */, false /* isPrediction */, SuggestedWords.NOT_A_SEQUENCE_NUMBER); mIsAutoCorrectionIndicatorOn = false; diff --git a/java/src/com/android/inputmethod/latin/inputlogic/InputLogicHandler.java b/java/src/com/android/inputmethod/latin/inputlogic/InputLogicHandler.java index b09e20591..db96de305 100644 --- a/java/src/com/android/inputmethod/latin/inputlogic/InputLogicHandler.java +++ b/java/src/com/android/inputmethod/latin/inputlogic/InputLogicHandler.java @@ -139,7 +139,7 @@ class InputLogicHandler implements Handler.Callback { forEnd /* dismissGestureFloatingPreviewText */); if (forEnd) { mInBatchInput = false; - // The following call schedules onEndBatchInputAsyncInternal + // The following call schedules onEndBatchInputInternal // to be called on the UI thread. mLatinIME.mHandler.onEndBatchInput(suggestedWords); } diff --git a/java/src/com/android/inputmethod/latin/makedict/BinaryDictDecoderUtils.java b/java/src/com/android/inputmethod/latin/makedict/BinaryDictDecoderUtils.java index b534ebeff..25e1bcd25 100644 --- a/java/src/com/android/inputmethod/latin/makedict/BinaryDictDecoderUtils.java +++ b/java/src/com/android/inputmethod/latin/makedict/BinaryDictDecoderUtils.java @@ -357,7 +357,7 @@ public final class BinaryDictDecoderUtils { * @return true if it's a binary dictionary, false otherwise */ public static boolean isBinaryDictionary(final File file) { - final DictDecoder dictDecoder = FormatSpec.getDictDecoder(file); + final DictDecoder dictDecoder = FormatSpec.getDictDecoder(file, 0, file.length()); if (dictDecoder == null) { return false; } diff --git a/java/src/com/android/inputmethod/latin/makedict/BinaryDictIOUtils.java b/java/src/com/android/inputmethod/latin/makedict/BinaryDictIOUtils.java index 989ca4b2f..90e7400fb 100644 --- a/java/src/com/android/inputmethod/latin/makedict/BinaryDictIOUtils.java +++ b/java/src/com/android/inputmethod/latin/makedict/BinaryDictIOUtils.java @@ -237,7 +237,7 @@ public final class BinaryDictIOUtils { final File file, final long offset, final long length) throws FileNotFoundException, IOException, UnsupportedFormatException { final byte[] buffer = new byte[HEADER_READING_BUFFER_SIZE]; - final DictDecoder dictDecoder = FormatSpec.getDictDecoder(file, + final DictDecoder dictDecoder = FormatSpec.getDictDecoder(file, offset, length, new DictDecoder.DictionaryBufferFactory() { @Override public DictBuffer getDictionaryBuffer(File file) @@ -251,8 +251,7 @@ public final class BinaryDictIOUtils { inStream.close(); } } - } - ); + }); if (dictDecoder == null) { return null; } diff --git a/java/src/com/android/inputmethod/latin/makedict/DictEncoder.java b/java/src/com/android/inputmethod/latin/makedict/DictEncoder.java index a5dc45691..678c5ca6b 100644 --- a/java/src/com/android/inputmethod/latin/makedict/DictEncoder.java +++ b/java/src/com/android/inputmethod/latin/makedict/DictEncoder.java @@ -16,6 +16,7 @@ package com.android.inputmethod.latin.makedict; +import com.android.inputmethod.annotations.UsedForTesting; import com.android.inputmethod.latin.makedict.FormatSpec.FormatOptions; import com.android.inputmethod.latin.makedict.FusionDictionary.PtNode; @@ -25,6 +26,7 @@ import java.io.IOException; * An interface of binary dictionary encoder. */ public interface DictEncoder { + @UsedForTesting public void writeDictionary(final FusionDictionary dict, final FormatOptions formatOptions) throws IOException, UnsupportedFormatException; diff --git a/java/src/com/android/inputmethod/latin/makedict/DictionaryHeader.java b/java/src/com/android/inputmethod/latin/makedict/DictionaryHeader.java index b99e281da..b32eb9195 100644 --- a/java/src/com/android/inputmethod/latin/makedict/DictionaryHeader.java +++ b/java/src/com/android/inputmethod/latin/makedict/DictionaryHeader.java @@ -38,6 +38,14 @@ public final class DictionaryHeader { public static final String DICTIONARY_DATE_KEY = "date"; public static final String HAS_HISTORICAL_INFO_KEY = "HAS_HISTORICAL_INFO"; public static final String USES_FORGETTING_CURVE_KEY = "USES_FORGETTING_CURVE"; + public static final String FORGETTING_CURVE_OCCURRENCES_TO_LEVEL_UP_KEY = + "FORGETTING_CURVE_OCCURRENCES_TO_LEVEL_UP"; + public static final String FORGETTING_CURVE_PROBABILITY_VALUES_TABLE_ID_KEY = + "FORGETTING_CURVE_PROBABILITY_VALUES_TABLE_ID"; + public static final String FORGETTING_CURVE_DURATION_TO_LEVEL_DOWN_IN_SECONDS_KEY = + "FORGETTING_CURVE_DURATION_TO_LEVEL_DOWN_IN_SECONDS"; + public static final String MAX_UNIGRAM_COUNT_KEY = "MAX_UNIGRAM_COUNT"; + public static final String MAX_BIGRAM_COUNT_KEY = "MAX_BIGRAM_COUNT"; public static final String ATTRIBUTE_VALUE_TRUE = "1"; public DictionaryHeader(final int headerSize, final DictionaryOptions dictionaryOptions, diff --git a/java/src/com/android/inputmethod/latin/makedict/FormatSpec.java b/java/src/com/android/inputmethod/latin/makedict/FormatSpec.java index c7635eff9..9abecbfec 100644 --- a/java/src/com/android/inputmethod/latin/makedict/FormatSpec.java +++ b/java/src/com/android/inputmethod/latin/makedict/FormatSpec.java @@ -326,30 +326,34 @@ public final class FormatSpec { * Returns new dictionary decoder. * * @param dictFile the dictionary file. + * @param offset the offset in the file. + * @param length the length of the file, in bytes. * @param bufferType The type of buffer, as one of USE_* in DictDecoder. * @return new dictionary decoder if the dictionary file exists, otherwise null. */ - public static DictDecoder getDictDecoder(final File dictFile, final int bufferType) { + public static DictDecoder getDictDecoder(final File dictFile, final long offset, + final long length, final int bufferType) { if (dictFile.isDirectory()) { return new Ver4DictDecoder(dictFile, bufferType); } else if (dictFile.isFile()) { - return new Ver2DictDecoder(dictFile, bufferType); + return new Ver2DictDecoder(dictFile, offset, length, bufferType); } return null; } - public static DictDecoder getDictDecoder(final File dictFile, - final DictionaryBufferFactory factory) { + public static DictDecoder getDictDecoder(final File dictFile, final long offset, + final long length, final DictionaryBufferFactory factory) { if (dictFile.isDirectory()) { return new Ver4DictDecoder(dictFile, factory); } else if (dictFile.isFile()) { - return new Ver2DictDecoder(dictFile, factory); + return new Ver2DictDecoder(dictFile, offset, length, factory); } return null; } - public static DictDecoder getDictDecoder(final File dictFile) { - return getDictDecoder(dictFile, DictDecoder.USE_READONLY_BYTEBUFFER); + public static DictDecoder getDictDecoder(final File dictFile, final long offset, + final long length) { + return getDictDecoder(dictFile, offset, length, DictDecoder.USE_READONLY_BYTEBUFFER); } private FormatSpec() { diff --git a/java/src/com/android/inputmethod/latin/makedict/Ver2DictDecoder.java b/java/src/com/android/inputmethod/latin/makedict/Ver2DictDecoder.java index bf776cfc5..ae1e443c5 100644 --- a/java/src/com/android/inputmethod/latin/makedict/Ver2DictDecoder.java +++ b/java/src/com/android/inputmethod/latin/makedict/Ver2DictDecoder.java @@ -116,13 +116,18 @@ public class Ver2DictDecoder extends AbstractDictDecoder { } protected final File mDictionaryBinaryFile; + protected final long mOffset; + protected final long mLength; // TODO: Remove mBufferFactory and mDictBuffer from this class members because they are now // used only for testing. private final DictionaryBufferFactory mBufferFactory; protected DictBuffer mDictBuffer; - /* package */ Ver2DictDecoder(final File file, final int factoryFlag) { + /* package */ Ver2DictDecoder(final File file, final long offset, final long length, + final int factoryFlag) { mDictionaryBinaryFile = file; + mOffset = offset; + mLength = length; mDictBuffer = null; if ((factoryFlag & MASK_DICTBUFFER) == USE_READONLY_BYTEBUFFER) { mBufferFactory = new DictionaryBufferFromReadOnlyByteBufferFactory(); @@ -135,8 +140,11 @@ public class Ver2DictDecoder extends AbstractDictDecoder { } } - /* package */ Ver2DictDecoder(final File file, final DictionaryBufferFactory factory) { + /* package */ Ver2DictDecoder(final File file, final long offset, final long length, + final DictionaryBufferFactory factory) { mDictionaryBinaryFile = file; + mOffset = offset; + mLength = length; mBufferFactory = factory; } @@ -164,9 +172,9 @@ public class Ver2DictDecoder extends AbstractDictDecoder { public DictionaryHeader readHeader() throws IOException, UnsupportedFormatException { // dictType is not being used in dicttool. Passing an empty string. final BinaryDictionary binaryDictionary = new BinaryDictionary( - mDictionaryBinaryFile.getAbsolutePath(), 0 /* offset */, - mDictionaryBinaryFile.length() /* length */, true /* useFullEditDistance */, - null /* locale */, "" /* dictType */, false /* isUpdatable */); + mDictionaryBinaryFile.getAbsolutePath(), mOffset, mLength, + true /* useFullEditDistance */, null /* locale */, "" /* dictType */, + false /* isUpdatable */); final DictionaryHeader header = binaryDictionary.getHeader(); binaryDictionary.close(); if (header == null) { diff --git a/java/src/com/android/inputmethod/latin/personalization/DecayingExpandableBinaryDictionaryBase.java b/java/src/com/android/inputmethod/latin/personalization/DecayingExpandableBinaryDictionaryBase.java index d3734d693..074ec4074 100644 --- a/java/src/com/android/inputmethod/latin/personalization/DecayingExpandableBinaryDictionaryBase.java +++ b/java/src/com/android/inputmethod/latin/personalization/DecayingExpandableBinaryDictionaryBase.java @@ -27,10 +27,8 @@ import com.android.inputmethod.latin.utils.LanguageModelParam; import java.io.File; import java.util.ArrayList; -import java.util.HashMap; import java.util.Locale; import java.util.Map; -import java.util.concurrent.TimeUnit; /** * This class is a base class of a dictionary that supports decaying for the personalized language @@ -49,14 +47,13 @@ public abstract class DecayingExpandableBinaryDictionaryBase extends ExpandableB /** The locale for this dictionary. */ public final Locale mLocale; - private final String mDictName; + private Map<String, String> mAdditionalAttributeMap = null; protected DecayingExpandableBinaryDictionaryBase(final Context context, final String dictName, final Locale locale, final String dictionaryType, final File dictFile) { - super(context, dictName, locale, dictionaryType, true /* isUpdatable */, dictFile); + super(context, dictName, locale, dictionaryType, dictFile); mLocale = locale; - mDictName = dictName; if (mLocale != null && mLocale.toString().length() > 1) { reloadDictionaryIfRequired(); } @@ -78,25 +75,24 @@ public abstract class DecayingExpandableBinaryDictionaryBase extends ExpandableB @Override protected Map<String, String> getHeaderAttributeMap() { - HashMap<String, String> attributeMap = new HashMap<String, String>(); + final Map<String, String> attributeMap = super.getHeaderAttributeMap(); + if (mAdditionalAttributeMap != null) { + attributeMap.putAll(mAdditionalAttributeMap); + } attributeMap.put(DictionaryHeader.USES_FORGETTING_CURVE_KEY, DictionaryHeader.ATTRIBUTE_VALUE_TRUE); attributeMap.put(DictionaryHeader.HAS_HISTORICAL_INFO_KEY, DictionaryHeader.ATTRIBUTE_VALUE_TRUE); - attributeMap.put(DictionaryHeader.DICTIONARY_ID_KEY, mDictName); - attributeMap.put(DictionaryHeader.DICTIONARY_LOCALE_KEY, mLocale.toString()); - attributeMap.put(DictionaryHeader.DICTIONARY_VERSION_KEY, - String.valueOf(TimeUnit.MILLISECONDS.toSeconds(System.currentTimeMillis()))); return attributeMap; } @Override - protected boolean hasContentChanged() { + protected boolean haveContentsChanged() { return false; } @Override - protected boolean needsToReloadBeforeWriting() { + protected boolean needsToReloadAfterCreation() { return false; } @@ -140,8 +136,8 @@ public abstract class DecayingExpandableBinaryDictionaryBase extends ExpandableB } @Override - protected void loadDictionaryAsync() { - // Never loaded to memory in Java side. + protected void loadInitialContentsLocked() { + // No initial contents. } @UsedForTesting @@ -152,7 +148,14 @@ public abstract class DecayingExpandableBinaryDictionaryBase extends ExpandableB asyncFlushBinaryDictionary(); } - /* package */ void decayIfNeeded() { + @UsedForTesting + public void clearAndFlushDictionaryWithAdditionalAttributes( + final Map<String, String> attributeMap) { + mAdditionalAttributeMap = attributeMap; + clearAndFlushDictionary(); + } + + /* package */ void runGCIfRequired() { runGCIfRequired(false /* mindsBlockByGC */); } } diff --git a/java/src/com/android/inputmethod/latin/personalization/DictionaryDecayBroadcastReciever.java b/java/src/com/android/inputmethod/latin/personalization/DictionaryDecayBroadcastReciever.java index e9ca662e7..de2744f29 100644 --- a/java/src/com/android/inputmethod/latin/personalization/DictionaryDecayBroadcastReciever.java +++ b/java/src/com/android/inputmethod/latin/personalization/DictionaryDecayBroadcastReciever.java @@ -43,7 +43,7 @@ public class DictionaryDecayBroadcastReciever extends BroadcastReceiver { /** * Interval to update for decaying dictionaries. */ - private static final long DICTIONARY_DECAY_INTERVAL = TimeUnit.MINUTES.toMillis(60); + /* package */ static final long DICTIONARY_DECAY_INTERVAL = TimeUnit.MINUTES.toMillis(60); public static void setUpIntervalAlarmForDictionaryDecaying(Context context) { AlarmManager alarmManager = (AlarmManager)context.getSystemService(Context.ALARM_SERVICE); @@ -60,7 +60,7 @@ public class DictionaryDecayBroadcastReciever extends BroadcastReceiver { public void onReceive(final Context context, final Intent intent) { final String action = intent.getAction(); if (action.equals(DICTIONARY_DECAY_INTENT_ACTION)) { - PersonalizationHelper.tryDecayingAllOpeningUserHistoryDictionary(); + PersonalizationHelper.runGCOnAllOpenedUserHistoryDictionaries(); } } } diff --git a/java/src/com/android/inputmethod/latin/personalization/PersonalizationHelper.java b/java/src/com/android/inputmethod/latin/personalization/PersonalizationHelper.java index 5ae2fb6f8..385b525b6 100644 --- a/java/src/com/android/inputmethod/latin/personalization/PersonalizationHelper.java +++ b/java/src/com/android/inputmethod/latin/personalization/PersonalizationHelper.java @@ -16,6 +16,7 @@ package com.android.inputmethod.latin.personalization; +import com.android.inputmethod.annotations.UsedForTesting; import com.android.inputmethod.latin.utils.CollectionUtils; import com.android.inputmethod.latin.utils.FileUtils; @@ -27,6 +28,7 @@ import java.io.FilenameFilter; import java.lang.ref.SoftReference; import java.util.Locale; import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.TimeUnit; public class PersonalizationHelper { private static final String TAG = PersonalizationHelper.class.getSimpleName(); @@ -59,14 +61,35 @@ public class PersonalizationHelper { } } - public static void tryDecayingAllOpeningUserHistoryDictionary() { - for (final ConcurrentHashMap.Entry<String, SoftReference<UserHistoryDictionary>> entry - : sLangUserHistoryDictCache.entrySet()) { - if (entry.getValue() != null) { - final UserHistoryDictionary dict = entry.getValue().get(); - if (dict != null) { - dict.decayIfNeeded(); - } + private static int sCurrentTimestampForTesting = 0; + public static void currentTimeChangedForTesting(final int currentTimestamp) { + if (TimeUnit.MILLISECONDS.toSeconds( + DictionaryDecayBroadcastReciever.DICTIONARY_DECAY_INTERVAL) + < currentTimestamp - sCurrentTimestampForTesting) { + // TODO: Run GC for both PersonalizationDictionary and UserHistoryDictionary. + runGCOnAllOpenedUserHistoryDictionaries(); + } + } + + public static void runGCOnAllOpenedUserHistoryDictionaries() { + runGCOnAllDictionariesIfRequired(sLangUserHistoryDictCache); + } + + @UsedForTesting + public static void runGCOnAllOpenedPersonalizationDictionaries() { + runGCOnAllDictionariesIfRequired(sLangPersonalizationDictCache); + } + + private static <T extends DecayingExpandableBinaryDictionaryBase> + void runGCOnAllDictionariesIfRequired( + final ConcurrentHashMap<String, SoftReference<T>> dictionaryMap) { + for (final ConcurrentHashMap.Entry<String, SoftReference<T>> entry + : dictionaryMap.entrySet()) { + final DecayingExpandableBinaryDictionaryBase dict = entry.getValue().get(); + if (dict != null) { + dict.runGCIfRequired(); + } else { + dictionaryMap.remove(entry.getKey()); } } } diff --git a/java/src/com/android/inputmethod/latin/suggestions/SuggestionStripLayoutHelper.java b/java/src/com/android/inputmethod/latin/suggestions/SuggestionStripLayoutHelper.java index 8ea712835..afa8fe3a8 100644 --- a/java/src/com/android/inputmethod/latin/suggestions/SuggestionStripLayoutHelper.java +++ b/java/src/com/android/inputmethod/latin/suggestions/SuggestionStripLayoutHelper.java @@ -95,9 +95,9 @@ final class SuggestionStripLayoutHelper { private static final CharacterStyle BOLD_SPAN = new StyleSpan(Typeface.BOLD); private static final CharacterStyle UNDERLINE_SPAN = new UnderlineSpan(); - private final int mSuggestionStripOption; + private final int mSuggestionStripOptions; // These constants are the flag values of - // {@link R.styleable#SuggestionStripView_suggestionStripOption} attribute. + // {@link R.styleable#SuggestionStripView_suggestionStripOptions} attribute. private static final int AUTO_CORRECT_BOLD = 0x01; private static final int AUTO_CORRECT_UNDERLINE = 0x02; private static final int VALID_TYPED_WORD_BOLD = 0x04; @@ -122,8 +122,8 @@ final class SuggestionStripLayoutHelper { final TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.SuggestionStripView, defStyle, R.style.SuggestionStripView); - mSuggestionStripOption = a.getInt( - R.styleable.SuggestionStripView_suggestionStripOption, 0); + mSuggestionStripOptions = a.getInt( + R.styleable.SuggestionStripView_suggestionStripOptions, 0); mAlphaObsoleted = ResourceUtils.getFraction(a, R.styleable.SuggestionStripView_alphaObsoleted, 1.0f); mColorValidTypedWord = a.getColor(R.styleable.SuggestionStripView_colorValidTypedWord, 0); @@ -200,22 +200,24 @@ final class SuggestionStripLayoutHelper { return null; } final String word = suggestedWords.getLabel(indexInSuggestedWords); - final boolean isAutoCorrect = indexInSuggestedWords == 1 - && suggestedWords.mWillAutoCorrect; - final boolean isTypedWordValid = indexInSuggestedWords == 0 - && suggestedWords.mTypedWordValid; - if (!isAutoCorrect && !isTypedWordValid) { + // TODO: don't use the index to decide whether this is the auto-correction/typed word, as + // this is brittle + final boolean isAutoCorrection = suggestedWords.mWillAutoCorrect + && indexInSuggestedWords == SuggestedWords.INDEX_OF_AUTO_CORRECTION; + final boolean isTypedWordValid = suggestedWords.mTypedWordValid + && indexInSuggestedWords == SuggestedWords.INDEX_OF_TYPED_WORD; + if (!isAutoCorrection && !isTypedWordValid) { return word; } final int len = word.length(); final Spannable spannedWord = new SpannableString(word); - final int option = mSuggestionStripOption; - if ((isAutoCorrect && (option & AUTO_CORRECT_BOLD) != 0) - || (isTypedWordValid && (option & VALID_TYPED_WORD_BOLD) != 0)) { + final int options = mSuggestionStripOptions; + if ((isAutoCorrection && (options & AUTO_CORRECT_BOLD) != 0) + || (isTypedWordValid && (options & VALID_TYPED_WORD_BOLD) != 0)) { spannedWord.setSpan(BOLD_SPAN, 0, len, Spanned.SPAN_INCLUSIVE_EXCLUSIVE); } - if (isAutoCorrect && (option & AUTO_CORRECT_UNDERLINE) != 0) { + if (isAutoCorrection && (options & AUTO_CORRECT_UNDERLINE) != 0) { spannedWord.setSpan(UNDERLINE_SPAN, 0, len, Spanned.SPAN_INCLUSIVE_EXCLUSIVE); } return spannedWord; @@ -242,22 +244,23 @@ final class SuggestionStripLayoutHelper { return indexInSuggestedWords; } - private int getSuggestionTextColor(final int indexInSuggestedWords, - final SuggestedWords suggestedWords) { + private int getSuggestionTextColor(final SuggestedWords suggestedWords, + final int indexInSuggestedWords) { final int positionInStrip = getPositionInSuggestionStrip(indexInSuggestedWords, suggestedWords); - // TODO: Need to revisit this logic with bigram suggestions - final boolean isSuggested = (indexInSuggestedWords != SuggestedWords.INDEX_OF_TYPED_WORD); + // Use identity for strings, not #equals : it's the typed word if it's the same object + final boolean isTypedWord = + suggestedWords.getWord(indexInSuggestedWords) == suggestedWords.mTypedWord; final int color; if (positionInStrip == mCenterPositionInStrip && suggestedWords.mWillAutoCorrect) { color = mColorAutoCorrect; - } else if (positionInStrip == mCenterPositionInStrip && suggestedWords.mTypedWordValid) { + } else if (isTypedWord && suggestedWords.mTypedWordValid) { color = mColorValidTypedWord; - } else if (isSuggested) { - color = mColorSuggested; - } else { + } else if (isTypedWord) { color = mColorTypedWord; + } else { + color = mColorSuggested; } if (LatinImeLogger.sDBG && suggestedWords.size() > 1) { // If we auto-correct, then the autocorrection is in slot 0 and the typed word @@ -270,7 +273,7 @@ final class SuggestionStripLayoutHelper { } } - if (suggestedWords.mIsObsoleteSuggestions && isSuggested) { + if (suggestedWords.mIsObsoleteSuggestions && !isTypedWord) { return applyAlpha(color, mAlphaObsoleted); } return color; @@ -438,7 +441,7 @@ final class SuggestionStripLayoutHelper { // {@link SuggestionStripView#onClick(View)}. wordView.setTag(indexInSuggestedWords); wordView.setText(getStyledSuggestedWord(suggestedWords, indexInSuggestedWords)); - wordView.setTextColor(getSuggestionTextColor(positionInStrip, suggestedWords)); + wordView.setTextColor(getSuggestionTextColor(suggestedWords, indexInSuggestedWords)); if (SuggestionStripView.DBG) { mDebugInfoViews.get(positionInStrip).setText( suggestedWords.getDebugString(indexInSuggestedWords)); diff --git a/java/src/com/android/inputmethod/latin/suggestions/SuggestionStripViewAccessor.java b/java/src/com/android/inputmethod/latin/suggestions/SuggestionStripViewAccessor.java index 60f1c7a4e..52708455e 100644 --- a/java/src/com/android/inputmethod/latin/suggestions/SuggestionStripViewAccessor.java +++ b/java/src/com/android/inputmethod/latin/suggestions/SuggestionStripViewAccessor.java @@ -22,7 +22,6 @@ import com.android.inputmethod.latin.SuggestedWords; * An object that gives basic control of a suggestion strip and some info on it. */ public interface SuggestionStripViewAccessor { - public boolean hasSuggestionStripView(); public void showAddToDictionaryHint(final String word); public boolean isShowingAddToDictionaryHint(); public void dismissAddToDictionaryHint(); diff --git a/java/src/com/android/inputmethod/latin/utils/ExecutorUtils.java b/java/src/com/android/inputmethod/latin/utils/ExecutorUtils.java new file mode 100644 index 000000000..ee9718ad3 --- /dev/null +++ b/java/src/com/android/inputmethod/latin/utils/ExecutorUtils.java @@ -0,0 +1,55 @@ +/* + * Copyright (C) 2014 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.utils; + +import com.android.inputmethod.annotations.UsedForTesting; + +import java.util.concurrent.ConcurrentHashMap; + +/** + * Utilities to manage executors. + */ +public class ExecutorUtils { + private static final ConcurrentHashMap<String, PrioritizedSerialExecutor> + sExecutorMap = CollectionUtils.newConcurrentHashMap(); + /** + * Gets the executor for the given dictionary name. + */ + public static PrioritizedSerialExecutor getExecutor(final String dictName) { + PrioritizedSerialExecutor executor = sExecutorMap.get(dictName); + if (executor == null) { + synchronized(sExecutorMap) { + executor = new PrioritizedSerialExecutor(); + sExecutorMap.put(dictName, executor); + } + } + return executor; + } + + /** + * Shutdowns all executors and removes all executors from the executor map for testing. + */ + @UsedForTesting + public static void shutdownAllExecutors() { + synchronized(sExecutorMap) { + for (final PrioritizedSerialExecutor executor : sExecutorMap.values()) { + executor.shutdown(); + sExecutorMap.remove(executor); + } + } + } +} diff --git a/java/src/com/android/inputmethod/latin/utils/LanguageModelParam.java b/java/src/com/android/inputmethod/latin/utils/LanguageModelParam.java index a1d641508..562ff9e8d 100644 --- a/java/src/com/android/inputmethod/latin/utils/LanguageModelParam.java +++ b/java/src/com/android/inputmethod/latin/utils/LanguageModelParam.java @@ -37,9 +37,9 @@ public final class LanguageModelParam { // non-0. Thus, it's not meaningful to compare 10, 100, and so on. // TODO: Revise the logic in ForgettingCurveUtils in native code. private static final int UNIGRAM_PROBABILITY_FOR_VALID_WORD = 100; - private static final int UNIGRAM_PROBABILITY_FOR_OOV_WORD = 10; - private static final int BIGRAM_PROBABILITY_FOR_VALID_WORD = 0; - private static final int BIGRAM_PROBABILITY_FOR_OOV_WORD = 0; + private static final int UNIGRAM_PROBABILITY_FOR_OOV_WORD = Dictionary.NOT_A_PROBABILITY; + private static final int BIGRAM_PROBABILITY_FOR_VALID_WORD = 10; + private static final int BIGRAM_PROBABILITY_FOR_OOV_WORD = Dictionary.NOT_A_PROBABILITY; public final String mTargetWord; public final int[] mWord0; |