aboutsummaryrefslogtreecommitdiffstats
path: root/java/src
diff options
context:
space:
mode:
Diffstat (limited to 'java/src')
-rw-r--r--java/src/com/android/inputmethod/latin/LatinIME.java86
-rw-r--r--java/src/com/android/inputmethod/latin/Suggest.java121
-rw-r--r--java/src/com/android/inputmethod/latin/SuggestedWords.java6
-rw-r--r--java/src/com/android/inputmethod/latin/inputlogic/InputLogic.java36
-rw-r--r--java/src/com/android/inputmethod/latin/inputlogic/InputLogicHandler.java49
-rw-r--r--java/src/com/android/inputmethod/latin/personalization/PersonalizationDictionary.java2
-rw-r--r--java/src/com/android/inputmethod/latin/personalization/PersonalizationDictionarySessionRegistrar.java (renamed from java/src/com/android/inputmethod/latin/personalization/PersonalizationDictionarySessionRegister.java)5
-rw-r--r--java/src/com/android/inputmethod/latin/personalization/PersonalizationHelper.java46
-rw-r--r--java/src/com/android/inputmethod/latin/personalization/UserHistoryDictionary.java3
-rw-r--r--java/src/com/android/inputmethod/latin/settings/DebugSettings.java2
-rw-r--r--java/src/com/android/inputmethod/latin/settings/Settings.java20
-rw-r--r--java/src/com/android/inputmethod/latin/settings/SettingsValues.java10
-rw-r--r--java/src/com/android/inputmethod/latin/suggestions/SuggestionStripLayoutHelper.java6
-rw-r--r--java/src/com/android/inputmethod/latin/utils/AutoCorrectionUtils.java47
-rw-r--r--java/src/com/android/inputmethod/latin/utils/FileUtils.java15
15 files changed, 274 insertions, 180 deletions
diff --git a/java/src/com/android/inputmethod/latin/LatinIME.java b/java/src/com/android/inputmethod/latin/LatinIME.java
index 2e5e4c32f..d8fb4f2be 100644
--- a/java/src/com/android/inputmethod/latin/LatinIME.java
+++ b/java/src/com/android/inputmethod/latin/LatinIME.java
@@ -72,13 +72,13 @@ import com.android.inputmethod.latin.define.ProductionFlag;
import com.android.inputmethod.latin.inputlogic.InputLogic;
import com.android.inputmethod.latin.inputlogic.SpaceState;
import com.android.inputmethod.latin.personalization.DictionaryDecayBroadcastReciever;
-import com.android.inputmethod.latin.personalization.PersonalizationDictionarySessionRegister;
+import com.android.inputmethod.latin.personalization.PersonalizationDictionarySessionRegistrar;
+import com.android.inputmethod.latin.personalization.PersonalizationHelper;
import com.android.inputmethod.latin.settings.Settings;
import com.android.inputmethod.latin.settings.SettingsActivity;
import com.android.inputmethod.latin.settings.SettingsValues;
import com.android.inputmethod.latin.suggestions.SuggestionStripView;
import com.android.inputmethod.latin.utils.ApplicationUtils;
-import com.android.inputmethod.latin.utils.AutoCorrectionUtils;
import com.android.inputmethod.latin.utils.CapsModeUtils;
import com.android.inputmethod.latin.utils.CompletionInfoUtils;
import com.android.inputmethod.latin.utils.IntentUtils;
@@ -127,8 +127,6 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
private final SubtypeSwitcher mSubtypeSwitcher;
private final SubtypeState mSubtypeState = new SubtypeState();
- private UserBinaryDictionary mUserDictionary;
-
// Object for reacting to adding/removing a dictionary pack.
private BroadcastReceiver mDictionaryPackInstallReceiver =
new DictionaryPackInstallBroadcastReceiver(this);
@@ -463,7 +461,6 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
KeyboardSwitcher.init(this);
AudioAndHapticFeedbackManager.init(this);
AccessibilityUtils.init(this);
- PersonalizationDictionarySessionRegister.init(this);
super.onCreate();
@@ -515,10 +512,18 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
// the layout; at this time, we need to skip resetting the contacts dictionary. It will
// be done later inside {@see #initSuggest()} when the reopenDictionaries message is
// processed.
+ final SettingsValues currentSettingsValues = mSettings.getCurrent();
if (!mHandler.hasPendingReopenDictionaries() && mInputLogic.mSuggest != null) {
// May need to reset dictionaries depending on the user settings.
+ // TODO: Quit setting dictionaries from LatinIME.
mInputLogic.mSuggest.setAdditionalDictionaries(mInputLogic.mSuggest /* oldSuggest */,
- mSettings.getCurrent());
+ currentSettingsValues);
+ }
+ if (currentSettingsValues.mUsePersonalizedDicts) {
+ PersonalizationDictionarySessionRegistrar.init(this);
+ } else {
+ PersonalizationHelper.removeAllPersonalizedDictionaries(this);
+ PersonalizationDictionarySessionRegistrar.resetAll(this);
}
}
@@ -548,9 +553,9 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
subtypeLocale = switcherSubtypeLocale;
}
- final Suggest newSuggest = new Suggest(this /* Context */, subtypeLocale,
- this /* SuggestInitializationListener */);
final SettingsValues settingsValues = mSettings.getCurrent();
+ final Suggest newSuggest = new Suggest(this /* Context */, subtypeLocale, settingsValues,
+ this /* SuggestInitializationListener */);
if (settingsValues.mCorrectionEnabled) {
newSuggest.setAutoCorrectionThreshold(settingsValues.mAutoCorrectionThreshold);
}
@@ -558,11 +563,8 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
if (ProductionFlag.USES_DEVELOPMENT_ONLY_DIAGNOSTICS) {
ResearchLogger.getInstance().initSuggest(newSuggest);
}
-
- mUserDictionary = new UserBinaryDictionary(this, subtypeLocale);
- newSuggest.setUserDictionary(mUserDictionary);
- newSuggest.setAdditionalDictionaries(mInputLogic.mSuggest /* oldSuggest */,
- mSettings.getCurrent());
+ // TODO: Quit setting dictionaries from LatinIME.
+ newSuggest.setAdditionalDictionaries(mInputLogic.mSuggest /* oldSuggest */, settingsValues);
final Suggest oldSuggest = mInputLogic.mSuggest;
mInputLogic.mSuggest = newSuggest;
if (oldSuggest != null) oldSuggest.close();
@@ -590,7 +592,7 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
ResearchLogger.getInstance().onDestroy();
}
unregisterReceiver(mDictionaryPackInstallReceiver);
- PersonalizationDictionarySessionRegister.onDestroy(this);
+ PersonalizationDictionarySessionRegistrar.onDestroy(this);
LatinImeLogger.commit();
LatinImeLogger.onDestroy();
super.onDestroy();
@@ -610,7 +612,7 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
mOptionsDialog.dismiss();
}
}
- PersonalizationDictionarySessionRegister.onConfigurationChanged(this, conf);
+ PersonalizationDictionarySessionRegistrar.onConfigurationChanged(this, conf);
super.onConfigurationChanged(conf);
}
@@ -1145,7 +1147,7 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
@Override
public boolean onEvaluateFullscreenMode() {
- // Reread resource value here, because this method is called by framework anytime as needed.
+ // Reread resource value here, because this method is called by the framework as needed.
final boolean isFullscreenModeAllowed = Settings.readUseFullscreenMode(getResources());
if (super.onEvaluateFullscreenMode() && isFullscreenModeAllowed) {
// TODO: Remove this hack. Actually we should not really assume NO_EXTRACT_UI
@@ -1201,7 +1203,7 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
} else {
wordToEdit = word;
}
- mUserDictionary.addWordToUserDictionary(wordToEdit);
+ mInputLogic.mSuggest.addWordToUserDictionary(wordToEdit);
}
public void displaySettingsDialog() {
@@ -1270,7 +1272,7 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
@Override
public void onCancelBatchInput() {
- mInputLogic.onCancelBatchInput(mInputUpdater);
+ mInputLogic.onCancelBatchInput(mHandler, mInputUpdater);
}
// TODO[IL]: Make this a package-private standalone class in inputlogic/ and remove all
@@ -1308,17 +1310,15 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
return true;
}
- // Run in the UI thread.
+ // Run on the UI thread.
public void onStartBatchInput() {
synchronized (mLock) {
mHandler.removeMessages(MSG_UPDATE_GESTURE_PREVIEW_AND_SUGGESTION_STRIP);
mInBatchInput = true;
- mLatinIme.mHandler.showGesturePreviewAndSuggestionStrip(
- SuggestedWords.EMPTY, false /* dismissGestureFloatingPreviewText */);
}
}
- // Run in the Handler thread.
+ // Run on the Handler thread.
private void updateBatchInput(final InputPointers batchPointers, final int sequenceNumber) {
synchronized (mLock) {
if (!mInBatchInput) {
@@ -1337,7 +1337,7 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
}
}
- // Run in the UI thread.
+ // Run on the UI thread.
public void onUpdateBatchInput(final InputPointers batchPointers,
final int sequenceNumber) {
if (mHandler.hasMessages(MSG_UPDATE_GESTURE_PREVIEW_AND_SUGGESTION_STRIP)) {
@@ -1350,12 +1350,10 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
public void onCancelBatchInput() {
synchronized (mLock) {
mInBatchInput = false;
- mLatinIme.mHandler.showGesturePreviewAndSuggestionStrip(
- SuggestedWords.EMPTY, true /* dismissGestureFloatingPreviewText */);
}
}
- // Run in the UI thread.
+ // Run on the UI thread.
public void onEndBatchInput(final InputPointers batchPointers) {
synchronized(mLock) {
getSuggestedWordsGestureLocked(batchPointers, SuggestedWords.NOT_A_SEQUENCE_NUMBER,
@@ -1405,7 +1403,7 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
}
}
- // This method must run in UI Thread.
+ // This method must run on the UI Thread.
private void showGesturePreviewAndSuggestionStrip(final SuggestedWords suggestedWords,
final boolean dismissGestureFloatingPreviewText) {
showSuggestionStrip(suggestedWords);
@@ -1416,7 +1414,7 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
}
}
- // This method must run in UI Thread.
+ // This method must run on the UI Thread.
public void onEndBatchInputAsyncInternal(final SuggestedWords suggestedWords) {
final String batchInputText = suggestedWords.isEmpty() ? null : suggestedWords.getWord(0);
if (TextUtils.isEmpty(batchInputText)) {
@@ -1613,19 +1611,6 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
false /* isPrediction */);
}
- private void setAutoCorrection(final SuggestedWords suggestedWords, final String typedWord) {
- if (suggestedWords.isEmpty()) return;
- final String autoCorrection;
- if (suggestedWords.mWillAutoCorrect) {
- autoCorrection = suggestedWords.getWord(SuggestedWords.INDEX_OF_AUTO_CORRECTION);
- } else {
- // We can't use suggestedWords.getWord(SuggestedWords.INDEX_OF_TYPED_WORD)
- // because it may differ from mWordComposer.mTypedWord.
- autoCorrection = typedWord;
- }
- mInputLogic.mWordComposer.setAutoCorrection(autoCorrection);
- }
-
private void showSuggestionStripWithTypedWord(final SuggestedWords suggestedWords,
final String typedWord) {
if (suggestedWords.isEmpty()) {
@@ -1634,10 +1619,17 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
clearSuggestionStrip();
return;
}
- setAutoCorrection(suggestedWords, typedWord);
- final boolean isAutoCorrection = suggestedWords.willAutoCorrect();
- setSuggestedWords(suggestedWords, isAutoCorrection);
- setAutoCorrectionIndicator(isAutoCorrection);
+ final String autoCorrection;
+ if (suggestedWords.mWillAutoCorrect) {
+ autoCorrection = suggestedWords.getWord(SuggestedWords.INDEX_OF_AUTO_CORRECTION);
+ } else {
+ // We can't use suggestedWords.getWord(SuggestedWords.INDEX_OF_TYPED_WORD)
+ // because it may differ from mWordComposer.mTypedWord.
+ autoCorrection = typedWord;
+ }
+ mInputLogic.mWordComposer.setAutoCorrection(autoCorrection);
+ setSuggestedWords(suggestedWords, suggestedWords.mWillAutoCorrect);
+ setAutoCorrectionIndicator(suggestedWords.mWillAutoCorrect);
setSuggestionStripShown(isSuggestionsStripVisible());
// An auto-correction is available, cache it in accessibility code so
// we can be speak it if the user touches a key that will insert it.
@@ -1732,13 +1724,13 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
|| SuggestedWordInfo.KIND_OOV_CORRECTION == suggestionInfo.mKind)
&& suggest != null
// If the suggestion is not in the dictionary, the hint should be shown.
- && !AutoCorrectionUtils.isValidWord(suggest, suggestion, true);
+ && !suggest.isValidWord(suggestion, true);
if (currentSettings.mIsInternal) {
LatinImeLoggerUtils.onSeparator((char)Constants.CODE_SPACE,
Constants.NOT_A_COORDINATE, Constants.NOT_A_COORDINATE);
}
- if (showingAddToDictionaryHint && mUserDictionary.mEnabled) {
+ if (showingAddToDictionaryHint && suggest.isUserDictionaryEnabled()) {
mSuggestionStripView.showAddToDictionaryHint(
suggestion, currentSettings.mHintToSaveText);
} else {
diff --git a/java/src/com/android/inputmethod/latin/Suggest.java b/java/src/com/android/inputmethod/latin/Suggest.java
index 32ab1f3df..16841224f 100644
--- a/java/src/com/android/inputmethod/latin/Suggest.java
+++ b/java/src/com/android/inputmethod/latin/Suggest.java
@@ -17,7 +17,6 @@
package com.android.inputmethod.latin;
import android.content.Context;
-import android.preference.PreferenceManager;
import android.text.TextUtils;
import android.util.Log;
@@ -27,7 +26,6 @@ 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.Settings;
import com.android.inputmethod.latin.settings.SettingsValues;
import com.android.inputmethod.latin.utils.AutoCorrectionUtils;
import com.android.inputmethod.latin.utils.BoundedTreeSet;
@@ -39,11 +37,13 @@ import java.util.Comparator;
import java.util.HashSet;
import java.util.Locale;
import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.TimeUnit;
/**
* This class loads a dictionary and provides a list of suggestions for a given sequence of
* characters. This includes corrections and completions.
*/
+// TODO: Separate dictionary operations from suggestions handling logic.
public final class Suggest {
public static final String TAG = Suggest.class.getSimpleName();
@@ -74,6 +74,7 @@ public final class Suggest {
private HashSet<String> mOnlyDictionarySetForDebug = null;
private Dictionary mMainDictionary;
private ContactsBinaryDictionary mContactsDictionary;
+ private UserBinaryDictionary mUserDictionary;
private UserHistoryDictionary mUserHistoryDictionary;
private PersonalizationDictionary mPersonalizationDictionary;
@UsedForTesting
@@ -86,18 +87,17 @@ public final class Suggest {
private final Context mContext;
- public Suggest(final Context context, final Locale locale,
+ public Suggest(final Context context, final Locale locale, final SettingsValues settingsValues,
final SuggestInitializationListener listener) {
initAsynchronously(context, locale, listener);
mLocale = locale;
mContext = context;
- // TODO: Use SettingsValues instead of Settings.
// initialize a debug flag for the personalization
- if (Settings.readUseOnlyPersonalizationDictionaryForDebug(
- PreferenceManager.getDefaultSharedPreferences(context))) {
+ if (settingsValues.mUseOnlyPersonalizationDictionaryForDebug) {
mOnlyDictionarySetForDebug = new HashSet<String>();
mOnlyDictionarySetForDebug.add(Dictionary.TYPE_PERSONALIZATION);
}
+ setUserDictionary(new UserBinaryDictionary(context, locale));
}
@UsedForTesting
@@ -171,27 +171,13 @@ public final class Suggest {
return mMainDictionary;
}
- public ContactsBinaryDictionary getContactsDictionary() {
- return mContactsDictionary;
- }
-
- public UserHistoryDictionary getUserHistoryDictionary() {
- return mUserHistoryDictionary;
- }
-
- public PersonalizationDictionary getPersonalizationDictionary() {
- return mPersonalizationDictionary;
- }
-
- public ConcurrentHashMap<String, Dictionary> getUnigramDictionaries() {
- return mDictionaries;
- }
-
/**
* 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.
*/
+ @UsedForTesting
public void setUserDictionary(final UserBinaryDictionary userDictionary) {
+ mUserDictionary = userDictionary;
addOrReplaceDictionaryInternal(Dictionary.TYPE_USER, userDictionary);
}
@@ -200,17 +186,18 @@ public final class Suggest {
* the contacts dictionary by passing null to this method. In this case no contacts dictionary
* won't be used.
*/
+ @UsedForTesting
public void setContactsDictionary(final ContactsBinaryDictionary contactsDictionary) {
mContactsDictionary = contactsDictionary;
addOrReplaceDictionaryInternal(Dictionary.TYPE_CONTACTS, contactsDictionary);
}
- public void setUserHistoryDictionary(final UserHistoryDictionary userHistoryDictionary) {
+ private void setUserHistoryDictionary(final UserHistoryDictionary userHistoryDictionary) {
mUserHistoryDictionary = userHistoryDictionary;
addOrReplaceDictionaryInternal(Dictionary.TYPE_USER_HISTORY, userHistoryDictionary);
}
- public void setPersonalizationDictionary(
+ private void setPersonalizationDictionary(
final PersonalizationDictionary personalizationDictionary) {
mPersonalizationDictionary = personalizationDictionary;
addOrReplaceDictionaryInternal(Dictionary.TYPE_PERSONALIZATION, personalizationDictionary);
@@ -225,7 +212,7 @@ public final class Suggest {
public void setAdditionalDictionaries(final Suggest oldSuggest,
final SettingsValues settingsValues) {
// Contacts dictionary
- resetContactsDictionary(null != oldSuggest ? oldSuggest.getContactsDictionary() : null,
+ resetContactsDictionary(null != oldSuggest ? oldSuggest.mContactsDictionary : null,
settingsValues);
// User history dictionary & Personalization dictionary
resetPersonalizedDictionaries(oldSuggest, settingsValues);
@@ -245,9 +232,9 @@ public final class Suggest {
final boolean shouldSetDictionaries = settingsValues.mUsePersonalizedDicts;
final UserHistoryDictionary oldUserHistoryDictionary = (null == oldSuggest) ? null :
- oldSuggest.getUserHistoryDictionary();
+ oldSuggest.mUserHistoryDictionary;
final PersonalizationDictionary oldPersonalizationDictionary = (null == oldSuggest) ? null :
- oldSuggest.getPersonalizationDictionary();
+ oldSuggest.mPersonalizationDictionary;
final UserHistoryDictionary userHistoryDictionaryToUse;
final PersonalizationDictionary personalizationDictionaryToUse;
if (!shouldSetDictionaries) {
@@ -311,6 +298,43 @@ public final class Suggest {
setContactsDictionary(dictionaryToUse);
}
+ public boolean isUserDictionaryEnabled() {
+ if (mUserDictionary == null) {
+ return false;
+ }
+ return mUserDictionary.mEnabled;
+ }
+
+ public void addWordToUserDictionary(String word) {
+ if (mUserDictionary == null) {
+ return;
+ }
+ mUserDictionary.addWordToUserDictionary(word);
+ }
+
+ public String addToUserHistory(final WordComposer wordComposer, final String previousWord,
+ final String suggestion) {
+ if (mUserHistoryDictionary == null) {
+ return null;
+ }
+ final String secondWord;
+ if (wordComposer.wasAutoCapitalized() && !wordComposer.isMostlyCaps()) {
+ secondWord = suggestion.toLowerCase(mLocale);
+ } else {
+ secondWord = suggestion;
+ }
+ // 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 int maxFreq = getMaxFrequency(suggestion);
+ if (maxFreq == 0) {
+ return null;
+ }
+ final boolean isValid = maxFreq > 0;
+ final int timeStamp = (int)TimeUnit.MILLISECONDS.toSeconds((System.currentTimeMillis()));
+ mUserHistoryDictionary.addToDictionary(previousWord, secondWord, isValid, timeStamp);
+ return previousWord;
+ }
+
public void cancelAddingUserHistory(final String previousWord, final String committedWord) {
if (mUserHistoryDictionary != null) {
mUserHistoryDictionary.cancelAddingUserHistory(previousWord, committedWord);
@@ -389,8 +413,8 @@ public final class Suggest {
// or if it's a 2+ characters non-word (i.e. it's not in the dictionary).
final boolean allowsToBeAutoCorrected = (null != whitelistedWord
&& !whitelistedWord.equals(consideredWord))
- || (consideredWord.length() > 1 && !AutoCorrectionUtils.isValidWord(this,
- consideredWord, wordComposer.isFirstCharCapitalized()));
+ || (consideredWord.length() > 1
+ && !isValidWord(consideredWord, wordComposer.isFirstCharCapitalized()));
final boolean hasAutoCorrection;
// TODO: using isCorrectionEnabled here is not very good. It's probably useless, because
@@ -594,6 +618,45 @@ public final class Suggest {
wordInfo.mAutoCommitFirstWordConfidence);
}
+ 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?
+ // 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.
+ if (null == dictionary) continue;
+ if (dictionary.isValidWord(word)
+ || (ignoreCase && dictionary.isValidWord(lowerCasedWord))) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ private int getMaxFrequency(final String word) {
+ if (TextUtils.isEmpty(word)) {
+ 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 int tempFreq = dictionary.getFrequency(word);
+ if (tempFreq >= maxFreq) {
+ maxFreq = tempFreq;
+ }
+ }
+ return maxFreq;
+ }
+
public void close() {
final HashSet<Dictionary> dictionaries = CollectionUtils.newHashSet();
dictionaries.addAll(mDictionaries.values());
diff --git a/java/src/com/android/inputmethod/latin/SuggestedWords.java b/java/src/com/android/inputmethod/latin/SuggestedWords.java
index 97c89dd4e..f9de89c80 100644
--- a/java/src/com/android/inputmethod/latin/SuggestedWords.java
+++ b/java/src/com/android/inputmethod/latin/SuggestedWords.java
@@ -104,10 +104,6 @@ public final class SuggestedWords {
return debugString;
}
- public boolean willAutoCorrect() {
- return mWillAutoCorrect;
- }
-
@Override
public String toString() {
// Pretty-print method to help debug
@@ -150,7 +146,7 @@ public final class SuggestedWords {
for (int index = 1; index < previousSize; index++) {
final SuggestedWordInfo prevWordInfo = previousSuggestions.getInfo(index);
final String prevWord = prevWordInfo.mWord;
- // Filter out duplicate suggestion.
+ // Filter out duplicate suggestions.
if (!alreadySeen.contains(prevWord)) {
suggestionsList.add(prevWordInfo);
alreadySeen.add(prevWord);
diff --git a/java/src/com/android/inputmethod/latin/inputlogic/InputLogic.java b/java/src/com/android/inputmethod/latin/inputlogic/InputLogic.java
index df34687e3..4a4abd7d2 100644
--- a/java/src/com/android/inputmethod/latin/inputlogic/InputLogic.java
+++ b/java/src/com/android/inputmethod/latin/inputlogic/InputLogic.java
@@ -44,12 +44,10 @@ import com.android.inputmethod.latin.SuggestedWords;
import com.android.inputmethod.latin.SuggestedWords.SuggestedWordInfo;
import com.android.inputmethod.latin.WordComposer;
import com.android.inputmethod.latin.define.ProductionFlag;
-import com.android.inputmethod.latin.personalization.UserHistoryDictionary;
import com.android.inputmethod.latin.settings.Settings;
import com.android.inputmethod.latin.settings.SettingsValues;
import com.android.inputmethod.latin.suggestions.SuggestionStripView;
import com.android.inputmethod.latin.utils.AsyncResultHolder;
-import com.android.inputmethod.latin.utils.AutoCorrectionUtils;
import com.android.inputmethod.latin.utils.CollectionUtils;
import com.android.inputmethod.latin.utils.InputTypeUtils;
import com.android.inputmethod.latin.utils.LatinImeLoggerUtils;
@@ -60,7 +58,6 @@ import com.android.inputmethod.research.ResearchLogger;
import java.util.ArrayList;
import java.util.TreeSet;
-import java.util.concurrent.TimeUnit;
/**
* This class manages the input logic.
@@ -71,6 +68,8 @@ public final class InputLogic {
// TODO : Remove this member when we can.
private final LatinIME mLatinIME;
+ private InputLogicHandler mInputLogicHandler;
+
// TODO : make all these fields private as soon as possible.
// Current space state of the input method. This can be any of the above constants.
public int mSpaceState;
@@ -105,6 +104,7 @@ public final class InputLogic {
mWordComposer = new WordComposer();
mEventInterpreter = new EventInterpreter(latinIME);
mConnection = new RichInputConnection(latinIME);
+ mInputLogicHandler = null;
}
/**
@@ -119,12 +119,15 @@ public final class InputLogic {
* @param restarting whether input is starting in the same field as before.
*/
public void startInput(final boolean restarting) {
+ mInputLogicHandler = new InputLogicHandler();
}
/**
* Clean up the input logic after input is finished.
*/
public void finishInput() {
+ mInputLogicHandler.destroy();
+ mInputLogicHandler = null;
}
/**
@@ -300,6 +303,8 @@ public final class InputLogic {
final KeyboardSwitcher keyboardSwitcher, final LatinIME.UIHandler handler,
final LatinIME.InputUpdater inputUpdater) {
inputUpdater.onStartBatchInput();
+ handler.showGesturePreviewAndSuggestionStrip(
+ SuggestedWords.EMPTY, false /* dismissGestureFloatingPreviewText */);
handler.cancelUpdateSuggestionStrip();
mConnection.beginBatchEdit();
if (mWordComposer.isComposingWord()) {
@@ -400,9 +405,12 @@ public final class InputLogic {
inputUpdater.onEndBatchInput(batchPointers);
}
- // TODO: remove this argument
- public void onCancelBatchInput(final LatinIME.InputUpdater inputUpdater) {
+ // TODO: remove these arguments
+ public void onCancelBatchInput(final LatinIME.UIHandler handler,
+ final LatinIME.InputUpdater inputUpdater) {
inputUpdater.onCancelBatchInput();
+ handler.showGesturePreviewAndSuggestionStrip(
+ SuggestedWords.EMPTY, true /* dismissGestureFloatingPreviewText */);
}
/**
@@ -977,24 +985,8 @@ public final class InputLogic {
final Suggest suggest = mSuggest;
if (suggest == null) return null;
- final UserHistoryDictionary userHistoryDictionary = suggest.getUserHistoryDictionary();
- if (userHistoryDictionary == null) return null;
-
final String prevWord = mConnection.getNthPreviousWord(settingsValues, 2);
- final String secondWord;
- if (mWordComposer.wasAutoCapitalized() && !mWordComposer.isMostlyCaps()) {
- secondWord = suggestion.toLowerCase(settingsValues.mLocale);
- } else {
- secondWord = suggestion;
- }
- // 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 int maxFreq = AutoCorrectionUtils.getMaxFrequency(
- suggest.getUnigramDictionaries(), suggestion);
- if (maxFreq == 0) return null;
- userHistoryDictionary.addToDictionary(prevWord, secondWord, maxFreq > 0,
- (int)TimeUnit.MILLISECONDS.toSeconds((System.currentTimeMillis())));
- return prevWord;
+ return suggest.addToUserHistory(mWordComposer, prevWord, suggestion);
}
public void performUpdateSuggestionStripSync(final SettingsValues settingsValues,
diff --git a/java/src/com/android/inputmethod/latin/inputlogic/InputLogicHandler.java b/java/src/com/android/inputmethod/latin/inputlogic/InputLogicHandler.java
new file mode 100644
index 000000000..d611e4bf8
--- /dev/null
+++ b/java/src/com/android/inputmethod/latin/inputlogic/InputLogicHandler.java
@@ -0,0 +1,49 @@
+/*
+ * Copyright (C) 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.inputmethod.latin.inputlogic;
+
+import android.os.Handler;
+import android.os.HandlerThread;
+import android.os.Message;
+
+/**
+ * A helper to manage deferred tasks for the input logic.
+ */
+// TODO: Make this package private
+public class InputLogicHandler implements Handler.Callback {
+ final Handler mNonUIThreadHandler;
+
+ public InputLogicHandler() {
+ final HandlerThread handlerThread = new HandlerThread(
+ InputLogicHandler.class.getSimpleName());
+ handlerThread.start();
+ mNonUIThreadHandler = new Handler(handlerThread.getLooper(), this);
+ }
+
+ public void destroy() {
+ mNonUIThreadHandler.getLooper().quit();
+ }
+
+ /**
+ * Handle a message.
+ * @see android.os.Handler.Callback#handleMessage(android.os.Message)
+ */
+ @Override
+ public boolean handleMessage(final Message msg) {
+ return true;
+ }
+}
diff --git a/java/src/com/android/inputmethod/latin/personalization/PersonalizationDictionary.java b/java/src/com/android/inputmethod/latin/personalization/PersonalizationDictionary.java
index 596562f1d..9b2b981d5 100644
--- a/java/src/com/android/inputmethod/latin/personalization/PersonalizationDictionary.java
+++ b/java/src/com/android/inputmethod/latin/personalization/PersonalizationDictionary.java
@@ -27,7 +27,7 @@ import java.util.Locale;
import android.content.Context;
public class PersonalizationDictionary extends DecayingExpandableBinaryDictionaryBase {
- private static final String NAME = PersonalizationDictionary.class.getSimpleName();
+ /* package */ static final String NAME = PersonalizationDictionary.class.getSimpleName();
private final ArrayList<PersonalizationDictionaryUpdateSession> mSessions =
CollectionUtils.newArrayList();
diff --git a/java/src/com/android/inputmethod/latin/personalization/PersonalizationDictionarySessionRegister.java b/java/src/com/android/inputmethod/latin/personalization/PersonalizationDictionarySessionRegistrar.java
index 542bda621..76965112f 100644
--- a/java/src/com/android/inputmethod/latin/personalization/PersonalizationDictionarySessionRegister.java
+++ b/java/src/com/android/inputmethod/latin/personalization/PersonalizationDictionarySessionRegistrar.java
@@ -19,7 +19,7 @@ package com.android.inputmethod.latin.personalization;
import android.content.Context;
import android.content.res.Configuration;
-public class PersonalizationDictionarySessionRegister {
+public class PersonalizationDictionarySessionRegistrar {
public static void init(final Context context) {
}
@@ -32,6 +32,9 @@ public class PersonalizationDictionarySessionRegister {
public static void onRemoveData(final Context context, final String type) {
}
+ public static void resetAll(final Context context) {
+ }
+
public static void onDestroy(final Context context) {
}
}
diff --git a/java/src/com/android/inputmethod/latin/personalization/PersonalizationHelper.java b/java/src/com/android/inputmethod/latin/personalization/PersonalizationHelper.java
index d55cae132..38b22e5f6 100644
--- a/java/src/com/android/inputmethod/latin/personalization/PersonalizationHelper.java
+++ b/java/src/com/android/inputmethod/latin/personalization/PersonalizationHelper.java
@@ -17,10 +17,13 @@
package com.android.inputmethod.latin.personalization;
import com.android.inputmethod.latin.utils.CollectionUtils;
+import com.android.inputmethod.latin.utils.FileUtils;
import android.content.Context;
import android.util.Log;
+import java.io.File;
+import java.io.FilenameFilter;
import java.lang.ref.SoftReference;
import java.util.Locale;
import java.util.concurrent.ConcurrentHashMap;
@@ -96,4 +99,47 @@ public class PersonalizationHelper {
return dict;
}
}
+
+ public static void removeAllPersonalizedDictionaries(final Context context) {
+ removeAllDictionaries(context, sLangUserHistoryDictCache,
+ UserHistoryDictionary.NAME);
+ removeAllDictionaries(context, sLangPersonalizationDictCache,
+ PersonalizationDictionary.NAME);
+ }
+
+ private static <T extends DecayingExpandableBinaryDictionaryBase> void removeAllDictionaries(
+ final Context context, final ConcurrentHashMap<String, SoftReference<T>> dictionaryMap,
+ final String dictNamePrefix) {
+ synchronized (dictionaryMap) {
+ for (final ConcurrentHashMap.Entry<String, SoftReference<T>> entry
+ : dictionaryMap.entrySet()) {
+ if (entry.getValue() != null) {
+ final DecayingExpandableBinaryDictionaryBase dict = entry.getValue().get();
+ if (dict != null) {
+ dict.clearAndFlushDictionary();
+ }
+ }
+ }
+ dictionaryMap.clear();
+ if (!FileUtils.deleteFilteredFiles(
+ context.getFilesDir(), new DictFilter(dictNamePrefix))) {
+ Log.e(TAG, "Cannot remove all existing dictionary files. filesDir: "
+ + context.getFilesDir().getAbsolutePath() + ", dictNamePrefix: "
+ + dictNamePrefix);
+ }
+ }
+ }
+
+ private static class DictFilter implements FilenameFilter {
+ private final String mName;
+
+ DictFilter(final String name) {
+ mName = name;
+ }
+
+ @Override
+ public boolean accept(final File dir, final String name) {
+ return name.startsWith(mName);
+ }
+ }
}
diff --git a/java/src/com/android/inputmethod/latin/personalization/UserHistoryDictionary.java b/java/src/com/android/inputmethod/latin/personalization/UserHistoryDictionary.java
index 868f21cbc..c23bc9bc0 100644
--- a/java/src/com/android/inputmethod/latin/personalization/UserHistoryDictionary.java
+++ b/java/src/com/android/inputmethod/latin/personalization/UserHistoryDictionary.java
@@ -29,8 +29,7 @@ import android.content.Context;
* cancellation or manual picks. This allows the keyboard to adapt to the typist over time.
*/
public class UserHistoryDictionary extends DecayingExpandableBinaryDictionaryBase {
- /* package for tests */ static final String NAME =
- UserHistoryDictionary.class.getSimpleName();
+ /* package */ static final String NAME = UserHistoryDictionary.class.getSimpleName();
/* package */ UserHistoryDictionary(final Context context, final Locale locale) {
super(context, locale, Dictionary.TYPE_USER_HISTORY, getDictNameWithLocale(NAME, locale));
}
diff --git a/java/src/com/android/inputmethod/latin/settings/DebugSettings.java b/java/src/com/android/inputmethod/latin/settings/DebugSettings.java
index d060485bd..29bbed8bd 100644
--- a/java/src/com/android/inputmethod/latin/settings/DebugSettings.java
+++ b/java/src/com/android/inputmethod/latin/settings/DebugSettings.java
@@ -39,8 +39,6 @@ public final class DebugSettings extends PreferenceFragment
public static final String PREF_STATISTICS_LOGGING = "enable_logging";
public static final String PREF_USE_ONLY_PERSONALIZATION_DICTIONARY_FOR_DEBUG =
"use_only_personalization_dictionary_for_debug";
- public static final String PREF_BOOST_PERSONALIZATION_DICTIONARY_FOR_DEBUG =
- "boost_personalization_dictionary_for_debug";
private static final String PREF_READ_EXTERNAL_DICTIONARY = "read_external_dictionary";
private static final boolean SHOW_STATISTICS_LOGGING = false;
diff --git a/java/src/com/android/inputmethod/latin/settings/Settings.java b/java/src/com/android/inputmethod/latin/settings/Settings.java
index 9bd2b9389..75c7258ae 100644
--- a/java/src/com/android/inputmethod/latin/settings/Settings.java
+++ b/java/src/com/android/inputmethod/latin/settings/Settings.java
@@ -358,21 +358,13 @@ public final class Settings implements SharedPreferences.OnSharedPreferenceChang
return prefs.getBoolean(Settings.PREF_KEY_IS_INTERNAL, false);
}
- public static boolean readUseOnlyPersonalizationDictionaryForDebug(
- final SharedPreferences prefs) {
- return prefs.getBoolean(
- DebugSettings.PREF_USE_ONLY_PERSONALIZATION_DICTIONARY_FOR_DEBUG, false);
- }
-
- public static boolean readBoostPersonalizationDictionaryForDebug(
- final SharedPreferences prefs) {
- return prefs.getBoolean(
- DebugSettings.PREF_BOOST_PERSONALIZATION_DICTIONARY_FOR_DEBUG, false);
- }
-
public void writeLastUsedPersonalizationToken(byte[] token) {
- final String tokenStr = StringUtils.byteArrayToHexString(token);
- mPrefs.edit().putString(PREF_LAST_USED_PERSONALIZATION_TOKEN, tokenStr).apply();
+ if (token == null) {
+ mPrefs.edit().remove(PREF_LAST_USED_PERSONALIZATION_TOKEN).apply();
+ } else {
+ final String tokenStr = StringUtils.byteArrayToHexString(token);
+ mPrefs.edit().putString(PREF_LAST_USED_PERSONALIZATION_TOKEN, tokenStr).apply();
+ }
}
public byte[] readLastUsedPersonalizationToken() {
diff --git a/java/src/com/android/inputmethod/latin/settings/SettingsValues.java b/java/src/com/android/inputmethod/latin/settings/SettingsValues.java
index 3307de8ae..a07a0cecf 100644
--- a/java/src/com/android/inputmethod/latin/settings/SettingsValues.java
+++ b/java/src/com/android/inputmethod/latin/settings/SettingsValues.java
@@ -99,7 +99,6 @@ public final class SettingsValues {
public final float mAutoCorrectionThreshold;
public final boolean mCorrectionEnabled;
public final int mSuggestionVisibility;
- public final boolean mBoostPersonalizationDictionaryForDebug;
public final boolean mUseOnlyPersonalizationDictionaryForDebug;
public final int mDisplayOrientation;
private final AsyncResultHolder<AppWorkaroundsUtils> mAppWorkarounds;
@@ -181,10 +180,8 @@ public final class SettingsValues {
AdditionalFeaturesSettingUtils.readAdditionalFeaturesPreferencesIntoArray(
prefs, mAdditionalFeaturesSettingValues);
mIsInternal = Settings.isInternal(prefs);
- mBoostPersonalizationDictionaryForDebug =
- Settings.readBoostPersonalizationDictionaryForDebug(prefs);
- mUseOnlyPersonalizationDictionaryForDebug =
- Settings.readUseOnlyPersonalizationDictionaryForDebug(prefs);
+ mUseOnlyPersonalizationDictionaryForDebug = prefs.getBoolean(
+ DebugSettings.PREF_USE_ONLY_PERSONALIZATION_DICTIONARY_FOR_DEBUG, false);
mDisplayOrientation = res.getConfiguration().orientation;
mAppWorkarounds = new AsyncResultHolder<AppWorkaroundsUtils>();
final PackageInfo packageInfo = TargetPackageInfoGetterTask.getCachedPackageInfo(
@@ -241,7 +238,6 @@ public final class SettingsValues {
mCorrectionEnabled = mAutoCorrectEnabled && !mInputAttributes.mInputTypeNoAutoCorrect;
mSuggestionVisibility = 0;
mIsInternal = false;
- mBoostPersonalizationDictionaryForDebug = false;
mUseOnlyPersonalizationDictionaryForDebug = false;
mDisplayOrientation = Configuration.ORIENTATION_PORTRAIT;
mAppWorkarounds = new AsyncResultHolder<AppWorkaroundsUtils>();
@@ -321,7 +317,7 @@ public final class SettingsValues {
public boolean isBrokenByRecorrection() {
final AppWorkaroundsUtils appWorkaroundUtils
= mAppWorkarounds.get(null, TIMEOUT_TO_GET_TARGET_PACKAGE);
- return null == appWorkaroundUtils ? null : appWorkaroundUtils.isBrokenByRecorrection();
+ return null == appWorkaroundUtils ? false : appWorkaroundUtils.isBrokenByRecorrection();
}
// Helper functions to create member values.
diff --git a/java/src/com/android/inputmethod/latin/suggestions/SuggestionStripLayoutHelper.java b/java/src/com/android/inputmethod/latin/suggestions/SuggestionStripLayoutHelper.java
index 72281e62c..f836e61cb 100644
--- a/java/src/com/android/inputmethod/latin/suggestions/SuggestionStripLayoutHelper.java
+++ b/java/src/com/android/inputmethod/latin/suggestions/SuggestionStripLayoutHelper.java
@@ -208,7 +208,7 @@ final class SuggestionStripLayoutHelper {
}
final String word = suggestedWords.getWord(indexInSuggestedWords);
final boolean isAutoCorrect = indexInSuggestedWords == 1
- && suggestedWords.willAutoCorrect();
+ && suggestedWords.mWillAutoCorrect;
final boolean isTypedWordValid = indexInSuggestedWords == 0
&& suggestedWords.mTypedWordValid;
if (!isAutoCorrect && !isTypedWordValid) {
@@ -232,7 +232,7 @@ final class SuggestionStripLayoutHelper {
final SuggestedWords suggestedWords) {
final int indexToDisplayMostImportantSuggestion;
final int indexToDisplaySecondMostImportantSuggestion;
- if (suggestedWords.willAutoCorrect()) {
+ if (suggestedWords.mWillAutoCorrect) {
indexToDisplayMostImportantSuggestion = SuggestedWords.INDEX_OF_AUTO_CORRECTION;
indexToDisplaySecondMostImportantSuggestion = SuggestedWords.INDEX_OF_TYPED_WORD;
} else {
@@ -257,7 +257,7 @@ final class SuggestionStripLayoutHelper {
final boolean isSuggested = (indexInSuggestedWords != SuggestedWords.INDEX_OF_TYPED_WORD);
final int color;
- if (positionInStrip == mCenterPositionInStrip && suggestedWords.willAutoCorrect()) {
+ if (positionInStrip == mCenterPositionInStrip && suggestedWords.mWillAutoCorrect) {
color = mColorAutoCorrect;
} else if (positionInStrip == mCenterPositionInStrip && suggestedWords.mTypedWordValid) {
color = mColorValidTypedWord;
diff --git a/java/src/com/android/inputmethod/latin/utils/AutoCorrectionUtils.java b/java/src/com/android/inputmethod/latin/utils/AutoCorrectionUtils.java
index 066c5fd32..37c173f96 100644
--- a/java/src/com/android/inputmethod/latin/utils/AutoCorrectionUtils.java
+++ b/java/src/com/android/inputmethod/latin/utils/AutoCorrectionUtils.java
@@ -17,16 +17,11 @@
package com.android.inputmethod.latin.utils;
import com.android.inputmethod.latin.BinaryDictionary;
-import com.android.inputmethod.latin.Dictionary;
import com.android.inputmethod.latin.LatinImeLogger;
-import com.android.inputmethod.latin.Suggest;
import com.android.inputmethod.latin.SuggestedWords.SuggestedWordInfo;
-import android.text.TextUtils;
import android.util.Log;
-import java.util.concurrent.ConcurrentHashMap;
-
public final class AutoCorrectionUtils {
private static final boolean DBG = LatinImeLogger.sDBG;
private static final String TAG = AutoCorrectionUtils.class.getSimpleName();
@@ -36,48 +31,6 @@ public final class AutoCorrectionUtils {
// Purely static class: can't instantiate.
}
- public static boolean isValidWord(final Suggest suggest, final String word,
- final boolean ignoreCase) {
- if (TextUtils.isEmpty(word)) {
- return false;
- }
- final ConcurrentHashMap<String, Dictionary> dictionaries = suggest.getUnigramDictionaries();
- final String lowerCasedWord = word.toLowerCase(suggest.mLocale);
- for (final String key : dictionaries.keySet()) {
- final Dictionary dictionary = dictionaries.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?
- // 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.
- if (null == dictionary) continue;
- if (dictionary.isValidWord(word)
- || (ignoreCase && dictionary.isValidWord(lowerCasedWord))) {
- return true;
- }
- }
- return false;
- }
-
- public static int getMaxFrequency(final ConcurrentHashMap<String, Dictionary> dictionaries,
- final String word) {
- if (TextUtils.isEmpty(word)) {
- return Dictionary.NOT_A_PROBABILITY;
- }
- int maxFreq = -1;
- for (final String key : dictionaries.keySet()) {
- final Dictionary dictionary = dictionaries.get(key);
- if (null == dictionary) continue;
- final int tempFreq = dictionary.getFrequency(word);
- if (tempFreq >= maxFreq) {
- maxFreq = tempFreq;
- }
- }
- return maxFreq;
- }
-
public static boolean suggestionExceedsAutoCorrectionThreshold(
final SuggestedWordInfo suggestion, final String consideredWord,
final float autoCorrectionThreshold) {
diff --git a/java/src/com/android/inputmethod/latin/utils/FileUtils.java b/java/src/com/android/inputmethod/latin/utils/FileUtils.java
index 83c1e7c4d..22b0fbbcd 100644
--- a/java/src/com/android/inputmethod/latin/utils/FileUtils.java
+++ b/java/src/com/android/inputmethod/latin/utils/FileUtils.java
@@ -17,6 +17,7 @@
package com.android.inputmethod.latin.utils;
import java.io.File;
+import java.io.FilenameFilter;
/**
* A simple class to help with removing directories recursively.
@@ -30,4 +31,18 @@ public class FileUtils {
}
return path.delete();
}
+
+ public static boolean deleteFilteredFiles(final File dir, final FilenameFilter fileNameFilter) {
+ if (!dir.isDirectory()) {
+ return false;
+ }
+ final File[] files = dir.listFiles(fileNameFilter);
+ boolean hasDeletedAllFiles = true;
+ for (final File file : files) {
+ if (!deleteRecursively(file)) {
+ hasDeletedAllFiles = false;
+ }
+ }
+ return hasDeletedAllFiles;
+ }
}