diff options
21 files changed, 701 insertions, 799 deletions
diff --git a/java/res/values/strings.xml b/java/res/values/strings.xml index bd18e5544..e38bdd64e 100644 --- a/java/res/values/strings.xml +++ b/java/res/values/strings.xml @@ -192,8 +192,8 @@ <string name="spoken_description_space">Space</string> <!-- Spoken description for the "Mic" keyboard key. --> <string name="spoken_description_mic">Voice input</string> - <!-- Spoken description for the "Smiley" keyboard key. --> - <string name="spoken_description_smiley">Smiley face</string> + <!-- Spoken description for the "Emoji" keyboard key. --> + <string name="spoken_description_emoji">Emoji</string> <!-- Spoken description for the "Return" keyboard key. --> <string name="spoken_description_return">Return</string> <!-- Spoken description for the "Search" keyboard key. --> diff --git a/java/src/com/android/inputmethod/accessibility/KeyCodeDescriptionMapper.java b/java/src/com/android/inputmethod/accessibility/KeyCodeDescriptionMapper.java index 58624a2e6..2e6649bf2 100644 --- a/java/src/com/android/inputmethod/accessibility/KeyCodeDescriptionMapper.java +++ b/java/src/com/android/inputmethod/accessibility/KeyCodeDescriptionMapper.java @@ -58,9 +58,6 @@ public final class KeyCodeDescriptionMapper { } private void initInternal() { - // Manual label substitutions for key labels with no string resource - mKeyLabelMap.put(":-)", R.string.spoken_description_smiley); - // Special non-character codes defined in Keyboard mKeyCodeMap.put(Constants.CODE_SPACE, R.string.spoken_description_space); mKeyCodeMap.put(Constants.CODE_DELETE, R.string.spoken_description_delete); @@ -75,6 +72,7 @@ public final class KeyCodeDescriptionMapper { mKeyCodeMap.put(Constants.CODE_ACTION_NEXT, R.string.spoken_description_action_next); mKeyCodeMap.put(Constants.CODE_ACTION_PREVIOUS, R.string.spoken_description_action_previous); + mKeyCodeMap.put(Constants.CODE_EMOJI, R.string.spoken_description_emoji); } /** diff --git a/java/src/com/android/inputmethod/event/CombinerChain.java b/java/src/com/android/inputmethod/event/CombinerChain.java index 5ca9842c1..8b59dc52a 100644 --- a/java/src/com/android/inputmethod/event/CombinerChain.java +++ b/java/src/com/android/inputmethod/event/CombinerChain.java @@ -17,7 +17,9 @@ package com.android.inputmethod.event; import android.text.SpannableStringBuilder; +import android.text.TextUtils; +import com.android.inputmethod.latin.Constants; import com.android.inputmethod.latin.utils.CollectionUtils; import java.util.ArrayList; @@ -84,7 +86,19 @@ public class CombinerChain { } } if (null != event) { - mCombinedText.append(event.getTextToCommit()); + // TODO: figure out the generic way of doing this + if (Constants.CODE_DELETE == event.mKeyCode) { + final int length = mCombinedText.length(); + if (length > 0) { + final int lastCodePoint = mCombinedText.codePointBefore(length); + mCombinedText.delete(length - Character.charCount(lastCodePoint), length); + } + } else { + final CharSequence textToCommit = event.getTextToCommit(); + if (!TextUtils.isEmpty(textToCommit)) { + mCombinedText.append(textToCommit); + } + } } mStateFeedback.clear(); for (int i = mCombiners.size() - 1; i >= 0; --i) { diff --git a/java/src/com/android/inputmethod/event/Event.java b/java/src/com/android/inputmethod/event/Event.java index 2bfe0732d..646590948 100644 --- a/java/src/com/android/inputmethod/event/Event.java +++ b/java/src/com/android/inputmethod/event/Event.java @@ -229,9 +229,9 @@ public class Event { switch (mType) { case EVENT_MODE_KEY: case EVENT_NOT_HANDLED: + case EVENT_TOGGLE: return ""; case EVENT_INPUT_KEYPRESS: - case EVENT_TOGGLE: return StringUtils.newSingleCodePointString(mCodePoint); case EVENT_GESTURE: case EVENT_SOFTWARE_GENERATED_STRING: diff --git a/java/src/com/android/inputmethod/latin/DictionaryFacilitatorForSuggest.java b/java/src/com/android/inputmethod/latin/DictionaryFacilitatorForSuggest.java index d6178fcee..8b8d5776e 100644 --- a/java/src/com/android/inputmethod/latin/DictionaryFacilitatorForSuggest.java +++ b/java/src/com/android/inputmethod/latin/DictionaryFacilitatorForSuggest.java @@ -26,15 +26,14 @@ import com.android.inputmethod.latin.SuggestedWords.SuggestedWordInfo; import com.android.inputmethod.latin.personalization.PersonalizationDictionary; 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 com.android.inputmethod.latin.utils.SuggestionResults; import java.io.File; import java.util.ArrayList; import java.util.HashMap; -import java.util.HashSet; import java.util.Locale; import java.util.Map; import java.util.Set; @@ -50,197 +49,314 @@ public class DictionaryFacilitatorForSuggest { // dictionary. private static final int CAPITALIZED_FORM_MAX_PROBABILITY_FOR_INSERT = 140; - private final Context mContext; - public final Locale mLocale; + private Dictionaries mDictionaries = new Dictionaries(); + private volatile CountDownLatch mLatchForWaitingLoadingMainDictionary = new CountDownLatch(0); + // To synchronize assigning mDictionaries to ensure closing dictionaries. + private Object mLock = new Object(); - private final ConcurrentHashMap<String, Dictionary> mDictionaries = - CollectionUtils.newConcurrentHashMap(); + /** + * Class contains dictionaries for a locale. + */ + private static class Dictionaries { + public final Locale mLocale; + public final ConcurrentHashMap<String, Dictionary> mDictMap = + CollectionUtils.newConcurrentHashMap(); + // Main dictionary will be asynchronously loaded. + public Dictionary mMainDictionary; + public final ContactsBinaryDictionary mContactsDictionary; + public final UserBinaryDictionary mUserDictionary; + public final UserHistoryDictionary mUserHistoryDictionary; + public final PersonalizationDictionary mPersonalizationDictionary; + + public Dictionaries() { + mLocale = null; + mMainDictionary = null; + mContactsDictionary = null; + mUserDictionary = null; + mUserHistoryDictionary = null; + mPersonalizationDictionary = null; + } + + public Dictionaries(final Locale locale, final Dictionary mainDict, + final ContactsBinaryDictionary contactsDict, final UserBinaryDictionary userDict, + final UserHistoryDictionary userHistoryDict, + final PersonalizationDictionary personalizationDict) { + mLocale = locale; + setMainDict(mainDict); + mContactsDictionary = contactsDict; + if (mContactsDictionary != null) { + mDictMap.put(Dictionary.TYPE_CONTACTS, mContactsDictionary); + } + mUserDictionary = userDict; + if (mUserDictionary != null) { + mDictMap.put(Dictionary.TYPE_USER, mUserDictionary); + } + mUserHistoryDictionary = userHistoryDict; + if (mUserHistoryDictionary != null) { + mDictMap.put(Dictionary.TYPE_USER_HISTORY, mUserHistoryDictionary); + } + mPersonalizationDictionary = personalizationDict; + if (mPersonalizationDictionary != null) { + mDictMap.put(Dictionary.TYPE_PERSONALIZATION, mPersonalizationDictionary); + } + } + + public void setMainDict(final Dictionary mainDict) { + mMainDictionary = mainDict; + // Close old dictionary if exists. Main dictionary can be assigned multiple times. + final Dictionary oldDict; + if (mMainDictionary != null) { + oldDict = mDictMap.put(Dictionary.TYPE_MAIN, mMainDictionary); + } else { + oldDict = mDictMap.remove(Dictionary.TYPE_MAIN); + } + if (oldDict != null && mMainDictionary != oldDict) { + oldDict.close(); + } + } + + public boolean hasMainDict() { + return mMainDictionary != null; + } - private Dictionary mMainDictionary; - private ContactsBinaryDictionary mContactsDictionary; - private UserBinaryDictionary mUserDictionary; - private UserHistoryDictionary mUserHistoryDictionary; - private PersonalizationDictionary mPersonalizationDictionary; + public boolean hasContactsDict() { + return mContactsDictionary != null; + } + + public boolean hasUserDict() { + return mUserDictionary != null; + } + + public boolean hasUserHistoryDict() { + return mUserHistoryDictionary != null; + } - private final CountDownLatch mLatchForWaitingLoadingMainDictionary; + public boolean hasPersonalizationDict() { + return mPersonalizationDictionary != null; + } + } public interface DictionaryInitializationListener { public void onUpdateMainDictionaryAvailability(boolean isMainDictionaryAvailable); } - /** - * Creates instance for initialization or when the locale is changed. - * - * @param context the context - * @param locale the locale - * @param settingsValues current settings values to control what dictionaries should be used - * @param listener the listener - * @param oldDictionaryFacilitator the instance having old dictionaries. This is null when the - * instance is initially created. - */ - public DictionaryFacilitatorForSuggest(final Context context, final Locale locale, - final SettingsValues settingsValues, final DictionaryInitializationListener listener, - final DictionaryFacilitatorForSuggest oldDictionaryFacilitator) { - mContext = context; - mLocale = locale; - mLatchForWaitingLoadingMainDictionary = new CountDownLatch(1); - loadMainDict(context, locale, listener); - setUserDictionary(new UserBinaryDictionary(context, locale)); - resetAdditionalDictionaries(oldDictionaryFacilitator, settingsValues); + public DictionaryFacilitatorForSuggest() {} + + public Locale getLocale() { + return mDictionaries.mLocale; } - /** - * Creates instance for reloading the main dict. - * - * @param listener the listener - * @param oldDictionaryFacilitator the instance having old dictionaries. This must not be null. - */ - public DictionaryFacilitatorForSuggest(final DictionaryInitializationListener listener, - final DictionaryFacilitatorForSuggest oldDictionaryFacilitator) { - mContext = oldDictionaryFacilitator.mContext; - mLocale = oldDictionaryFacilitator.mLocale; - mLatchForWaitingLoadingMainDictionary = new CountDownLatch(1); - loadMainDict(mContext, mLocale, listener); - // Transfer user dictionary. - setUserDictionary(oldDictionaryFacilitator.mUserDictionary); - oldDictionaryFacilitator.removeDictionary(Dictionary.TYPE_USER); - // Transfer contacts dictionary. - setContactsDictionary(oldDictionaryFacilitator.mContactsDictionary); - oldDictionaryFacilitator.removeDictionary(Dictionary.TYPE_CONTACTS); - // Transfer user history dictionary. - setUserHistoryDictionary(oldDictionaryFacilitator.mUserHistoryDictionary); - oldDictionaryFacilitator.removeDictionary(Dictionary.TYPE_USER_HISTORY); - // Transfer personalization dictionary. - setPersonalizationDictionary(oldDictionaryFacilitator.mPersonalizationDictionary); - oldDictionaryFacilitator.removeDictionary(Dictionary.TYPE_PERSONALIZATION); + public void resetDictionaries(final Context context, final Locale newLocale, + final boolean useContactsDict, final boolean usePersonalizedDicts, + final boolean forceReloadMainDictionary, + final DictionaryInitializationListener listener) { + final boolean localeHasBeenChanged = !newLocale.equals(mDictionaries.mLocale); + // We always try to have the main dictionary. Other dictionaries can be unused. + final boolean reloadMainDictionary = localeHasBeenChanged || forceReloadMainDictionary; + final boolean closeContactsDictionary = localeHasBeenChanged || !useContactsDict; + final boolean closeUserDictionary = localeHasBeenChanged; + final boolean closeUserHistoryDictionary = localeHasBeenChanged || !usePersonalizedDicts; + final boolean closePersonalizationDictionary = + localeHasBeenChanged || !usePersonalizedDicts; + + final Dictionary newMainDict; + if (reloadMainDictionary) { + // The main dictionary will be asynchronously loaded. + newMainDict = null; + } else { + newMainDict = mDictionaries.mMainDictionary; + } + + // Open or move contacts dictionary. + final ContactsBinaryDictionary newContactsDict; + if (!closeContactsDictionary && mDictionaries.hasContactsDict()) { + newContactsDict = mDictionaries.mContactsDictionary; + } else if (useContactsDict) { + newContactsDict = new ContactsBinaryDictionary(context, newLocale); + } else { + newContactsDict = null; + } + + // Open or move user dictionary. + final UserBinaryDictionary newUserDictionary; + if (!closeUserDictionary && mDictionaries.hasUserDict()) { + newUserDictionary = mDictionaries.mUserDictionary; + } else { + newUserDictionary = new UserBinaryDictionary(context, newLocale); + } + + // Open or move user history dictionary. + final UserHistoryDictionary newUserHistoryDict; + if (!closeUserHistoryDictionary && mDictionaries.hasUserHistoryDict()) { + newUserHistoryDict = mDictionaries.mUserHistoryDictionary; + } else if (usePersonalizedDicts) { + newUserHistoryDict = PersonalizationHelper.getUserHistoryDictionary(context, newLocale); + } else { + newUserHistoryDict = null; + } + + // Open or move personalization dictionary. + final PersonalizationDictionary newPersonalizationDict; + if (!closePersonalizationDictionary && mDictionaries.hasPersonalizationDict()) { + newPersonalizationDict = mDictionaries.mPersonalizationDictionary; + } else if (usePersonalizedDicts) { + newPersonalizationDict = + PersonalizationHelper.getPersonalizationDictionary(context, newLocale); + } else { + newPersonalizationDict = null; + } + + // Replace Dictionaries. + final Dictionaries newDictionaries = new Dictionaries(newLocale, newMainDict, + newContactsDict, newUserDictionary, newUserHistoryDict, newPersonalizationDict); + if (listener != null) { + listener.onUpdateMainDictionaryAvailability(newDictionaries.hasMainDict()); + } + final Dictionaries oldDictionaries; + synchronized (mLock) { + oldDictionaries = mDictionaries; + mDictionaries = newDictionaries; + if (reloadMainDictionary) { + asyncReloadMainDictionary(context, newLocale, listener); + } + } + + // Clean up old dictionaries. + oldDictionaries.mDictMap.clear(); + if (reloadMainDictionary && oldDictionaries.hasMainDict()) { + oldDictionaries.mMainDictionary.close(); + } + if (closeContactsDictionary && oldDictionaries.hasContactsDict()) { + oldDictionaries.mContactsDictionary.close(); + } + if (closeUserDictionary && oldDictionaries.hasUserDict()) { + oldDictionaries.mUserDictionary.close(); + } + if (closeUserHistoryDictionary && oldDictionaries.hasUserHistoryDict()) { + oldDictionaries.mUserHistoryDictionary.close(); + } + if (closePersonalizationDictionary && oldDictionaries.hasPersonalizationDict()) { + oldDictionaries.mPersonalizationDictionary.close(); + } } - /** - * Creates instance for when the settings values have been changed. - * - * @param settingsValues the new settings values - * @param oldDictionaryFacilitator the instance having old dictionaries. This must not be null. - */ - // - public DictionaryFacilitatorForSuggest(final SettingsValues settingsValues, - final DictionaryFacilitatorForSuggest oldDictionaryFacilitator) { - mContext = oldDictionaryFacilitator.mContext; - mLocale = oldDictionaryFacilitator.mLocale; - mLatchForWaitingLoadingMainDictionary = new CountDownLatch(0); - // Transfer main dictionary. - setMainDictionary(oldDictionaryFacilitator.mMainDictionary); - oldDictionaryFacilitator.removeDictionary(Dictionary.TYPE_MAIN); - // Transfer user dictionary. - setUserDictionary(oldDictionaryFacilitator.mUserDictionary); - oldDictionaryFacilitator.removeDictionary(Dictionary.TYPE_USER); - // Transfer or create additional dictionaries depending on the settings values. - resetAdditionalDictionaries(oldDictionaryFacilitator, settingsValues); + private void asyncReloadMainDictionary(final Context context, final Locale locale, + final DictionaryInitializationListener listener) { + final CountDownLatch latchForWaitingLoadingMainDictionary = new CountDownLatch(1); + mLatchForWaitingLoadingMainDictionary = latchForWaitingLoadingMainDictionary; + ExecutorUtils.getExecutor("InitializeBinaryDictionary").execute(new Runnable() { + @Override + public void run() { + final Dictionary mainDict = + DictionaryFactory.createMainDictionaryFromManager(context, locale); + synchronized (mLock) { + if (locale.equals(mDictionaries.mLocale)) { + mDictionaries.setMainDict(mainDict); + } else { + // Dictionary facilitator has been reset for another locale. + mainDict.close(); + } + } + if (listener != null) { + listener.onUpdateMainDictionaryAvailability(mDictionaries.hasMainDict()); + } + latchForWaitingLoadingMainDictionary.countDown(); + } + }); } @UsedForTesting - public DictionaryFacilitatorForSuggest(final Context context, final Locale locale, + public void resetDictionariesForTesting(final Context context, final Locale locale, final ArrayList<String> dictionaryTypes, final HashMap<String, File> dictionaryFiles, final Map<String, Map<String, String>> additionalDictAttributes) { - mContext = context; - mLocale = locale; - mLatchForWaitingLoadingMainDictionary = new CountDownLatch(0); + Dictionary mainDictionary = null; + ContactsBinaryDictionary contactsDictionary = null; + UserBinaryDictionary userDictionary = null; + UserHistoryDictionary userHistoryDictionary = null; + PersonalizationDictionary personalizationDictionary = null; + for (final String dictType : dictionaryTypes) { if (dictType.equals(Dictionary.TYPE_MAIN)) { - final DictionaryCollection mainDictionary = - DictionaryFactory.createMainDictionaryFromManager(context, locale); - setMainDictionary(mainDictionary); + mainDictionary = DictionaryFactory.createMainDictionaryFromManager(context, locale); } else if (dictType.equals(Dictionary.TYPE_USER_HISTORY)) { - final UserHistoryDictionary userHistoryDictionary = + userHistoryDictionary = PersonalizationHelper.getUserHistoryDictionary(context, locale); // Staring with an empty user history dictionary for testing. // Testing program may populate this dictionary before actual testing. 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 = + personalizationDictionary = PersonalizationHelper.getPersonalizationDictionary(context, locale); // Staring with an empty personalization dictionary for testing. // Testing program may populate this dictionary before actual testing. 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( - context, locale, file); + userDictionary = new UserBinaryDictionary(context, locale, file); userDictionary.reloadDictionaryIfRequired(); userDictionary.waitAllTasksForTests(); - setUserDictionary(userDictionary); } else if (dictType.equals(Dictionary.TYPE_CONTACTS)) { final File file = dictionaryFiles.get(dictType); - final ContactsBinaryDictionary contactsDictionary = new ContactsBinaryDictionary( - context, locale, file); + contactsDictionary = new ContactsBinaryDictionary(context, locale, file); contactsDictionary.reloadDictionaryIfRequired(); contactsDictionary.waitAllTasksForTests(); - setContactsDictionary(contactsDictionary); } else { throw new RuntimeException("Unknown dictionary type: " + dictType); } } + mDictionaries = new Dictionaries(locale, mainDictionary, contactsDictionary, + userDictionary, userHistoryDictionary, personalizationDictionary); } - public boolean needsToBeRecreated(final Locale newLocale, - final SettingsValues newSettingsValues) { - return !mLocale.equals(newLocale) - || (newSettingsValues.mUseContactsDict != (mContactsDictionary != null)) - || (newSettingsValues.mUsePersonalizedDicts != (mUserHistoryDictionary != null)) - || (newSettingsValues.mUsePersonalizedDicts != hasPersonalizationDictionary()); - } - - public void close() { - final HashSet<Dictionary> dictionaries = CollectionUtils.newHashSet(); - dictionaries.addAll(mDictionaries.values()); - for (final Dictionary dictionary : dictionaries) { - dictionary.close(); + public void closeDictionaries() { + final Dictionaries dictionaries; + synchronized (mLock) { + dictionaries = mDictionaries; + mDictionaries = new Dictionaries(); } - } - - private void loadMainDict(final Context context, final Locale locale, - final DictionaryInitializationListener listener) { - mMainDictionary = null; - if (listener != null) { - listener.onUpdateMainDictionaryAvailability(hasMainDictionary()); + if (dictionaries.hasMainDict()) { + dictionaries.mMainDictionary.close(); + } + if (dictionaries.hasContactsDict()) { + dictionaries.mContactsDictionary.close(); + } + if (dictionaries.hasUserDict()) { + dictionaries.mUserDictionary.close(); + } + if (dictionaries.hasUserHistoryDict()) { + dictionaries.mUserHistoryDictionary.close(); + } + if (dictionaries.hasPersonalizationDict()) { + dictionaries.mPersonalizationDictionary.close(); } - ExecutorUtils.getExecutor("InitializeBinaryDictionary").execute(new Runnable() { - public void run() { - final DictionaryCollection newMainDict = - DictionaryFactory.createMainDictionaryFromManager(context, locale); - setMainDictionary(newMainDict); - if (listener != null) { - listener.onUpdateMainDictionaryAvailability(hasMainDictionary()); - } - mLatchForWaitingLoadingMainDictionary.countDown(); - } - }); } // The main dictionary could have been loaded asynchronously. Don't cache the return value // of this method. - public boolean hasMainDictionary() { - return null != mMainDictionary && mMainDictionary.isInitialized(); + public boolean hasInitializedMainDictionary() { + final Dictionaries dictionaries = mDictionaries; + return dictionaries.hasMainDict() && dictionaries.mMainDictionary.isInitialized(); } public boolean hasPersonalizationDictionary() { - return null != mPersonalizationDictionary; + return mDictionaries.hasPersonalizationDict(); } public void flushPersonalizationDictionary() { - if (hasPersonalizationDictionary()) { - mPersonalizationDictionary.flush(); + final PersonalizationDictionary personalizationDict = + mDictionaries.mPersonalizationDictionary; + if (personalizationDict != null) { + personalizationDict.flush(); } } @@ -253,177 +369,48 @@ public class DictionaryFacilitatorForSuggest { public void waitForLoadingDictionariesForTesting(final long timeout, final TimeUnit unit) throws InterruptedException { waitForLoadingMainDictionary(timeout, unit); - if (mContactsDictionary != null) { - mContactsDictionary.waitAllTasksForTests(); + final Dictionaries dictionaries = mDictionaries; + if (dictionaries.hasContactsDict()) { + dictionaries.mContactsDictionary.waitAllTasksForTests(); } - if (mUserDictionary != null) { - mUserDictionary.waitAllTasksForTests(); + if (dictionaries.hasUserDict()) { + dictionaries.mUserDictionary.waitAllTasksForTests(); } - if (mUserHistoryDictionary != null) { - mUserHistoryDictionary.waitAllTasksForTests(); - } - if (mPersonalizationDictionary != null) { - mPersonalizationDictionary.waitAllTasksForTests(); - } - } - - private void setMainDictionary(final Dictionary mainDictionary) { - mMainDictionary = mainDictionary; - addOrReplaceDictionary(Dictionary.TYPE_MAIN, mainDictionary); - } - - /** - * Sets an optional user dictionary resource to be loaded. The user dictionary is consulted - * before the main dictionary, if set. This refers to the system-managed user dictionary. - */ - private void setUserDictionary(final UserBinaryDictionary userDictionary) { - mUserDictionary = userDictionary; - addOrReplaceDictionary(Dictionary.TYPE_USER, userDictionary); - } - - /** - * Sets an optional contacts dictionary resource to be loaded. It is also possible to remove - * the contacts dictionary by passing null to this method. In this case no contacts dictionary - * won't be used. - */ - private void setContactsDictionary(final ContactsBinaryDictionary contactsDictionary) { - mContactsDictionary = contactsDictionary; - addOrReplaceDictionary(Dictionary.TYPE_CONTACTS, contactsDictionary); - } - - private void setUserHistoryDictionary(final UserHistoryDictionary userHistoryDictionary) { - mUserHistoryDictionary = userHistoryDictionary; - addOrReplaceDictionary(Dictionary.TYPE_USER_HISTORY, userHistoryDictionary); - } - - private void setPersonalizationDictionary( - final PersonalizationDictionary personalizationDictionary) { - mPersonalizationDictionary = personalizationDictionary; - addOrReplaceDictionary(Dictionary.TYPE_PERSONALIZATION, personalizationDictionary); - } - - /** - * Reset dictionaries that can be turned off according to the user settings. - * - * @param oldDictionaryFacilitator the instance having old dictionaries - * @param settingsValues current SettingsValues - */ - private void resetAdditionalDictionaries( - final DictionaryFacilitatorForSuggest oldDictionaryFacilitator, - final SettingsValues settingsValues) { - // Contacts dictionary - resetContactsDictionary(null != oldDictionaryFacilitator ? - oldDictionaryFacilitator.mContactsDictionary : null, settingsValues); - // User history dictionary & Personalization dictionary - resetPersonalizedDictionaries(oldDictionaryFacilitator, settingsValues); - } - - /** - * Set the user history dictionary and personalization dictionary according to the user - * settings. - * - * @param oldDictionaryFacilitator the instance that has been used - * @param settingsValues current settingsValues - */ - // TODO: Consolidate resetPersonalizedDictionaries() and resetContactsDictionary(). Call up the - // new method for each dictionary. - private void resetPersonalizedDictionaries( - final DictionaryFacilitatorForSuggest oldDictionaryFacilitator, - final SettingsValues settingsValues) { - final boolean shouldSetDictionaries = settingsValues.mUsePersonalizedDicts; - - final UserHistoryDictionary oldUserHistoryDictionary = (null == oldDictionaryFacilitator) ? - null : oldDictionaryFacilitator.mUserHistoryDictionary; - final PersonalizationDictionary oldPersonalizationDictionary = - (null == oldDictionaryFacilitator) ? null : - oldDictionaryFacilitator.mPersonalizationDictionary; - final UserHistoryDictionary userHistoryDictionaryToUse; - final PersonalizationDictionary personalizationDictionaryToUse; - if (!shouldSetDictionaries) { - userHistoryDictionaryToUse = null; - personalizationDictionaryToUse = null; - } else { - if (null != oldUserHistoryDictionary - && oldUserHistoryDictionary.mLocale.equals(mLocale)) { - userHistoryDictionaryToUse = oldUserHistoryDictionary; - } else { - userHistoryDictionaryToUse = - PersonalizationHelper.getUserHistoryDictionary(mContext, mLocale); - } - if (null != oldPersonalizationDictionary - && oldPersonalizationDictionary.mLocale.equals(mLocale)) { - personalizationDictionaryToUse = oldPersonalizationDictionary; - } else { - personalizationDictionaryToUse = - PersonalizationHelper.getPersonalizationDictionary(mContext, mLocale); - } + if (dictionaries.hasUserHistoryDict()) { + dictionaries.mUserHistoryDictionary.waitAllTasksForTests(); } - setUserHistoryDictionary(userHistoryDictionaryToUse); - setPersonalizationDictionary(personalizationDictionaryToUse); - } - - /** - * Set the contacts dictionary according to the user settings. - * - * This method takes an optional contacts dictionary to use when the locale hasn't changed - * since the contacts dictionary can be opened or closed as necessary depending on the settings. - * - * @param oldContactsDictionary an optional dictionary to use, or null - * @param settingsValues current settingsValues - */ - private void resetContactsDictionary(final ContactsBinaryDictionary oldContactsDictionary, - final SettingsValues settingsValues) { - final boolean shouldSetDictionary = settingsValues.mUseContactsDict; - final ContactsBinaryDictionary dictionaryToUse; - if (!shouldSetDictionary) { - // Make sure the dictionary is closed. If it is already closed, this is a no-op, - // so it's safe to call it anyways. - if (null != oldContactsDictionary) oldContactsDictionary.close(); - dictionaryToUse = null; - } else { - if (null != oldContactsDictionary) { - if (!oldContactsDictionary.mLocale.equals(mLocale)) { - // If the locale has changed then recreate the contacts dictionary. This - // allows locale dependent rules for handling bigram name predictions. - oldContactsDictionary.close(); - dictionaryToUse = new ContactsBinaryDictionary(mContext, mLocale); - } else { - // Make sure the old contacts dictionary is opened. If it is already open, - // this is a no-op, so it's safe to call it anyways. - oldContactsDictionary.reopen(mContext); - dictionaryToUse = oldContactsDictionary; - } - } else { - dictionaryToUse = new ContactsBinaryDictionary(mContext, mLocale); - } + if (dictionaries.hasPersonalizationDict()) { + dictionaries.mPersonalizationDictionary.waitAllTasksForTests(); } - setContactsDictionary(dictionaryToUse); } public boolean isUserDictionaryEnabled() { - if (mUserDictionary == null) { + final UserBinaryDictionary userDictionary = mDictionaries.mUserDictionary; + if (userDictionary == null) { return false; } - return mUserDictionary.mEnabled; + return userDictionary.mEnabled; } public void addWordToUserDictionary(String word) { - if (mUserDictionary == null) { + final UserBinaryDictionary userDictionary = mDictionaries.mUserDictionary; + if (userDictionary == null) { return; } - mUserDictionary.addWordToUserDictionary(word); + userDictionary.addWordToUserDictionary(word); } public void addToUserHistory(final String suggestion, final boolean wasAutoCapitalized, final String previousWord, final int timeStampInSeconds) { - if (mUserHistoryDictionary == null) { + final Dictionaries dictionaries = mDictionaries; + if (!dictionaries.hasUserHistoryDict()) { return; } final int maxFreq = getMaxFrequency(suggestion); if (maxFreq == 0) { return; } - final String suggestionLowerCase = suggestion.toLowerCase(mLocale); + final String suggestionLowerCase = suggestion.toLowerCase(dictionaries.mLocale); final String secondWord; if (wasAutoCapitalized) { secondWord = suggestionLowerCase; @@ -432,11 +419,11 @@ public class DictionaryFacilitatorForSuggest { // History dictionary in order to avoid suggesting them until the dictionary // consolidation is done. // TODO: Remove this hack when ready. - final int lowerCasefreqInMainDict = mMainDictionary != null ? - mMainDictionary.getFrequency(suggestionLowerCase) : + final int lowerCaseFreqInMainDict = dictionaries.hasMainDict() ? + dictionaries.mMainDictionary.getFrequency(suggestionLowerCase) : Dictionary.NOT_A_PROBABILITY; - if (maxFreq < lowerCasefreqInMainDict - && lowerCasefreqInMainDict >= CAPITALIZED_FORM_MAX_PROBABILITY_FOR_INSERT) { + if (maxFreq < lowerCaseFreqInMainDict + && lowerCaseFreqInMainDict >= CAPITALIZED_FORM_MAX_PROBABILITY_FOR_INSERT) { // Use lower cased word as the word can be a distracter of the popular word. secondWord = suggestionLowerCase; } else { @@ -446,54 +433,56 @@ public class DictionaryFacilitatorForSuggest { // We demote unrecognized words (frequency < 0, below) by specifying them as "invalid". // We don't add words with 0-frequency (assuming they would be profanity etc.). final boolean isValid = maxFreq > 0; - mUserHistoryDictionary.addToDictionary( + dictionaries.mUserHistoryDictionary.addToDictionary( previousWord, secondWord, isValid, timeStampInSeconds); } public void cancelAddingUserHistory(final String previousWord, final String committedWord) { - if (mUserHistoryDictionary != null) { - mUserHistoryDictionary.cancelAddingUserHistory(previousWord, committedWord); + final UserHistoryDictionary userHistoryDictionary = mDictionaries.mUserHistoryDictionary; + if (userHistoryDictionary != null) { + userHistoryDictionary.cancelAddingUserHistory(previousWord, committedWord); } } // TODO: Revise the way to fusion suggestion results. - public void getSuggestions(final WordComposer composer, + public SuggestionResults getSuggestionResults(final WordComposer composer, final String prevWord, final ProximityInfo proximityInfo, final boolean blockOffensiveWords, final int[] additionalFeaturesOptions, - final int sessionId, final Set<SuggestedWordInfo> suggestionSet, - final ArrayList<SuggestedWordInfo> rawSuggestions) { - for (final String key : mDictionaries.keySet()) { - final Dictionary dictionary = mDictionaries.get(key); + final int sessionId, final ArrayList<SuggestedWordInfo> rawSuggestions) { + final Dictionaries dictionaries = mDictionaries; + final Map<String, Dictionary> dictMap = dictionaries.mDictMap; + final SuggestionResults suggestionResults = + new SuggestionResults(dictionaries.mLocale, SuggestedWords.MAX_SUGGESTIONS); + for (final Dictionary dictionary : dictMap.values()) { if (null == dictionary) continue; final ArrayList<SuggestedWordInfo> dictionarySuggestions = dictionary.getSuggestionsWithSessionId(composer, prevWord, proximityInfo, blockOffensiveWords, additionalFeaturesOptions, sessionId); if (null == dictionarySuggestions) continue; - suggestionSet.addAll(dictionarySuggestions); + suggestionResults.addAll(dictionarySuggestions); if (null != rawSuggestions) { rawSuggestions.addAll(dictionarySuggestions); } } + return suggestionResults; } public boolean isValidMainDictWord(final String word) { - if (TextUtils.isEmpty(word) || !hasMainDictionary()) { + final Dictionaries dictionaries = mDictionaries; + if (TextUtils.isEmpty(word) || !dictionaries.hasMainDict()) { return false; } - return mMainDictionary.isValidWord(word); + return dictionaries.mMainDictionary.isValidWord(word); } public boolean isValidWord(final String word, final boolean ignoreCase) { if (TextUtils.isEmpty(word)) { return false; } - final String lowerCasedWord = word.toLowerCase(mLocale); - for (final String key : mDictionaries.keySet()) { - final Dictionary dictionary = mDictionaries.get(key); - // It's unclear how realistically 'dictionary' can be null, but the monkey is somehow - // managing to get null in here. Presumably the language is changing to a language with - // no main dictionary and the monkey manages to type a whole word before the thread - // that reads the dictionary is started or something? + final Dictionaries dictionaries = mDictionaries; + final String lowerCasedWord = word.toLowerCase(dictionaries.mLocale); + final Map<String, Dictionary> dictMap = dictionaries.mDictMap; + for (final Dictionary dictionary : dictMap.values()) { // Ideally the passed map would come out of a {@link java.util.concurrent.Future} and // would be immutable once it's finished initializing, but concretely a null test is // probably good enough for the time being. @@ -511,9 +500,8 @@ public class DictionaryFacilitatorForSuggest { return Dictionary.NOT_A_PROBABILITY; } int maxFreq = -1; - for (final String key : mDictionaries.keySet()) { - final Dictionary dictionary = mDictionaries.get(key); - if (null == dictionary) continue; + final Map<String, Dictionary> dictMap = mDictionaries.mDictMap; + for (final Dictionary dictionary : dictMap.values()) { final int tempFreq = dictionary.getFrequency(word); if (tempFreq >= maxFreq) { maxFreq = tempFreq; @@ -522,61 +510,50 @@ public class DictionaryFacilitatorForSuggest { return maxFreq; } - private void removeDictionary(final String key) { - mDictionaries.remove(key); - } - - private void addOrReplaceDictionary(final String key, final Dictionary dict) { - final Dictionary oldDict; - if (dict == null) { - oldDict = mDictionaries.remove(key); - } else { - oldDict = mDictionaries.put(key, dict); - } - if (oldDict != null && dict != oldDict) { - oldDict.close(); - } - } public void clearUserHistoryDictionary() { - if (mUserHistoryDictionary == null) { + final UserHistoryDictionary userHistoryDict = mDictionaries.mUserHistoryDictionary; + if (userHistoryDict == null) { return; } - mUserHistoryDictionary.clearAndFlushDictionary(); + userHistoryDict.clearAndFlushDictionary(); } // This method gets called only when the IME receives a notification to remove the // personalization dictionary. public void clearPersonalizationDictionary() { - if (!hasPersonalizationDictionary()) { + final PersonalizationDictionary personalizationDict = + mDictionaries.mPersonalizationDictionary; + if (personalizationDict == null) { return; } - mPersonalizationDictionary.clearAndFlushDictionary(); + personalizationDict.clearAndFlushDictionary(); } public void addMultipleDictionaryEntriesToPersonalizationDictionary( final ArrayList<LanguageModelParam> languageModelParams, final ExpandableBinaryDictionary.AddMultipleDictionaryEntriesCallback callback) { - if (!hasPersonalizationDictionary()) { + final PersonalizationDictionary personalizationDict = + mDictionaries.mPersonalizationDictionary; + if (personalizationDict == null) { if (callback != null) { callback.onFinished(); } return; } - mPersonalizationDictionary.addMultipleDictionaryEntriesToDictionary(languageModelParams, - callback); + personalizationDict.addMultipleDictionaryEntriesToDictionary(languageModelParams, callback); } public void dumpDictionaryForDebug(final String dictName) { final ExpandableBinaryDictionary dictToDump; if (dictName.equals(Dictionary.TYPE_CONTACTS)) { - dictToDump = mContactsDictionary; + dictToDump = mDictionaries.mContactsDictionary; } else if (dictName.equals(Dictionary.TYPE_USER)) { - dictToDump = mUserDictionary; + dictToDump = mDictionaries.mUserDictionary; } else if (dictName.equals(Dictionary.TYPE_USER_HISTORY)) { - dictToDump = mUserHistoryDictionary; + dictToDump = mDictionaries.mUserHistoryDictionary; } else if (dictName.equals(Dictionary.TYPE_PERSONALIZATION)) { - dictToDump = mPersonalizationDictionary; + dictToDump = mDictionaries.mPersonalizationDictionary; } else { dictToDump = null; } diff --git a/java/src/com/android/inputmethod/latin/LatinIME.java b/java/src/com/android/inputmethod/latin/LatinIME.java index 38e386493..0c0be82df 100644 --- a/java/src/com/android/inputmethod/latin/LatinIME.java +++ b/java/src/com/android/inputmethod/latin/LatinIME.java @@ -215,7 +215,7 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen false /* includeResumedWordInSuggestions */); break; case MSG_REOPEN_DICTIONARIES: - latinIme.initSuggest(); + latinIme.resetSuggest(); // In theory we could call latinIme.updateSuggestionStrip() right away, but // in the practice, the dictionary is not finished opening yet so we wouldn't // get any suggestions. Wait one frame. @@ -478,10 +478,12 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen // TODO: Resolve mutual dependencies of {@link #loadSettings()} and {@link #initSuggest()}. loadSettings(); - initSuggest(); + resetSuggest(); if (ProductionFlag.USES_DEVELOPMENT_ONLY_DIAGNOSTICS) { ResearchLogger.getInstance().init(this, mKeyboardSwitcher); + ResearchLogger.getInstance().initDictionary( + mInputLogic.mSuggest.mDictionaryFacilitator); } // Register to receive ringer mode change and network state change. @@ -520,31 +522,15 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen // This method is called on startup and language switch, before the new layout has // been displayed. Opening dictionaries never affects responsivity as dictionaries are // asynchronously loaded. - initOrResetSuggestForSettingsValues(mInputLogic.mSuggest, locale, currentSettingsValues); - } - - private void initOrResetSuggestForSettingsValues(final Suggest oldSuggest, - final Locale locale, final SettingsValues settingsValues) { - if (!mHandler.hasPendingReopenDictionaries() && oldSuggest != null) { - // May need to reset dictionaries depending on the user settings. - final DictionaryFacilitatorForSuggest oldDictionaryFacilitator = - oldSuggest.mDictionaryFacilitator; - if (!oldDictionaryFacilitator.needsToBeRecreated(locale, settingsValues)) { - // Continue to use the same dictionary facilitator if no configuration has changed. - refreshPersonalizationDictionarySession(); - return; - } - final DictionaryFacilitatorForSuggest dictionaryFacilitator = - new DictionaryFacilitatorForSuggest(settingsValues, oldDictionaryFacilitator); - // Create Suggest instance with the new dictionary facilitator. - replaceSuggest(new Suggest(oldSuggest, dictionaryFacilitator)); - } else if (oldSuggest == null) { - initSuggest(); + if (!mHandler.hasPendingReopenDictionaries()) { + resetSuggestForLocale(locale); } + refreshPersonalizationDictionarySession(); } private void refreshPersonalizationDictionarySession() { - final Suggest suggest = mInputLogic.mSuggest; + final DictionaryFacilitatorForSuggest dictionaryFacilitator = + mInputLogic.mSuggest.mDictionaryFacilitator; final boolean shouldKeepUserHistoryDictionaries; final boolean shouldKeepPersonalizationDictionaries; if (mSettings.getCurrent().mUsePersonalizedDicts) { @@ -559,17 +545,13 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen if (!shouldKeepUserHistoryDictionaries) { // Remove user history dictionaries. PersonalizationHelper.removeAllUserHistoryDictionaries(this); - if (suggest != null) { - suggest.mDictionaryFacilitator.clearUserHistoryDictionary(); - } + dictionaryFacilitator.clearUserHistoryDictionary(); } if (!shouldKeepPersonalizationDictionaries) { // Remove personalization dictionaries. PersonalizationHelper.removeAllPersonalizationDictionaries(this); PersonalizationDictionarySessionRegistrar.resetAll(this); } else { - final DictionaryFacilitatorForSuggest dictionaryFacilitator = - (suggest == null) ? null : suggest.mDictionaryFacilitator; PersonalizationDictionarySessionRegistrar.init(this, dictionaryFacilitator); } } @@ -583,7 +565,7 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen } } - private void initSuggest() { + private void resetSuggest() { final Locale switcherSubtypeLocale = mSubtypeSwitcher.getCurrentSubtypeLocale(); final String switcherLocaleStr = switcherSubtypeLocale.toString(); final Locale subtypeLocale; @@ -599,47 +581,42 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen } else { subtypeLocale = switcherSubtypeLocale; } - initSuggestForLocale(mInputLogic.mSuggest, subtypeLocale); + resetSuggestForLocale(subtypeLocale); } - private void initSuggestForLocale(final Suggest oldSuggest, final Locale locale) { - final SettingsValues settingsValues = mSettings.getCurrent(); - final DictionaryFacilitatorForSuggest oldDictionaryFacilitator = - (oldSuggest == null) ? null : oldSuggest.mDictionaryFacilitator; - // Creates new dictionary facilitator for the new locale. + /** + * Reset suggest by loading dictionaries for the locale and the current settings values. + * + * @param locale the locale + */ + private void resetSuggestForLocale(final Locale locale) { final DictionaryFacilitatorForSuggest dictionaryFacilitator = - new DictionaryFacilitatorForSuggest(this /* context */, locale, settingsValues, - this /* DictionaryInitializationListener */, oldDictionaryFacilitator); - final Suggest newSuggest = new Suggest(locale, dictionaryFacilitator); + mInputLogic.mSuggest.mDictionaryFacilitator; + final SettingsValues settingsValues = mSettings.getCurrent(); + dictionaryFacilitator.resetDictionaries(this /* context */, locale, + settingsValues.mUseContactsDict, settingsValues.mUsePersonalizedDicts, + false /* forceReloadMainDictionary */, this); if (settingsValues.mCorrectionEnabled) { - newSuggest.setAutoCorrectionThreshold(settingsValues.mAutoCorrectionThreshold); + mInputLogic.mSuggest.setAutoCorrectionThreshold( + settingsValues.mAutoCorrectionThreshold); } - replaceSuggest(newSuggest); } + /** + * Reset suggest by loading the main dictionary of the current locale. + */ /* package private */ void resetSuggestMainDict() { - final DictionaryFacilitatorForSuggest oldDictionaryFacilitator = - mInputLogic.mSuggest.mDictionaryFacilitator; final DictionaryFacilitatorForSuggest dictionaryFacilitator = - new DictionaryFacilitatorForSuggest(this /* listener */, oldDictionaryFacilitator); - replaceSuggest(new Suggest(mInputLogic.mSuggest /* oldSuggest */, dictionaryFacilitator)); - } - - private void replaceSuggest(final Suggest newSuggest) { - if (ProductionFlag.USES_DEVELOPMENT_ONLY_DIAGNOSTICS) { - ResearchLogger.getInstance().initDictionary(newSuggest.mDictionaryFacilitator); - } - mInputLogic.replaceSuggest(newSuggest); - refreshPersonalizationDictionarySession(); + mInputLogic.mSuggest.mDictionaryFacilitator; + final SettingsValues settingsValues = mSettings.getCurrent(); + dictionaryFacilitator.resetDictionaries(this /* context */, + dictionaryFacilitator.getLocale(), settingsValues.mUseContactsDict, + settingsValues.mUsePersonalizedDicts, true /* forceReloadMainDictionary */, this); } @Override public void onDestroy() { - final Suggest suggest = mInputLogic.mSuggest; - if (suggest != null) { - suggest.close(); - mInputLogic.mSuggest = null; - } + mInputLogic.mSuggest.mDictionaryFacilitator.closeDictionaries(); mSettings.onDestroy(); unregisterReceiver(mReceiver); if (ProductionFlag.USES_DEVELOPMENT_ONLY_DIAGNOSTICS) { @@ -802,10 +779,10 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen // Note: the following does a round-trip IPC on the main thread: be careful final Locale currentLocale = mSubtypeSwitcher.getCurrentSubtypeLocale(); - Suggest suggest = mInputLogic.mSuggest; - if (null != suggest && null != currentLocale && !currentLocale.equals(suggest.mLocale)) { - initSuggest(); - suggest = mInputLogic.mSuggest; + final Suggest suggest = mInputLogic.mSuggest; + if (null != currentLocale && !currentLocale.equals(suggest.getLocale())) { + // TODO: Do this automatically. + resetSuggest(); } // TODO[IL]: Can the following be moved to InputLogic#startInput? @@ -833,13 +810,12 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen if (isDifferentTextField || !currentSettingsValues.hasSameOrientation(getResources().getConfiguration())) { loadSettings(); - suggest = mInputLogic.mSuggest; } if (isDifferentTextField) { mainKeyboardView.closing(); currentSettingsValues = mSettings.getCurrent(); - if (suggest != null && currentSettingsValues.mCorrectionEnabled) { + if (currentSettingsValues.mCorrectionEnabled) { suggest.setAutoCorrectionThreshold(currentSettingsValues.mAutoCorrectionThreshold); } @@ -865,8 +841,8 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen mHandler.cancelUpdateSuggestionStrip(); - mainKeyboardView.setMainDictionaryAvailability(null != suggest - ? suggest.mDictionaryFacilitator.hasMainDictionary() : false); + mainKeyboardView.setMainDictionaryAvailability( + suggest.mDictionaryFacilitator.hasInitializedMainDictionary()); mainKeyboardView.setKeyPreviewPopupEnabled(currentSettingsValues.mKeyPreviewPopupOn, currentSettingsValues.mKeyPreviewPopupDismissDelay); mainKeyboardView.setSlidingKeyInputPreviewEnabled( @@ -1407,8 +1383,7 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen public void getSuggestedWords(final int sessionId, final int sequenceNumber, final OnGetSuggestedWordsCallback callback) { final Keyboard keyboard = mKeyboardSwitcher.getKeyboard(); - final Suggest suggest = mInputLogic.mSuggest; - if (keyboard == null || suggest == null) { + if (keyboard == null) { callback.onGetSuggestedWords(SuggestedWords.EMPTY); return; } @@ -1437,7 +1412,7 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen } } } - suggest.getSuggestedWords(mInputLogic.mWordComposer, + mInputLogic.mSuggest.getSuggestedWords(mInputLogic.mWordComposer, mInputLogic.mWordComposer.getPreviousWordForSuggestion(), keyboard.getProximityInfo(), currentSettings.mBlockPotentiallyOffensive, currentSettings.mCorrectionEnabled, additionalFeaturesOptions, sessionId, @@ -1722,12 +1697,10 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen // DO NOT USE THIS for any other purpose than testing. This can break the keyboard badly. @UsedForTesting /* package for test */ void replaceDictionariesForTest(final Locale locale) { - final DictionaryFacilitatorForSuggest oldDictionaryFacilitator = - mInputLogic.mSuggest.mDictionaryFacilitator; - final DictionaryFacilitatorForSuggest dictionaryFacilitator = - new DictionaryFacilitatorForSuggest(this, locale, mSettings.getCurrent(), - this /* listener */, oldDictionaryFacilitator); - replaceSuggest(new Suggest(locale, dictionaryFacilitator)); + final SettingsValues settingsValues = mSettings.getCurrent(); + mInputLogic.mSuggest.mDictionaryFacilitator.resetDictionaries(this, locale, + settingsValues.mUseContactsDict, settingsValues.mUsePersonalizedDicts, + false /* forceReloadMainDictionary */, this /* listener */); } // DO NOT USE THIS for any other purpose than testing. @@ -1738,8 +1711,10 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen } public void dumpDictionaryForDebug(final String dictName) { - if (mInputLogic.mSuggest == null) { - initSuggest(); + final DictionaryFacilitatorForSuggest dictionaryFacilitator = + mInputLogic.mSuggest.mDictionaryFacilitator; + if (dictionaryFacilitator.getLocale() == null) { + resetSuggest(); } mInputLogic.mSuggest.mDictionaryFacilitator.dumpDictionaryForDebug(dictName); } diff --git a/java/src/com/android/inputmethod/latin/Suggest.java b/java/src/com/android/inputmethod/latin/Suggest.java index ba64028ca..6985d9a84 100644 --- a/java/src/com/android/inputmethod/latin/Suggest.java +++ b/java/src/com/android/inputmethod/latin/Suggest.java @@ -18,17 +18,17 @@ package com.android.inputmethod.latin; import android.text.TextUtils; +import com.android.inputmethod.event.Event; import com.android.inputmethod.keyboard.ProximityInfo; import com.android.inputmethod.latin.SuggestedWords.SuggestedWordInfo; import com.android.inputmethod.latin.define.ProductionFlag; import com.android.inputmethod.latin.utils.AutoCorrectionUtils; import com.android.inputmethod.latin.utils.BinaryDictionaryUtils; -import com.android.inputmethod.latin.utils.BoundedTreeSet; import com.android.inputmethod.latin.utils.CollectionUtils; import com.android.inputmethod.latin.utils.StringUtils; +import com.android.inputmethod.latin.utils.SuggestionResults; import java.util.ArrayList; -import java.util.Comparator; import java.util.Locale; /** @@ -53,29 +53,16 @@ public final class Suggest { private static final int SUPPRESS_SUGGEST_THRESHOLD = -2000000000; private static final boolean DBG = LatinImeLogger.sDBG; - public final DictionaryFacilitatorForSuggest mDictionaryFacilitator; + public final DictionaryFacilitatorForSuggest mDictionaryFacilitator = + new DictionaryFacilitatorForSuggest(); private float mAutoCorrectionThreshold; - // Locale used for upper- and title-casing words - public final Locale mLocale; - - // TODO: Move dictionaryFacilitator constructing logics from LatinIME to Suggest. - public Suggest(final Locale locale, - final DictionaryFacilitatorForSuggest dictionaryFacilitator) { - mLocale = locale; - mDictionaryFacilitator = dictionaryFacilitator; - } - - // Creates instance with new dictionary facilitator. - public Suggest(final Suggest oldSuggst, - final DictionaryFacilitatorForSuggest dictionaryFacilitator) { - mLocale = oldSuggst.mLocale; - mAutoCorrectionThreshold = oldSuggst.mAutoCorrectionThreshold; - mDictionaryFacilitator = dictionaryFacilitator; + public Locale getLocale() { + return mDictionaryFacilitator.getLocale(); } - public void setAutoCorrectionThreshold(float threshold) { + public void setAutoCorrectionThreshold(final float threshold) { mAutoCorrectionThreshold = threshold; } @@ -108,9 +95,6 @@ public final class Suggest { final int[] additionalFeaturesOptions, final int sequenceNumber, final OnGetSuggestedWordsCallback callback) { final int trailingSingleQuotesCount = wordComposer.trailingSingleQuotesCount(); - final BoundedTreeSet suggestionsSet = new BoundedTreeSet(sSuggestedWordInfoComparator, - SuggestedWords.MAX_SUGGESTIONS); - final String typedWord = wordComposer.getTypedWord(); final String consideredWord = trailingSingleQuotesCount > 0 ? typedWord.substring(0, typedWord.length() - trailingSingleQuotesCount) @@ -121,7 +105,11 @@ public final class Suggest { if (trailingSingleQuotesCount > 0) { wordComposerForLookup = new WordComposer(wordComposer); for (int i = trailingSingleQuotesCount - 1; i >= 0; --i) { - wordComposerForLookup.deleteLast(); + // TODO: do not create a fake event for this. Ideally the word composer should know + // how to give out the word without trailing quotes and we can remove this entirely + wordComposerForLookup.deleteLast(Event.createSoftwareKeypressEvent( + Event.NOT_A_CODE_POINT, Constants.CODE_DELETE, + Constants.NOT_A_COORDINATE, Constants.NOT_A_COORDINATE)); } } else { wordComposerForLookup = wordComposer; @@ -132,20 +120,20 @@ public final class Suggest { } else { rawSuggestions = null; } - mDictionaryFacilitator.getSuggestions(wordComposerForLookup, prevWordForBigram, - proximityInfo, blockOffensiveWords, additionalFeaturesOptions, SESSION_TYPING, - suggestionsSet, rawSuggestions); + final SuggestionResults suggestionResults = mDictionaryFacilitator.getSuggestionResults( + wordComposerForLookup, prevWordForBigram, proximityInfo, blockOffensiveWords, + additionalFeaturesOptions, SESSION_TYPING, rawSuggestions); final boolean isFirstCharCapitalized = wordComposer.isFirstCharCapitalized(); final boolean isAllUpperCase = wordComposer.isAllUpperCase(); final String firstSuggestion; final String whitelistedWord; - if (suggestionsSet.isEmpty()) { + if (suggestionResults.isEmpty()) { whitelistedWord = firstSuggestion = null; } else { final SuggestedWordInfo firstSuggestedWordInfo = getTransformedSuggestedWordInfo( - suggestionsSet.first(), mLocale, isAllUpperCase, isFirstCharCapitalized, - trailingSingleQuotesCount); + suggestionResults.first(), suggestionResults.mLocale, isAllUpperCase, + isFirstCharCapitalized, trailingSingleQuotesCount); firstSuggestion = firstSuggestedWordInfo.mWord; if (SuggestedWordInfo.KIND_WHITELIST != firstSuggestedWordInfo.mKind) { whitelistedWord = null; @@ -175,10 +163,10 @@ public final class Suggest { // 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 || isPrediction - || suggestionsSet.isEmpty() || wordComposer.hasDigits() + || suggestionResults.isEmpty() || wordComposer.hasDigits() || wordComposer.isMostlyCaps() || wordComposer.isResumed() - || !mDictionaryFacilitator.hasMainDictionary() - || SuggestedWordInfo.KIND_SHORTCUT == suggestionsSet.first().mKind) { + || !mDictionaryFacilitator.hasInitializedMainDictionary() + || SuggestedWordInfo.KIND_SHORTCUT == suggestionResults.first().mKind) { // If we don't have a main dictionary, we never want to auto-correct. The reason for // this is, the user may have a contact whose name happens to match a valid word in // their language, and it will unexpectedly auto-correct. For example, if the user @@ -190,17 +178,17 @@ public final class Suggest { hasAutoCorrection = false; } else { hasAutoCorrection = AutoCorrectionUtils.suggestionExceedsAutoCorrectionThreshold( - suggestionsSet.first(), consideredWord, mAutoCorrectionThreshold); + suggestionResults.first(), consideredWord, mAutoCorrectionThreshold); } final ArrayList<SuggestedWordInfo> suggestionsContainer = - CollectionUtils.newArrayList(suggestionsSet); + CollectionUtils.newArrayList(suggestionResults); final int suggestionsCount = suggestionsContainer.size(); if (isFirstCharCapitalized || isAllUpperCase || 0 != trailingSingleQuotesCount) { for (int i = 0; i < suggestionsCount; ++i) { final SuggestedWordInfo wordInfo = suggestionsContainer.get(i); final SuggestedWordInfo transformedWordInfo = getTransformedSuggestedWordInfo( - wordInfo, mLocale, isAllUpperCase, isFirstCharCapitalized, + wordInfo, suggestionResults.mLocale, isAllUpperCase, isFirstCharCapitalized, trailingSingleQuotesCount); suggestionsContainer.set(i, transformedWordInfo); } @@ -244,23 +232,21 @@ public final class Suggest { final boolean blockOffensiveWords, final int[] additionalFeaturesOptions, final int sessionId, final int sequenceNumber, final OnGetSuggestedWordsCallback callback) { - final BoundedTreeSet suggestionsSet = new BoundedTreeSet(sSuggestedWordInfoComparator, - SuggestedWords.MAX_SUGGESTIONS); final ArrayList<SuggestedWordInfo> rawSuggestions; if (ProductionFlag.INCLUDE_RAW_SUGGESTIONS) { rawSuggestions = CollectionUtils.newArrayList(); } else { rawSuggestions = null; } - mDictionaryFacilitator.getSuggestions(wordComposer, prevWordForBigram, proximityInfo, - blockOffensiveWords, additionalFeaturesOptions, sessionId, suggestionsSet, - rawSuggestions); - for (SuggestedWordInfo wordInfo : suggestionsSet) { + final SuggestionResults suggestionResults = mDictionaryFacilitator.getSuggestionResults( + wordComposer, prevWordForBigram, proximityInfo, blockOffensiveWords, + additionalFeaturesOptions, sessionId, rawSuggestions); + for (SuggestedWordInfo wordInfo : suggestionResults) { LatinImeLogger.onAddSuggestedWord(wordInfo.mWord, wordInfo.mSourceDict.mDictType); } final ArrayList<SuggestedWordInfo> suggestionsContainer = - CollectionUtils.newArrayList(suggestionsSet); + CollectionUtils.newArrayList(suggestionResults); final int suggestionsCount = suggestionsContainer.size(); final boolean isFirstCharCapitalized = wordComposer.wasShiftedNoLock(); final boolean isAllUpperCase = wordComposer.isAllUpperCase(); @@ -268,7 +254,7 @@ public final class Suggest { for (int i = 0; i < suggestionsCount; ++i) { final SuggestedWordInfo wordInfo = suggestionsContainer.get(i); final SuggestedWordInfo transformedWordInfo = getTransformedSuggestedWordInfo( - wordInfo, mLocale, isAllUpperCase, isFirstCharCapitalized, + wordInfo, suggestionResults.mLocale, isAllUpperCase, isFirstCharCapitalized, 0 /* trailingSingleQuotesCount */); suggestionsContainer.set(i, transformedWordInfo); } @@ -326,22 +312,6 @@ public final class Suggest { return suggestionsList; } - private static final class SuggestedWordInfoComparator - implements Comparator<SuggestedWordInfo> { - // This comparator ranks the word info with the higher frequency first. That's because - // that's the order we want our elements in. - @Override - public int compare(final SuggestedWordInfo o1, final SuggestedWordInfo o2) { - if (o1.mScore > o2.mScore) return -1; - if (o1.mScore < o2.mScore) return 1; - if (o1.mCodePointCount < o2.mCodePointCount) return -1; - if (o1.mCodePointCount > o2.mCodePointCount) return 1; - return o1.mWord.compareTo(o2.mWord); - } - } - private static final SuggestedWordInfoComparator sSuggestedWordInfoComparator = - new SuggestedWordInfoComparator(); - /* package for test */ static SuggestedWordInfo getTransformedSuggestedWordInfo( final SuggestedWordInfo wordInfo, final Locale locale, final boolean isAllUpperCase, final boolean isFirstCharCapitalized, final int trailingSingleQuotesCount) { @@ -365,8 +335,4 @@ public final class Suggest { wordInfo.mSourceDict, wordInfo.mIndexOfTouchPointOfSecondWord, wordInfo.mAutoCommitFirstWordConfidence); } - - public void close() { - mDictionaryFacilitator.close(); - } } diff --git a/java/src/com/android/inputmethod/latin/WordComposer.java b/java/src/com/android/inputmethod/latin/WordComposer.java index a60ca3d41..a955f375b 100644 --- a/java/src/com/android/inputmethod/latin/WordComposer.java +++ b/java/src/com/android/inputmethod/latin/WordComposer.java @@ -314,29 +314,14 @@ public final class WordComposer { } /** - * Delete the last keystroke as a result of hitting backspace. + * Delete the last composing unit as a result of hitting backspace. */ - public void deleteLast() { - final int size = size(); - if (size > 0) { - // Note: mTypedWord.length() and mCodes.length differ when there are surrogate pairs - final int stringBuilderLength = mTypedWord.length(); - if (stringBuilderLength < size) { - throw new RuntimeException( - "In WordComposer: mCodes and mTypedWords have non-matching lengths"); - } - final int lastChar = mTypedWord.codePointBefore(stringBuilderLength); - // TODO: with events and composition, this is absolutely not necessarily true. - mEvents.remove(mEvents.size() - 1); - if (Character.isSupplementaryCodePoint(lastChar)) { - mTypedWord.delete(stringBuilderLength - 2, stringBuilderLength); - } else { - mTypedWord.deleteCharAt(stringBuilderLength - 1); - } - if (Character.isUpperCase(lastChar)) mCapsCount--; - if (Character.isDigit(lastChar)) mDigitsCount--; - refreshSize(); - } + public void deleteLast(final Event event) { + mCombinerChain.processEvent(mEvents, event); + mTypedWord.replace(0, mTypedWord.length(), + mCombinerChain.getComposingWordWithCombiningFeedback().toString()); + mEvents.add(event); + refreshSize(); // We may have deleted the last one. if (0 == size()) { mIsFirstCharCapitalized = false; diff --git a/java/src/com/android/inputmethod/latin/inputlogic/InputLogic.java b/java/src/com/android/inputmethod/latin/inputlogic/InputLogic.java index 36b30eabe..3a59be198 100644 --- a/java/src/com/android/inputmethod/latin/inputlogic/InputLogic.java +++ b/java/src/com/android/inputmethod/latin/inputlogic/InputLogic.java @@ -32,6 +32,7 @@ import com.android.inputmethod.event.InputTransaction; import com.android.inputmethod.keyboard.KeyboardSwitcher; import com.android.inputmethod.latin.Constants; import com.android.inputmethod.latin.Dictionary; +import com.android.inputmethod.latin.DictionaryFacilitatorForSuggest; import com.android.inputmethod.latin.InputPointers; import com.android.inputmethod.latin.LastComposedWord; import com.android.inputmethod.latin.LatinIME; @@ -77,8 +78,7 @@ public final class InputLogic { private int mSpaceState; // Never null public SuggestedWords mSuggestedWords = SuggestedWords.EMPTY; - // TODO: mSuggest should be touched by a single thread. - public volatile Suggest mSuggest; + public final Suggest mSuggest = new Suggest(); public LastComposedWord mLastComposedWord = LastComposedWord.NOT_A_COMPOSED_WORD; public final WordComposer mWordComposer; @@ -106,15 +106,6 @@ public final class InputLogic { mInputLogicHandler = InputLogicHandler.NULL_HANDLER; } - // Replace the old Suggest with the passed Suggest and close it. - public void replaceSuggest(final Suggest newSuggest) { - final Suggest oldSuggest = mSuggest; - mSuggest = newSuggest; - if (oldSuggest != null) { - oldSuggest.close(); - } - } - /** * Initializes the input logic for input in an editor. * @@ -283,23 +274,18 @@ public final class InputLogic { // We should show the "Touch again to save" hint if the user pressed the first entry // AND it's in none of our current dictionaries (main, user or otherwise). - // Please note that if mSuggest is null, it means that everything is off: suggestion - // and correction, so we shouldn't try to show the hint - final Suggest suggest = mSuggest; + final DictionaryFacilitatorForSuggest dictionaryFacilitator = + mSuggest.mDictionaryFacilitator; final boolean showingAddToDictionaryHint = (SuggestedWordInfo.KIND_TYPED == suggestionInfo.mKind || SuggestedWordInfo.KIND_OOV_CORRECTION == suggestionInfo.mKind) - && suggest != null - // If the suggestion is not in the dictionary, the hint should be shown. - && !suggest.mDictionaryFacilitator.isValidWord(suggestion, - true /* ignoreCase */); + && !dictionaryFacilitator.isValidWord(suggestion, true /* ignoreCase */); if (settingsValues.mIsInternal) { LatinImeLoggerUtils.onSeparator((char)Constants.CODE_SPACE, Constants.NOT_A_COORDINATE, Constants.NOT_A_COORDINATE); } - if (showingAddToDictionaryHint - && suggest.mDictionaryFacilitator.isUserDictionaryEnabled()) { + if (showingAddToDictionaryHint && dictionaryFacilitator.isUserDictionaryEnabled()) { mSuggestionStripViewAccessor.showAddToDictionaryHint(suggestion); } else { // If we're not showing the "Touch again to save", then update the suggestion strip. @@ -922,7 +908,7 @@ public final class InputLogic { mWordComposer.reset(); mWordComposer.setRejectedBatchModeSuggestion(rejectedSuggestion); } else { - mWordComposer.deleteLast(); + mWordComposer.deleteLast(inputTransaction.mEvent); } mConnection.setComposingText(getTextWithUnderline(mWordComposer.getTypedWord()), 1); handler.postUpdateSuggestionStrip(); @@ -1231,20 +1217,17 @@ public final class InputLogic { if (!settingsValues.mCorrectionEnabled) return; if (TextUtils.isEmpty(suggestion)) return; - final Suggest suggest = mSuggest; - if (suggest == null) return; - final boolean wasAutoCapitalized = mWordComposer.wasAutoCapitalized() && !mWordComposer.isMostlyCaps(); final int timeStampInSeconds = (int)TimeUnit.MILLISECONDS.toSeconds( System.currentTimeMillis()); - suggest.mDictionaryFacilitator.addToUserHistory(suggestion, wasAutoCapitalized, prevWord, + mSuggest.mDictionaryFacilitator.addToUserHistory(suggestion, wasAutoCapitalized, prevWord, timeStampInSeconds); } public void performUpdateSuggestionStripSync(final SettingsValues settingsValues) { // Check if we have a suggestion engine attached. - if (mSuggest == null || !settingsValues.isSuggestionsRequested()) { + if (!settingsValues.isSuggestionsRequested()) { if (mWordComposer.isComposingWord()) { Log.w(TAG, "Called updateSuggestionsOrPredictions but suggestions were not " + "requested!"); @@ -1446,10 +1429,8 @@ public final class InputLogic { } mConnection.deleteSurroundingText(deleteLength, 0); if (!TextUtils.isEmpty(previousWord) && !TextUtils.isEmpty(committedWord)) { - if (mSuggest != null) { - mSuggest.mDictionaryFacilitator.cancelAddingUserHistory( - previousWord, committedWordString); - } + mSuggest.mDictionaryFacilitator.cancelAddingUserHistory( + previousWord, committedWordString); } final String stringToCommit = originallyTypedWord + mLastComposedWord.mSeparatorString; final SpannableString textToCommit = new SpannableString(stringToCommit); diff --git a/java/src/com/android/inputmethod/latin/utils/BoundedTreeSet.java b/java/src/com/android/inputmethod/latin/utils/BoundedTreeSet.java deleted file mode 100644 index ae1fd3f79..000000000 --- a/java/src/com/android/inputmethod/latin/utils/BoundedTreeSet.java +++ /dev/null @@ -1,49 +0,0 @@ -/* - * Copyright (C) 2012 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.inputmethod.latin.utils; - -import com.android.inputmethod.latin.SuggestedWords.SuggestedWordInfo; - -import java.util.Collection; -import java.util.Comparator; -import java.util.TreeSet; - -/** - * A TreeSet that is bounded in size and throws everything that's smaller than its limit - */ -public final class BoundedTreeSet extends TreeSet<SuggestedWordInfo> { - private final int mCapacity; - public BoundedTreeSet(final Comparator<SuggestedWordInfo> comparator, final int capacity) { - super(comparator); - mCapacity = capacity; - } - - @Override - public boolean add(final SuggestedWordInfo e) { - if (size() < mCapacity) return super.add(e); - if (comparator().compare(e, last()) > 0) return false; - super.add(e); - pollLast(); // removes the last element - return true; - } - - @Override - public boolean addAll(final Collection<? extends SuggestedWordInfo> e) { - if (null == e) return false; - return super.addAll(e); - } -} diff --git a/java/src/com/android/inputmethod/latin/utils/LanguageModelParam.java b/java/src/com/android/inputmethod/latin/utils/LanguageModelParam.java index 562ff9e8d..acd16a9e4 100644 --- a/java/src/com/android/inputmethod/latin/utils/LanguageModelParam.java +++ b/java/src/com/android/inputmethod/latin/utils/LanguageModelParam.java @@ -119,7 +119,7 @@ public final class LanguageModelParam { private static LanguageModelParam detectWhetherVaildWordOrNotAndGetLanguageModelParam( final String prevWord, final String targetWord, final int timestamp, final DictionaryFacilitatorForSuggest dictionaryFacilitator) { - final Locale locale = dictionaryFacilitator.mLocale; + final Locale locale = dictionaryFacilitator.getLocale(); if (!dictionaryFacilitator.isValidWord(targetWord, true /* ignoreCase */)) { // OOV word. return createAndGetLanguageModelParamOfWord(prevWord, targetWord, timestamp, diff --git a/java/src/com/android/inputmethod/latin/utils/SuggestionResults.java b/java/src/com/android/inputmethod/latin/utils/SuggestionResults.java new file mode 100644 index 000000000..0b362c48a --- /dev/null +++ b/java/src/com/android/inputmethod/latin/utils/SuggestionResults.java @@ -0,0 +1,76 @@ +/* + * 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.latin.SuggestedWords.SuggestedWordInfo; + +import java.util.Collection; +import java.util.Comparator; +import java.util.Locale; +import java.util.TreeSet; + +/** + * A TreeSet of SuggestedWordInfo that is bounded in size and throws everything that's smaller + * than its limit + */ +public final class SuggestionResults extends TreeSet<SuggestedWordInfo> { + public final Locale mLocale; + private final int mCapacity; + + public SuggestionResults(final Locale locale, final int capacity) { + this(locale, sSuggestedWordInfoComparator, capacity); + } + + public SuggestionResults(final Locale locale, final Comparator<SuggestedWordInfo> comparator, + final int capacity) { + super(comparator); + mLocale = locale; + mCapacity = capacity; + } + + @Override + public boolean add(final SuggestedWordInfo e) { + if (size() < mCapacity) return super.add(e); + if (comparator().compare(e, last()) > 0) return false; + super.add(e); + pollLast(); // removes the last element + return true; + } + + @Override + public boolean addAll(final Collection<? extends SuggestedWordInfo> e) { + if (null == e) return false; + return super.addAll(e); + } + + private static final class SuggestedWordInfoComparator + implements Comparator<SuggestedWordInfo> { + // This comparator ranks the word info with the higher frequency first. That's because + // that's the order we want our elements in. + @Override + public int compare(final SuggestedWordInfo o1, final SuggestedWordInfo o2) { + if (o1.mScore > o2.mScore) return -1; + if (o1.mScore < o2.mScore) return 1; + if (o1.mCodePointCount < o2.mCodePointCount) return -1; + if (o1.mCodePointCount > o2.mCodePointCount) return 1; + return o1.mWord.compareTo(o2.mWord); + } + } + + private static final SuggestedWordInfoComparator sSuggestedWordInfoComparator = + new SuggestedWordInfoComparator(); +} diff --git a/java/src/com/android/inputmethod/research/MainLogBuffer.java b/java/src/com/android/inputmethod/research/MainLogBuffer.java index 9b1d8c535..ffdb43c15 100644 --- a/java/src/com/android/inputmethod/research/MainLogBuffer.java +++ b/java/src/com/android/inputmethod/research/MainLogBuffer.java @@ -155,8 +155,9 @@ public abstract class MainLogBuffer extends FixedLogBuffer { } // Reload the dictionary in case it has changed (e.g., because the user has changed // languages). - if ((mDictionaryFacilitator == null || !mDictionaryFacilitator.hasMainDictionary()) - && mDictionaryForTesting == null) { + if ((mDictionaryFacilitator == null + || !mDictionaryFacilitator.hasInitializedMainDictionary()) + && mDictionaryForTesting == null) { // Main dictionary is unavailable. Since we cannot check it, we cannot tell if a // word is out-of-vocabulary or not. Therefore, we must judge the entire buffer // contents to potentially pose a privacy risk. diff --git a/native/jni/src/suggest/core/dicnode/dic_node.h b/native/jni/src/suggest/core/dicnode/dic_node.h index ae228fb98..865aab632 100644 --- a/native/jni/src/suggest/core/dicnode/dic_node.h +++ b/native/jni/src/suggest/core/dicnode/dic_node.h @@ -20,29 +20,34 @@ #include "defines.h" #include "suggest/core/dicnode/dic_node_profiler.h" #include "suggest/core/dicnode/dic_node_release_listener.h" +#include "suggest/core/dicnode/dic_node_utils.h" #include "suggest/core/dicnode/internal/dic_node_state.h" #include "suggest/core/dicnode/internal/dic_node_properties.h" #include "suggest/core/dictionary/digraph_utils.h" #include "suggest/core/dictionary/error_type_utils.h" +#include "suggest/core/layout/proximity_info_state.h" #include "utils/char_utils.h" #if DEBUG_DICT #define LOGI_SHOW_ADD_COST_PROP \ - do { char charBuf[50]; \ - INTS_TO_CHARS(getOutputWordBuf(), getNodeCodePointCount(), charBuf, NELEMS(charBuf)); \ - AKLOGI("%20s, \"%c\", size = %03d, total = %03d, index(0) = %02d, dist = %.4f, %s,,", \ - __FUNCTION__, getNodeCodePoint(), inputSize, getTotalInputIndex(), \ - getInputIndex(0), getNormalizedCompoundDistance(), charBuf); } while (0) + do { \ + char charBuf[50]; \ + INTS_TO_CHARS(getOutputWordBuf(), getNodeCodePointCount(), charBuf, NELEMS(charBuf)); \ + AKLOGI("%20s, \"%c\", size = %03d, total = %03d, index(0) = %02d, dist = %.4f, %s,,", \ + __FUNCTION__, getNodeCodePoint(), inputSize, getTotalInputIndex(), \ + getInputIndex(0), getNormalizedCompoundDistance(), charBuf); \ + } while (0) #define DUMP_WORD_AND_SCORE(header) \ - do { char charBuf[50]; char prevWordCharBuf[50]; \ - INTS_TO_CHARS(getOutputWordBuf(), getNodeCodePointCount(), charBuf, NELEMS(charBuf)); \ - INTS_TO_CHARS(mDicNodeState.mDicNodeStatePrevWord.getPrevWordBuf(), \ - mDicNodeState.mDicNodeStatePrevWord.getPrevWordLength(), prevWordCharBuf, \ - NELEMS(prevWordCharBuf)); \ - AKLOGI("#%8s, %5f, %5f, %5f, %5f, %s, %s, %d, %5f,", header, \ - getSpatialDistanceForScoring(), getLanguageDistanceForScoring(), \ - getNormalizedCompoundDistance(), getRawLength(), prevWordCharBuf, charBuf, \ - getInputIndex(0), getNormalizedCompoundDistanceAfterFirstWord()); \ + do { \ + char charBuf[50]; \ + INTS_TO_CHARS(getOutputWordBuf(), \ + getNodeCodePointCount() \ + + mDicNodeState.mDicNodeStateOutput.getPrevWordsLength(), \ + charBuf, NELEMS(charBuf)); \ + AKLOGI("#%8s, %5f, %5f, %5f, %5f, %s, %d, %5f,", header, \ + getSpatialDistanceForScoring(), getLanguageDistanceForScoring(), \ + getNormalizedCompoundDistance(), getRawLength(), charBuf, \ + getInputIndex(0), getNormalizedCompoundDistanceAfterFirstWord()); \ } while (0) #else #define LOGI_SHOW_ADD_COST_PROP @@ -103,8 +108,8 @@ class DicNode { void initByCopy(const DicNode *const dicNode) { mIsUsed = true; mIsCachedForNextSuggestion = dicNode->mIsCachedForNextSuggestion; - mDicNodeProperties.init(&dicNode->mDicNodeProperties); - mDicNodeState.init(&dicNode->mDicNodeState); + mDicNodeProperties.initByCopy(&dicNode->mDicNodeProperties); + mDicNodeState.initByCopy(&dicNode->mDicNodeState); PROF_NODE_COPY(&dicNode->mProfiler, mProfiler); } @@ -112,12 +117,8 @@ class DicNode { void initAsRoot(const int rootPtNodeArrayPos, const int prevWordPtNodePos) { mIsUsed = true; mIsCachedForNextSuggestion = false; - mDicNodeProperties.init( - NOT_A_DICT_POS /* pos */, rootPtNodeArrayPos, NOT_A_CODE_POINT /* nodeCodePoint */, - NOT_A_PROBABILITY /* probability */, false /* isTerminal */, - true /* hasChildren */, false /* isBlacklistedOrNotAWord */, 0 /* depth */, - 0 /* terminalDepth */); - mDicNodeState.init(prevWordPtNodePos); + mDicNodeProperties.init(rootPtNodeArrayPos, prevWordPtNodePos); + mDicNodeState.init(); PROF_NODE_RESET(mProfiler); } @@ -125,13 +126,8 @@ class DicNode { void initAsRootWithPreviousWord(const DicNode *const dicNode, const int rootPtNodeArrayPos) { mIsUsed = true; mIsCachedForNextSuggestion = dicNode->mIsCachedForNextSuggestion; - mDicNodeProperties.init( - NOT_A_DICT_POS /* pos */, rootPtNodeArrayPos, NOT_A_CODE_POINT /* nodeCodePoint */, - NOT_A_PROBABILITY /* probability */, false /* isTerminal */, - true /* hasChildren */, false /* isBlacklistedOrNotAWord */, 0 /* depth */, - 0 /* terminalDepth */); + mDicNodeProperties.init(rootPtNodeArrayPos, dicNode->mDicNodeProperties.getPtNodePos()); mDicNodeState.initAsRootWithPreviousWord(&dicNode->mDicNodeState, - dicNode->mDicNodeProperties.getPtNodePos(), dicNode->mDicNodeProperties.getDepth()); PROF_NODE_COPY(&dicNode->mProfiler, mProfiler); } @@ -141,7 +137,7 @@ class DicNode { mIsCachedForNextSuggestion = parentDicNode->mIsCachedForNextSuggestion; const int parentCodePoint = parentDicNode->getNodeTypedCodePoint(); mDicNodeProperties.init(&parentDicNode->mDicNodeProperties, parentCodePoint); - mDicNodeState.init(&parentDicNode->mDicNodeState); + mDicNodeState.initByCopy(&parentDicNode->mDicNodeState); PROF_NODE_COPY(&parentDicNode->mProfiler, mProfiler); } @@ -156,7 +152,7 @@ class DicNode { dicNode->mDicNodeProperties.getLeavingDepth() + mergedNodeCodePointCount); mDicNodeProperties.init(ptNodePos, childrenPtNodeArrayPos, mergedNodeCodePoints[0], probability, isTerminal, hasChildren, isBlacklistedOrNotAWord, newDepth, - newLeavingDepth); + newLeavingDepth, dicNode->mDicNodeProperties.getPrevWordTerminalPtNodePos()); mDicNodeState.init(&dicNode->mDicNodeState, mergedNodeCodePointCount, mergedNodeCodePoints); PROF_NODE_COPY(&dicNode->mProfiler, mProfiler); @@ -200,7 +196,7 @@ class DicNode { // Used to expand the node in DicNodeUtils int getNodeTypedCodePoint() const { - return mDicNodeState.mDicNodeStateOutput.getCodePointAt(getNodeCodePointCount()); + return mDicNodeState.mDicNodeStateOutput.getCurrentWordCodePointAt(getNodeCodePointCount()); } // Check if the current word and the previous word can be considered as a valid multiple word @@ -211,19 +207,19 @@ class DicNode { } // Treat suggestion as invalid if the current and the previous word are single character // words. - const int prevWordLen = mDicNodeState.mDicNodeStatePrevWord.getPrevWordLength() - - mDicNodeState.mDicNodeStatePrevWord.getPrevWordStart() - 1; + const int prevWordLen = mDicNodeState.mDicNodeStateOutput.getPrevWordsLength() + - mDicNodeState.mDicNodeStateOutput.getPrevWordStart() - 1; const int currentWordLen = getNodeCodePointCount(); return (prevWordLen != 1 || currentWordLen != 1); } bool isFirstCharUppercase() const { - const int c = mDicNodeState.mDicNodeStateOutput.getCodePointAt(0); + const int c = mDicNodeState.mDicNodeStateOutput.getCurrentWordCodePointAt(0); return CharUtils::isAsciiUpper(c); } bool isFirstWord() const { - return mDicNodeState.mDicNodeStatePrevWord.getPrevWordPtNodePos() == NOT_A_DICT_POS; + return mDicNodeProperties.getPrevWordTerminalPtNodePos() == NOT_A_DICT_POS; } bool isCompletion(const int inputSize) const { @@ -241,7 +237,7 @@ class DicNode { // Used to get bigram probability in DicNodeUtils int getPrevWordTerminalPtNodePos() const { - return mDicNodeState.mDicNodeStatePrevWord.getPrevWordPtNodePos(); + return mDicNodeProperties.getPrevWordTerminalPtNodePos(); } // Used in DicNodeUtils @@ -263,8 +259,8 @@ class DicNode { bool shouldBeFilteredBySafetyNetForBigram() const { const uint16_t currentDepth = getNodeCodePointCount(); - const int prevWordLen = mDicNodeState.mDicNodeStatePrevWord.getPrevWordLength() - - mDicNodeState.mDicNodeStatePrevWord.getPrevWordStart() - 1; + const int prevWordLen = mDicNodeState.mDicNodeStateOutput.getPrevWordsLength() + - mDicNodeState.mDicNodeStateOutput.getPrevWordStart() - 1; return !(currentDepth > 0 && (currentDepth != 1 || prevWordLen != 1)); } @@ -277,7 +273,7 @@ class DicNode { } bool isTotalInputSizeExceedingLimit() const { - const int prevWordsLen = mDicNodeState.mDicNodeStatePrevWord.getPrevWordLength(); + const int prevWordsLen = mDicNodeState.mDicNodeStateOutput.getPrevWordsLength(); const int currentWordDepth = getNodeCodePointCount(); // TODO: 3 can be 2? Needs to be investigated. // TODO: Have a const variable for 3 (or 2) @@ -285,25 +281,24 @@ class DicNode { } void outputResult(int *dest) const { - const uint16_t prevWordLength = mDicNodeState.mDicNodeStatePrevWord.getPrevWordLength(); + const uint16_t prevWordLength = mDicNodeState.mDicNodeStateOutput.getPrevWordsLength(); const uint16_t currentDepth = getNodeCodePointCount(); - DicNodeUtils::appendTwoWords(mDicNodeState.mDicNodeStatePrevWord.getPrevWordBuf(), - prevWordLength, getOutputWordBuf(), currentDepth, dest); + memmove(dest, getOutputWordBuf(), (prevWordLength + currentDepth) * sizeof(dest[0])); DUMP_WORD_AND_SCORE("OUTPUT"); } // "Total" in this context (and other methods in this class) means the whole suggestion. When // this represents a multi-word suggestion, the referenced PtNode (in mDicNodeState) is only // the one that corresponds to the last word of the suggestion, and all the previous words - // are concatenated together in mPrevWord - which contains a space at the end. + // are concatenated together in mDicNodeStateOutput. int getTotalNodeSpaceCount() const { if (isFirstWord()) return 0; - return CharUtils::getSpaceCount(mDicNodeState.mDicNodeStatePrevWord.getPrevWordBuf(), - mDicNodeState.mDicNodeStatePrevWord.getPrevWordLength()); + return CharUtils::getSpaceCount(mDicNodeState.mDicNodeStateOutput.getCodePointBuf(), + mDicNodeState.mDicNodeStateOutput.getPrevWordsLength()); } int getSecondWordFirstInputIndex(const ProximityInfoState *const pInfoState) const { - const int inputIndex = mDicNodeState.mDicNodeStatePrevWord.getSecondWordFirstInputIndex(); + const int inputIndex = mDicNodeState.mDicNodeStateOutput.getSecondWordFirstInputIndex(); if (inputIndex == NOT_AN_INDEX) { return NOT_AN_INDEX; } else { @@ -312,7 +307,7 @@ class DicNode { } bool hasMultipleWords() const { - return mDicNodeState.mDicNodeStatePrevWord.getPrevWordCount() > 0; + return mDicNodeState.mDicNodeStateOutput.getPrevWordCount() > 0; } int getProximityCorrectionCount() const { @@ -346,7 +341,7 @@ class DicNode { // Used to commit input partially int getPrevWordPtNodePos() const { - return mDicNodeState.mDicNodeStatePrevWord.getPrevWordPtNodePos(); + return mDicNodeProperties.getPrevWordTerminalPtNodePos(); } AK_FORCE_INLINE const int *getOutputWordBuf() const { @@ -425,7 +420,7 @@ class DicNode { float getLanguageDistanceRatePerWordForScoring() const { const float langDist = getLanguageDistanceForScoring(); const float totalWordCount = - static_cast<float>(mDicNodeState.mDicNodeStatePrevWord.getPrevWordCount() + 1); + static_cast<float>(mDicNodeState.mDicNodeStateOutput.getPrevWordCount() + 1); return langDist / totalWordCount; } @@ -469,7 +464,7 @@ class DicNode { // Returns code point count including spaces inline uint16_t getTotalNodeCodePointCount() const { - return getNodeCodePointCount() + mDicNodeState.mDicNodeStatePrevWord.getPrevWordLength(); + return getNodeCodePointCount() + mDicNodeState.mDicNodeStateOutput.getPrevWordsLength(); } AK_FORCE_INLINE void dump(const char *tag) const { @@ -516,8 +511,9 @@ class DicNode { return depthDiff > 0; } for (int i = 0; i < depth; ++i) { - const int codePoint = mDicNodeState.mDicNodeStateOutput.getCodePointAt(i); - const int rightCodePoint = right->mDicNodeState.mDicNodeStateOutput.getCodePointAt(i); + const int codePoint = mDicNodeState.mDicNodeStateOutput.getCurrentWordCodePointAt(i); + const int rightCodePoint = + right->mDicNodeState.mDicNodeStateOutput.getCurrentWordCodePointAt(i); if (codePoint != rightCodePoint) { return rightCodePoint > codePoint; } @@ -574,8 +570,8 @@ class DicNode { } AK_FORCE_INLINE void updateInputIndexG(const DicNode_InputStateG *const inputStateG) { - if (mDicNodeState.mDicNodeStatePrevWord.getPrevWordCount() == 1 && isFirstLetter()) { - mDicNodeState.mDicNodeStatePrevWord.setSecondWordFirstInputIndex( + if (mDicNodeState.mDicNodeStateOutput.getPrevWordCount() == 1 && isFirstLetter()) { + mDicNodeState.mDicNodeStateOutput.setSecondWordFirstInputIndex( inputStateG->mInputIndex); } mDicNodeState.mDicNodeStateInput.updateInputIndexG(inputStateG->mPointerId, diff --git a/native/jni/src/suggest/core/dicnode/internal/dic_node_properties.h b/native/jni/src/suggest/core/dicnode/internal/dic_node_properties.h index ab02e6192..6ddb7f1af 100644 --- a/native/jni/src/suggest/core/dicnode/internal/dic_node_properties.h +++ b/native/jni/src/suggest/core/dicnode/internal/dic_node_properties.h @@ -29,16 +29,18 @@ namespace latinime { class DicNodeProperties { public: AK_FORCE_INLINE DicNodeProperties() - : mPtNodePos(0), mChildrenPtNodeArrayPos(0), mProbability(0), mDicNodeCodePoint(0), - mIsTerminal(false), mHasChildrenPtNodes(false), mIsBlacklistedOrNotAWord(false), - mDepth(0), mLeavingDepth(0) {} + : mPtNodePos(NOT_A_DICT_POS), mChildrenPtNodeArrayPos(NOT_A_DICT_POS), + mProbability(NOT_A_PROBABILITY), mDicNodeCodePoint(NOT_A_CODE_POINT), + mIsTerminal(false), mHasChildrenPtNodes(false), + mIsBlacklistedOrNotAWord(false), mDepth(0), mLeavingDepth(0), + mPrevWordTerminalPtNodePos(NOT_A_DICT_POS) {} ~DicNodeProperties() {} // Should be called only once per DicNode is initialized. void init(const int pos, const int childrenPos, const int nodeCodePoint, const int probability, const bool isTerminal, const bool hasChildren, const bool isBlacklistedOrNotAWord, - const uint16_t depth, const uint16_t leavingDepth) { + const uint16_t depth, const uint16_t leavingDepth, const int prevWordNodePos) { mPtNodePos = pos; mChildrenPtNodeArrayPos = childrenPos; mDicNodeCodePoint = nodeCodePoint; @@ -48,10 +50,24 @@ class DicNodeProperties { mIsBlacklistedOrNotAWord = isBlacklistedOrNotAWord; mDepth = depth; mLeavingDepth = leavingDepth; + mPrevWordTerminalPtNodePos = prevWordNodePos; } - // Init for copy - void init(const DicNodeProperties *const dicNodeProp) { + // Init for root with prevWordPtNodePos which is used for bigram + void init(const int rootPtNodeArrayPos, const int prevWordNodePos) { + mPtNodePos = NOT_A_DICT_POS; + mChildrenPtNodeArrayPos = rootPtNodeArrayPos; + mDicNodeCodePoint = NOT_A_CODE_POINT; + mProbability = NOT_A_PROBABILITY; + mIsTerminal = false; + mHasChildrenPtNodes = true; + mIsBlacklistedOrNotAWord = false; + mDepth = 0; + mLeavingDepth = 0; + mPrevWordTerminalPtNodePos = prevWordNodePos; + } + + void initByCopy(const DicNodeProperties *const dicNodeProp) { mPtNodePos = dicNodeProp->mPtNodePos; mChildrenPtNodeArrayPos = dicNodeProp->mChildrenPtNodeArrayPos; mDicNodeCodePoint = dicNodeProp->mDicNodeCodePoint; @@ -61,6 +77,7 @@ class DicNodeProperties { mIsBlacklistedOrNotAWord = dicNodeProp->mIsBlacklistedOrNotAWord; mDepth = dicNodeProp->mDepth; mLeavingDepth = dicNodeProp->mLeavingDepth; + mPrevWordTerminalPtNodePos = dicNodeProp->mPrevWordTerminalPtNodePos; } // Init as passing child @@ -74,6 +91,7 @@ class DicNodeProperties { mIsBlacklistedOrNotAWord = dicNodeProp->mIsBlacklistedOrNotAWord; mDepth = dicNodeProp->mDepth + 1; // Increment the depth of a passing child mLeavingDepth = dicNodeProp->mLeavingDepth; + mPrevWordTerminalPtNodePos = dicNodeProp->mPrevWordTerminalPtNodePos; } int getPtNodePos() const { @@ -113,6 +131,10 @@ class DicNodeProperties { return mIsBlacklistedOrNotAWord; } + int getPrevWordTerminalPtNodePos() const { + return mPrevWordTerminalPtNodePos; + } + private: // Caution!!! // Use a default copy constructor and an assign operator because shallow copies are ok @@ -126,6 +148,7 @@ class DicNodeProperties { bool mIsBlacklistedOrNotAWord; uint16_t mDepth; uint16_t mLeavingDepth; + int mPrevWordTerminalPtNodePos; }; } // namespace latinime #endif // LATINIME_DIC_NODE_PROPERTIES_H diff --git a/native/jni/src/suggest/core/dicnode/internal/dic_node_state.h b/native/jni/src/suggest/core/dicnode/internal/dic_node_state.h index a41667567..badb1f5f2 100644 --- a/native/jni/src/suggest/core/dicnode/internal/dic_node_state.h +++ b/native/jni/src/suggest/core/dicnode/internal/dic_node_state.h @@ -20,7 +20,6 @@ #include "defines.h" #include "suggest/core/dicnode/internal/dic_node_state_input.h" #include "suggest/core/dicnode/internal/dic_node_state_output.h" -#include "suggest/core/dicnode/internal/dic_node_state_prevword.h" #include "suggest/core/dicnode/internal/dic_node_state_scoring.h" namespace latinime { @@ -29,65 +28,50 @@ class DicNodeState { public: DicNodeStateInput mDicNodeStateInput; DicNodeStateOutput mDicNodeStateOutput; - DicNodeStatePrevWord mDicNodeStatePrevWord; DicNodeStateScoring mDicNodeStateScoring; AK_FORCE_INLINE DicNodeState() - : mDicNodeStateInput(), mDicNodeStateOutput(), mDicNodeStatePrevWord(), - mDicNodeStateScoring() { - } + : mDicNodeStateInput(), mDicNodeStateOutput(), mDicNodeStateScoring() {} ~DicNodeState() {} DicNodeState &operator=(const DicNodeState& src) { - init(&src); + initByCopy(&src); return *this; } DicNodeState(const DicNodeState& src) - : mDicNodeStateInput(), mDicNodeStateOutput(), mDicNodeStatePrevWord(), - mDicNodeStateScoring() { - init(&src); + : mDicNodeStateInput(), mDicNodeStateOutput(), mDicNodeStateScoring() { + initByCopy(&src); } - // Init with prevWordPos - void init(const int prevWordPos) { + // Init for root + void init() { mDicNodeStateInput.init(); mDicNodeStateOutput.init(); - mDicNodeStatePrevWord.init(prevWordPos); mDicNodeStateScoring.init(); } // Init with previous word. void initAsRootWithPreviousWord(const DicNodeState *prevWordDicNodeState, - const int prevWordPos, const int prevWordCodePointCount) { - mDicNodeStateOutput.init(); // reset for next word + const int prevWordCodePointCount) { + mDicNodeStateOutput.init(&prevWordDicNodeState->mDicNodeStateOutput); mDicNodeStateInput.init( &prevWordDicNodeState->mDicNodeStateInput, true /* resetTerminalDiffCost */); - mDicNodeStateScoring.init(&prevWordDicNodeState->mDicNodeStateScoring); - mDicNodeStatePrevWord.init( - prevWordDicNodeState->mDicNodeStatePrevWord.getPrevWordCount() + 1, - prevWordPos, - prevWordDicNodeState->mDicNodeStatePrevWord.getPrevWordBuf(), - prevWordDicNodeState->mDicNodeStatePrevWord.getPrevWordLength(), - prevWordDicNodeState->mDicNodeStateOutput.getCodePointBuf(), - prevWordCodePointCount, - prevWordDicNodeState->mDicNodeStatePrevWord.getSecondWordFirstInputIndex(), - prevWordDicNodeState->mDicNodeStateInput.getInputIndex(0) /* lastInputIndex */); + mDicNodeStateScoring.initByCopy(&prevWordDicNodeState->mDicNodeStateScoring); } // Init by copy - AK_FORCE_INLINE void init(const DicNodeState *const src) { - mDicNodeStateInput.init(&src->mDicNodeStateInput); - mDicNodeStateOutput.init(&src->mDicNodeStateOutput); - mDicNodeStatePrevWord.init(&src->mDicNodeStatePrevWord); - mDicNodeStateScoring.init(&src->mDicNodeStateScoring); + AK_FORCE_INLINE void initByCopy(const DicNodeState *const src) { + mDicNodeStateInput.initByCopy(&src->mDicNodeStateInput); + mDicNodeStateOutput.initByCopy(&src->mDicNodeStateOutput); + mDicNodeStateScoring.initByCopy(&src->mDicNodeStateScoring); } // Init by copy and adding merged node code points. void init(const DicNodeState *const src, const uint16_t mergedNodeCodePointCount, const int *const mergedNodeCodePoints) { - init(src); + initByCopy(src); mDicNodeStateOutput.addMergedNodeCodePoints( mergedNodeCodePointCount, mergedNodeCodePoints); } diff --git a/native/jni/src/suggest/core/dicnode/internal/dic_node_state_input.h b/native/jni/src/suggest/core/dicnode/internal/dic_node_state_input.h index 03042a8a7..50a37ba3e 100644 --- a/native/jni/src/suggest/core/dicnode/internal/dic_node_state_input.h +++ b/native/jni/src/suggest/core/dicnode/internal/dic_node_state_input.h @@ -53,7 +53,7 @@ class DicNodeStateInput { mTerminalDiffCost[pointerId] = terminalDiffCost; } - void init(const DicNodeStateInput *const src) { + void initByCopy(const DicNodeStateInput *const src) { init(src, false); } diff --git a/native/jni/src/suggest/core/dicnode/internal/dic_node_state_output.h b/native/jni/src/suggest/core/dicnode/internal/dic_node_state_output.h index bdb182c1d..ea48de1ea 100644 --- a/native/jni/src/suggest/core/dicnode/internal/dic_node_state_output.h +++ b/native/jni/src/suggest/core/dicnode/internal/dic_node_state_output.h @@ -25,24 +25,53 @@ namespace latinime { +// Class to have information to be output. This can contain previous words when the suggestion +// is a multi-word suggestion. class DicNodeStateOutput { public: - DicNodeStateOutput() : mOutputtedCodePointCount(0) {} + DicNodeStateOutput() + : mOutputtedCodePointCount(0), mCurrentWordStart(0), mPrevWordCount(0), + mPrevWordsLength(0), mPrevWordStart(0), mSecondWordFirstInputIndex(NOT_AN_INDEX) {} ~DicNodeStateOutput() {} + // Init for root void init() { mOutputtedCodePointCount = 0; - mCodePointsBuf[0] = 0; + mCurrentWordStart = 0; + mOutputCodePoints[0] = 0; + mPrevWordCount = 0; + mPrevWordsLength = 0; + mPrevWordStart = 0; + mSecondWordFirstInputIndex = NOT_AN_INDEX; } + // Init for next word. void init(const DicNodeStateOutput *const stateOutput) { - memmove(mCodePointsBuf, stateOutput->mCodePointsBuf, - stateOutput->mOutputtedCodePointCount * sizeof(mCodePointsBuf[0])); + mOutputtedCodePointCount = stateOutput->mOutputtedCodePointCount + 1; + memmove(mOutputCodePoints, stateOutput->mOutputCodePoints, + stateOutput->mOutputtedCodePointCount * sizeof(mOutputCodePoints[0])); + mOutputCodePoints[stateOutput->mOutputtedCodePointCount] = KEYCODE_SPACE; + mCurrentWordStart = stateOutput->mOutputtedCodePointCount + 1; + mPrevWordCount = std::min(static_cast<int16_t>(stateOutput->mPrevWordCount + 1), + static_cast<int16_t>(MAX_RESULTS)); + mPrevWordsLength = stateOutput->mOutputtedCodePointCount + 1; + mPrevWordStart = stateOutput->mCurrentWordStart; + mSecondWordFirstInputIndex = stateOutput->mSecondWordFirstInputIndex; + } + + void initByCopy(const DicNodeStateOutput *const stateOutput) { + memmove(mOutputCodePoints, stateOutput->mOutputCodePoints, + stateOutput->mOutputtedCodePointCount * sizeof(mOutputCodePoints[0])); mOutputtedCodePointCount = stateOutput->mOutputtedCodePointCount; if (mOutputtedCodePointCount < MAX_WORD_LENGTH) { - mCodePointsBuf[mOutputtedCodePointCount] = 0; + mOutputCodePoints[mOutputtedCodePointCount] = 0; } + mCurrentWordStart = stateOutput->mCurrentWordStart; + mPrevWordCount = stateOutput->mPrevWordCount; + mPrevWordsLength = stateOutput->mPrevWordsLength; + mPrevWordStart = stateOutput->mPrevWordStart; + mSecondWordFirstInputIndex = stateOutput->mSecondWordFirstInputIndex; } void addMergedNodeCodePoints(const uint16_t mergedNodeCodePointCount, @@ -51,29 +80,72 @@ class DicNodeStateOutput { const int additionalCodePointCount = std::min( static_cast<int>(mergedNodeCodePointCount), MAX_WORD_LENGTH - mOutputtedCodePointCount); - memmove(&mCodePointsBuf[mOutputtedCodePointCount], mergedNodeCodePoints, - additionalCodePointCount * sizeof(mCodePointsBuf[0])); + memmove(&mOutputCodePoints[mOutputtedCodePointCount], mergedNodeCodePoints, + additionalCodePointCount * sizeof(mOutputCodePoints[0])); mOutputtedCodePointCount = static_cast<uint16_t>( - mOutputtedCodePointCount + mergedNodeCodePointCount); + mOutputtedCodePointCount + additionalCodePointCount); if (mOutputtedCodePointCount < MAX_WORD_LENGTH) { - mCodePointsBuf[mOutputtedCodePointCount] = 0; + mOutputCodePoints[mOutputtedCodePointCount] = 0; } } } - int getCodePointAt(const int index) const { - return mCodePointsBuf[index]; + int getCurrentWordCodePointAt(const int index) const { + return mOutputCodePoints[mCurrentWordStart + index]; } const int *getCodePointBuf() const { - return mCodePointsBuf; + return mOutputCodePoints; + } + + void setSecondWordFirstInputIndex(const int inputIndex) { + mSecondWordFirstInputIndex = inputIndex; + } + + int getSecondWordFirstInputIndex() const { + return mSecondWordFirstInputIndex; + } + + // TODO: remove + int16_t getPrevWordsLength() const { + return mPrevWordsLength; + } + + int16_t getPrevWordCount() const { + return mPrevWordCount; + } + + int16_t getPrevWordStart() const { + return mPrevWordStart; + } + + int getOutputCodePointAt(const int id) const { + return mOutputCodePoints[id]; } private: DISALLOW_COPY_AND_ASSIGN(DicNodeStateOutput); + // When the DicNode represents "this is a pen": + // mOutputtedCodePointCount is 13, which is total code point count of "this is a pen" including + // spaces. + // mCurrentWordStart indicates the head of "pen", thus it is 10. + // This contains 3 previous words, "this", "is" and "a"; thus, mPrevWordCount is 3. + // mPrevWordsLength is length of "this is a ", which is 10. + // mPrevWordStart is the start index of "a"; thus, it is 8. + // mSecondWordFirstInputIndex is the first input index of "is". + uint16_t mOutputtedCodePointCount; - int mCodePointsBuf[MAX_WORD_LENGTH]; + int mOutputCodePoints[MAX_WORD_LENGTH]; + int16_t mCurrentWordStart; + // Previous word count in mOutputCodePoints. + int16_t mPrevWordCount; + // Total length of previous words in mOutputCodePoints. This is being used by the algorithm + // that may want to look at the previous word information. + int16_t mPrevWordsLength; + // Start index of the previous word in mOutputCodePoints. This is being used for auto commit. + int16_t mPrevWordStart; + int mSecondWordFirstInputIndex; }; } // namespace latinime #endif // LATINIME_DIC_NODE_STATE_OUTPUT_H diff --git a/native/jni/src/suggest/core/dicnode/internal/dic_node_state_prevword.h b/native/jni/src/suggest/core/dicnode/internal/dic_node_state_prevword.h deleted file mode 100644 index 409841e2d..000000000 --- a/native/jni/src/suggest/core/dicnode/internal/dic_node_state_prevword.h +++ /dev/null @@ -1,117 +0,0 @@ -/* - * Copyright (C) 2012 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#ifndef LATINIME_DIC_NODE_STATE_PREVWORD_H -#define LATINIME_DIC_NODE_STATE_PREVWORD_H - -#include <algorithm> -#include <cstring> // for memset() and memmove() -#include <stdint.h> - -#include "defines.h" -#include "suggest/core/dicnode/dic_node_utils.h" -#include "suggest/core/layout/proximity_info_state.h" - -namespace latinime { - -class DicNodeStatePrevWord { - public: - AK_FORCE_INLINE DicNodeStatePrevWord() - : mPrevWordCount(0), mPrevWordLength(0), mPrevWordStart(0), - mPrevWordPtNodePos(NOT_A_DICT_POS), mSecondWordFirstInputIndex(NOT_AN_INDEX) {} - - ~DicNodeStatePrevWord() {} - - void init(const int prevWordNodePos) { - mPrevWordLength = 0; - mPrevWordCount = 0; - mPrevWordStart = 0; - mPrevWordPtNodePos = prevWordNodePos; - mSecondWordFirstInputIndex = NOT_AN_INDEX; - mPrevWord[0] = 0; - } - - // Init by copy - AK_FORCE_INLINE void init(const DicNodeStatePrevWord *const prevWord) { - mPrevWordLength = prevWord->mPrevWordLength; - mPrevWordCount = prevWord->mPrevWordCount; - mPrevWordStart = prevWord->mPrevWordStart; - mPrevWordPtNodePos = prevWord->mPrevWordPtNodePos; - mSecondWordFirstInputIndex = prevWord->mSecondWordFirstInputIndex; - memmove(mPrevWord, prevWord->mPrevWord, prevWord->mPrevWordLength * sizeof(mPrevWord[0])); - } - - void init(const int16_t prevWordCount, const int prevWordNodePos, const int *const src0, - const int16_t length0, const int *const src1, const int16_t length1, - const int prevWordSecondWordFirstInputIndex, const int lastInputIndex) { - mPrevWordCount = std::min(prevWordCount, static_cast<int16_t>(MAX_RESULTS)); - mPrevWordPtNodePos = prevWordNodePos; - int twoWordsLen = - DicNodeUtils::appendTwoWords(src0, length0, src1, length1, mPrevWord); - if (twoWordsLen >= MAX_WORD_LENGTH) { - twoWordsLen = MAX_WORD_LENGTH - 1; - } - mPrevWord[twoWordsLen] = KEYCODE_SPACE; - mPrevWordStart = length0; - mPrevWordLength = static_cast<int16_t>(twoWordsLen + 1); - mSecondWordFirstInputIndex = prevWordSecondWordFirstInputIndex; - } - - void setSecondWordFirstInputIndex(const int inputIndex) { - mSecondWordFirstInputIndex = inputIndex; - } - - int getSecondWordFirstInputIndex() const { - return mSecondWordFirstInputIndex; - } - - // TODO: remove - int16_t getPrevWordLength() const { - return mPrevWordLength; - } - - int16_t getPrevWordCount() const { - return mPrevWordCount; - } - - int16_t getPrevWordStart() const { - return mPrevWordStart; - } - - int getPrevWordPtNodePos() const { - return mPrevWordPtNodePos; - } - - int getPrevWordCodePointAt(const int id) const { - return mPrevWord[id]; - } - - const int *getPrevWordBuf() const { - return mPrevWord; - } - - private: - DISALLOW_COPY_AND_ASSIGN(DicNodeStatePrevWord); - - int16_t mPrevWordCount; - int16_t mPrevWordLength; - int16_t mPrevWordStart; - int mPrevWordPtNodePos; - int mSecondWordFirstInputIndex; - int mPrevWord[MAX_WORD_LENGTH]; -}; -} // namespace latinime -#endif // LATINIME_DIC_NODE_STATE_PREVWORD_H diff --git a/native/jni/src/suggest/core/dicnode/internal/dic_node_state_scoring.h b/native/jni/src/suggest/core/dicnode/internal/dic_node_state_scoring.h index b0db55fc1..f164edbee 100644 --- a/native/jni/src/suggest/core/dicnode/internal/dic_node_state_scoring.h +++ b/native/jni/src/suggest/core/dicnode/internal/dic_node_state_scoring.h @@ -53,7 +53,7 @@ class DicNodeStateScoring { mContainedErrorTypes = ErrorTypeUtils::NOT_AN_ERROR; } - AK_FORCE_INLINE void init(const DicNodeStateScoring *const scoring) { + AK_FORCE_INLINE void initByCopy(const DicNodeStateScoring *const scoring) { mEditCorrectionCount = scoring->mEditCorrectionCount; mProximityCorrectionCount = scoring->mProximityCorrectionCount; mCompletionCount = scoring->mCompletionCount; diff --git a/tests/src/com/android/inputmethod/latin/InputLogicTests.java b/tests/src/com/android/inputmethod/latin/InputLogicTests.java index ab6245635..d4e6ad87a 100644 --- a/tests/src/com/android/inputmethod/latin/InputLogicTests.java +++ b/tests/src/com/android/inputmethod/latin/InputLogicTests.java @@ -454,4 +454,24 @@ public class InputLogicTests extends InputTestsBase { assertEquals("predictions after recorrection", "Obama", suggestedWords.size() > 0 ? suggestedWords.getWord(0) : null); } + + public void testComposingMultipleBackspace() { + final String WORD_TO_TYPE = "radklro"; + final int TIMES_TO_TYPE = 3; + final int TIMES_TO_BACKSPACE = 8; + type(WORD_TO_TYPE); + type(Constants.CODE_DELETE); + type(Constants.CODE_DELETE); + type(Constants.CODE_DELETE); + type(WORD_TO_TYPE); + type(Constants.CODE_DELETE); + type(Constants.CODE_DELETE); + type(WORD_TO_TYPE); + type(Constants.CODE_DELETE); + type(Constants.CODE_DELETE); + type(Constants.CODE_DELETE); + assertEquals("composing with multiple backspace", + WORD_TO_TYPE.length() * TIMES_TO_TYPE - TIMES_TO_BACKSPACE, + mEditText.getText().length()); + } } |