diff options
Diffstat (limited to 'java/src/com/android/inputmethod/latin')
5 files changed, 293 insertions, 155 deletions
diff --git a/java/src/com/android/inputmethod/latin/LatinIME.java b/java/src/com/android/inputmethod/latin/LatinIME.java index 69b044e5c..a64b03ab5 100644 --- a/java/src/com/android/inputmethod/latin/LatinIME.java +++ b/java/src/com/android/inputmethod/latin/LatinIME.java @@ -497,7 +497,7 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen // Note that the calling sequence of onCreate() and onCurrentInputMethodSubtypeChanged() // is not guaranteed. It may even be called at the same time on a different thread. if (null == mPrefs) mPrefs = PreferenceManager.getDefaultSharedPreferences(this); - mUserHistoryDictionary = new UserHistoryDictionary( + mUserHistoryDictionary = UserHistoryDictionary.getInstance( this, localeStr, Suggest.DIC_USER_HISTORY, mPrefs); mSuggest.setUserHistoryDictionary(mUserHistoryDictionary); } @@ -982,7 +982,9 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen final KeyboardView inputView = mKeyboardSwitcher.getKeyboardView(); if (inputView == null || mSuggestionsContainer == null) return; - final int backingHeight = getAdjustedBackingViewHeight(); + final int adjustedBackingHeight = getAdjustedBackingViewHeight(); + final boolean backingGone = (mKeyPreviewBackingView.getVisibility() == View.GONE); + final int backingHeight = backingGone ? 0 : adjustedBackingHeight; // In fullscreen mode, the height of the extract area managed by InputMethodService should // be considered. // See {@link android.inputmethodservice.InputMethodService#onComputeInsets}. diff --git a/java/src/com/android/inputmethod/latin/SynchronouslyLoadedContactsBinaryDictionary.java b/java/src/com/android/inputmethod/latin/SynchronouslyLoadedContactsBinaryDictionary.java index 4994e5902..673b54500 100644 --- a/java/src/com/android/inputmethod/latin/SynchronouslyLoadedContactsBinaryDictionary.java +++ b/java/src/com/android/inputmethod/latin/SynchronouslyLoadedContactsBinaryDictionary.java @@ -20,12 +20,13 @@ import android.content.Context; import com.android.inputmethod.keyboard.ProximityInfo; +import java.util.Locale; + public class SynchronouslyLoadedContactsBinaryDictionary extends ContactsBinaryDictionary { private boolean mClosed; - public SynchronouslyLoadedContactsBinaryDictionary(final Context context) { - // TODO: add locale information. - super(context, Suggest.DIC_CONTACTS, null); + public SynchronouslyLoadedContactsBinaryDictionary(final Context context, final Locale locale) { + super(context, Suggest.DIC_CONTACTS, locale); } @Override @@ -51,4 +52,4 @@ public class SynchronouslyLoadedContactsBinaryDictionary extends ContactsBinaryD mClosed = true; super.close(); } -}
\ No newline at end of file +} diff --git a/java/src/com/android/inputmethod/latin/UserHistoryDictionary.java b/java/src/com/android/inputmethod/latin/UserHistoryDictionary.java index c8ad40b12..a73e71bb2 100644 --- a/java/src/com/android/inputmethod/latin/UserHistoryDictionary.java +++ b/java/src/com/android/inputmethod/latin/UserHistoryDictionary.java @@ -29,7 +29,9 @@ import android.util.Log; import com.android.inputmethod.latin.UserHistoryForgettingCurveUtils.ForgettingCurveParams; +import java.lang.ref.SoftReference; import java.util.HashMap; +import java.util.concurrent.ConcurrentHashMap; /** * Locally gathers stats about the words user types and various other signals like auto-correction @@ -38,6 +40,7 @@ import java.util.HashMap; public class UserHistoryDictionary extends ExpandableDictionary { private static final String TAG = "UserHistoryDictionary"; public static final boolean DBG_SAVE_RESTORE = false; + public static final boolean PROFILE_SAVE_RESTORE = LatinImeLogger.sDBG; /** Any pair being typed or picked */ private static final int FREQUENCY_FOR_TYPED = 2; @@ -72,18 +75,19 @@ public class UserHistoryDictionary extends ExpandableDictionary { private static final String FREQ_TABLE_NAME = "frequency"; private static final String FREQ_COLUMN_ID = BaseColumns._ID; private static final String FREQ_COLUMN_PAIR_ID = "pair_id"; - private static final String FREQ_COLUMN_FREQUENCY = "freq"; + private static final String COLUMN_FORGETTING_CURVE_VALUE = "freq"; - /** Locale for which this auto dictionary is storing words */ - private String mLocale; + /** Locale for which this user history dictionary is storing words */ + private final String mLocale; - private UserHistoryDictionaryBigramList mBigramList = + private final UserHistoryDictionaryBigramList mBigramList = new UserHistoryDictionaryBigramList(); - private final Object mPendingWritesLock = new Object(); private static volatile boolean sUpdatingDB = false; private final SharedPreferences mPrefs; private final static HashMap<String, String> sDictProjectionMap; + private final static ConcurrentHashMap<String, SoftReference<UserHistoryDictionary>> + sLangDictCache = new ConcurrentHashMap<String, SoftReference<UserHistoryDictionary>>(); static { sDictProjectionMap = new HashMap<String, String>(); @@ -94,7 +98,7 @@ public class UserHistoryDictionary extends ExpandableDictionary { sDictProjectionMap.put(FREQ_COLUMN_ID, FREQ_COLUMN_ID); sDictProjectionMap.put(FREQ_COLUMN_PAIR_ID, FREQ_COLUMN_PAIR_ID); - sDictProjectionMap.put(FREQ_COLUMN_FREQUENCY, FREQ_COLUMN_FREQUENCY); + sDictProjectionMap.put(COLUMN_FORGETTING_CURVE_VALUE, COLUMN_FORGETTING_CURVE_VALUE); } private static DatabaseHelper sOpenHelper = null; @@ -107,7 +111,26 @@ public class UserHistoryDictionary extends ExpandableDictionary { sDeleteHistoryBigrams = deleteHistoryBigram; } - public UserHistoryDictionary(final Context context, final String locale, final int dicTypeId, + public synchronized static UserHistoryDictionary getInstance( + final Context context, final String locale, + final int dictTypeId, final SharedPreferences sp) { + if (sLangDictCache.containsKey(locale)) { + final SoftReference<UserHistoryDictionary> ref = sLangDictCache.get(locale); + final UserHistoryDictionary dict = ref == null ? null : ref.get(); + if (dict != null) { + if (PROFILE_SAVE_RESTORE) { + Log.w(TAG, "Use cached UserHistoryDictionary for " + locale); + } + return dict; + } + } + final UserHistoryDictionary dict = + new UserHistoryDictionary(context, locale, dictTypeId, sp); + sLangDictCache.put(locale, new SoftReference<UserHistoryDictionary>(dict)); + return dict; + } + + private UserHistoryDictionary(final Context context, final String locale, final int dicTypeId, SharedPreferences sp) { super(context, dicTypeId); mLocale = locale; @@ -123,12 +146,13 @@ public class UserHistoryDictionary extends ExpandableDictionary { @Override public void close() { flushPendingWrites(); - SettingsValues.setLastUserHistoryWriteTime(mPrefs, mLocale); // Don't close the database as locale changes will require it to be reopened anyway // Also, the database is written to somewhat frequently, so it needs to be kept alive // throughout the life of the process. // mOpenHelper.close(); - super.close(); + // Ignore close because we cache UserHistoryDictionary for each language. See getInstance() + // above. + // super.close(); } /** @@ -160,7 +184,7 @@ public class UserHistoryDictionary extends ExpandableDictionary { } else { freq = super.setBigramAndGetFrequency(word1, word2, new ForgettingCurveParams(isValid)); } - synchronized (mPendingWritesLock) { + synchronized (mBigramList) { mBigramList.addBigram(word1, word2); } @@ -168,7 +192,7 @@ public class UserHistoryDictionary extends ExpandableDictionary { } public boolean cancelAddingUserHistory(String word1, String word2) { - synchronized (mPendingWritesLock) { + synchronized (mBigramList) { if (mBigramList.removeBigram(word1, word2)) { return super.removeBigram(word1, word2); } @@ -180,19 +204,17 @@ public class UserHistoryDictionary extends ExpandableDictionary { * Schedules a background thread to write any pending words to the database. */ private void flushPendingWrites() { - synchronized (mPendingWritesLock) { + synchronized (mBigramList) { // Nothing pending? Return if (mBigramList.isEmpty()) return; // Create a background thread to write the pending entries - new UpdateDbTask(sOpenHelper, mBigramList, mLocale, this).execute(); - // Create a new map for writing new entries into while the old one is written to db - mBigramList = new UserHistoryDictionaryBigramList(); + new UpdateDbTask(sOpenHelper, mBigramList, mLocale, this, mPrefs).execute(); } } /** Used for testing purpose **/ void waitUntilUpdateDBDone() { - synchronized (mPendingWritesLock) { + synchronized (mBigramList) { while (sUpdatingDB) { try { Thread.sleep(100); @@ -205,40 +227,46 @@ public class UserHistoryDictionary extends ExpandableDictionary { @Override public void loadDictionaryAsync() { - final long last = SettingsValues.getLastUserHistoryWriteTime(mPrefs, mLocale); - final long now = System.currentTimeMillis(); - // Load the words that correspond to the current input locale - final Cursor cursor = query(MAIN_COLUMN_LOCALE + "=?", new String[] { mLocale }); - if (null == cursor) return; - try { - if (cursor.moveToFirst()) { - final int word1Index = cursor.getColumnIndex(MAIN_COLUMN_WORD1); - final int word2Index = cursor.getColumnIndex(MAIN_COLUMN_WORD2); - final int frequencyIndex = cursor.getColumnIndex(FREQ_COLUMN_FREQUENCY); - while (!cursor.isAfterLast()) { - final String word1 = cursor.getString(word1Index); - final String word2 = cursor.getString(word2Index); - final int frequency = cursor.getInt(frequencyIndex); - if (DBG_SAVE_RESTORE) { - Log.d(TAG, "--- Load user history: " + word1 + ", " + word2); - } - // Safeguard against adding really long words. Stack may overflow due - // to recursive lookup - if (null == word1) { - super.addWord(word2, null /* shortcut */, frequency); - } else if (word1.length() < BinaryDictionary.MAX_WORD_LENGTH - && word2.length() < BinaryDictionary.MAX_WORD_LENGTH) { - super.setBigramAndGetFrequency( - word1, word2, new ForgettingCurveParams(frequency, now, last)); - } - synchronized(mPendingWritesLock) { - mBigramList.addBigram(word1, word2); + synchronized(mBigramList) { + final long last = SettingsValues.getLastUserHistoryWriteTime(mPrefs, mLocale); + final long now = System.currentTimeMillis(); + // Load the words that correspond to the current input locale + final Cursor cursor = query(MAIN_COLUMN_LOCALE + "=?", new String[] { mLocale }); + if (null == cursor) return; + try { + if (cursor.moveToFirst()) { + final int word1Index = cursor.getColumnIndex(MAIN_COLUMN_WORD1); + final int word2Index = cursor.getColumnIndex(MAIN_COLUMN_WORD2); + final int fcIndex = cursor.getColumnIndex(COLUMN_FORGETTING_CURVE_VALUE); + while (!cursor.isAfterLast()) { + final String word1 = cursor.getString(word1Index); + final String word2 = cursor.getString(word2Index); + final int fc = cursor.getInt(fcIndex); + if (DBG_SAVE_RESTORE) { + Log.d(TAG, "--- Load user history: " + word1 + ", " + word2 + "," + + mLocale + "," + this); + } + // Safeguard against adding really long words. Stack may overflow due + // to recursive lookup + if (null == word1) { + super.addWord(word2, null /* shortcut */, fc); + } else if (word1.length() < BinaryDictionary.MAX_WORD_LENGTH + && word2.length() < BinaryDictionary.MAX_WORD_LENGTH) { + super.setBigramAndGetFrequency( + word1, word2, new ForgettingCurveParams(fc, now, last)); + } + mBigramList.addBigram(word1, word2, (byte)fc); + cursor.moveToNext(); } - cursor.moveToNext(); + } + } finally { + cursor.close(); + if (PROFILE_SAVE_RESTORE) { + final long diff = System.currentTimeMillis() - now; + Log.w(TAG, "PROF: Load User HistoryDictionary: " + + mLocale + ", " + diff + "ms."); } } - } finally { - cursor.close(); } } @@ -259,7 +287,8 @@ public class UserHistoryDictionary extends ExpandableDictionary { try { SQLiteDatabase db = sOpenHelper.getReadableDatabase(); Cursor c = qb.query(db, - new String[] { MAIN_COLUMN_WORD1, MAIN_COLUMN_WORD2, FREQ_COLUMN_FREQUENCY }, + new String[] { + MAIN_COLUMN_WORD1, MAIN_COLUMN_WORD2, COLUMN_FORGETTING_CURVE_VALUE }, selection, selectionArgs, null, null, null); return c; } catch (android.database.sqlite.SQLiteCantOpenDatabaseException e) { @@ -290,7 +319,7 @@ public class UserHistoryDictionary extends ExpandableDictionary { db.execSQL("CREATE TABLE " + FREQ_TABLE_NAME + " (" + FREQ_COLUMN_ID + " INTEGER PRIMARY KEY," + FREQ_COLUMN_PAIR_ID + " INTEGER," - + FREQ_COLUMN_FREQUENCY + " INTEGER," + + COLUMN_FORGETTING_CURVE_VALUE + " INTEGER," + "FOREIGN KEY(" + FREQ_COLUMN_PAIR_ID + ") REFERENCES " + MAIN_TABLE_NAME + "(" + MAIN_COLUMN_ID + ")" + " ON DELETE CASCADE" + ");"); @@ -315,14 +344,16 @@ public class UserHistoryDictionary extends ExpandableDictionary { private final DatabaseHelper mDbHelper; private final String mLocale; private final UserHistoryDictionary mUserHistoryDictionary; + private final SharedPreferences mPrefs; public UpdateDbTask( DatabaseHelper openHelper, UserHistoryDictionaryBigramList pendingWrites, - String locale, UserHistoryDictionary dict) { + String locale, UserHistoryDictionary dict, SharedPreferences prefs) { mBigramList = pendingWrites; mLocale = locale; mDbHelper = openHelper; mUserHistoryDictionary = dict; + mPrefs = prefs; } /** Prune any old data if the database is getting too big. */ @@ -361,67 +392,65 @@ public class UserHistoryDictionary extends ExpandableDictionary { @Override protected Void doInBackground(Void... v) { - SQLiteDatabase db = null; - try { - db = mDbHelper.getWritableDatabase(); - } catch (android.database.sqlite.SQLiteCantOpenDatabaseException e) { - // If we can't open the db, don't do anything. Exit through the next test - // for non-nullity of the db variable. - } - if (null == db) { - // Not much we can do. Just exit. - sUpdatingDB = false; - return null; - } - db.execSQL("PRAGMA foreign_keys = ON;"); - final boolean addLevel0Bigram = mBigramList.size() <= sMaxHistoryBigrams; - - // Write all the entries to the db - for (String word1 : mBigramList.keySet()) { - for (String word2 : mBigramList.getBigrams(word1)) { - // TODO: this process of making a text search for each pair each time - // is terribly inefficient. Optimize this. - // find pair id - Cursor c = null; - try { - if (null != word1) { - c = db.query(MAIN_TABLE_NAME, new String[] { MAIN_COLUMN_ID }, - MAIN_COLUMN_WORD1 + "=? AND " + MAIN_COLUMN_WORD2 + "=? AND " - + MAIN_COLUMN_LOCALE + "=?", - new String[] { word1, word2, mLocale }, null, null, - null); - } else { - c = db.query(MAIN_TABLE_NAME, new String[] { MAIN_COLUMN_ID }, - MAIN_COLUMN_WORD1 + " IS NULL AND " + MAIN_COLUMN_WORD2 - + "=? AND " + MAIN_COLUMN_LOCALE + "=?", - new String[] { word2, mLocale }, null, null, null); - } - - final int pairId; - if (c.moveToFirst()) { - // existing pair - pairId = c.getInt(c.getColumnIndex(MAIN_COLUMN_ID)); - db.delete(FREQ_TABLE_NAME, FREQ_COLUMN_PAIR_ID + "=?", - new String[] { Integer.toString(pairId) }); - } else { - // new pair - Long pairIdLong = db.insert(MAIN_TABLE_NAME, null, - getContentValues(word1, word2, mLocale)); - pairId = pairIdLong.intValue(); + synchronized(mBigramList) { + final long now = PROFILE_SAVE_RESTORE ? System.currentTimeMillis() : 0; + int profTotal = 0; + int profInsert = 0; + int profDelete = 0; + SQLiteDatabase db = null; + try { + db = mDbHelper.getWritableDatabase(); + } catch (android.database.sqlite.SQLiteCantOpenDatabaseException e) { + // If we can't open the db, don't do anything. Exit through the next test + // for non-nullity of the db variable. + } + if (null == db) { + // Not much we can do. Just exit. + sUpdatingDB = false; + return null; + } + db.execSQL("PRAGMA foreign_keys = ON;"); + final boolean addLevel0Bigram = mBigramList.size() <= sMaxHistoryBigrams; + + // Write all the entries to the db + for (String word1 : mBigramList.keySet()) { + final HashMap<String, Byte> word1Bigrams = mBigramList.getBigrams(word1); + for (String word2 : word1Bigrams.keySet()) { + if (PROFILE_SAVE_RESTORE) { + ++profTotal; } - // insert new frequency - final int freq; - if (word1 == null) { + // Get new frequency. Do not insert unigrams/bigrams which freq is "-1". + final int freq; // -1, or 0~255 + if (word1 == null) { // unigram freq = FREQUENCY_FOR_TYPED; - } else { + final byte prevFc = word1Bigrams.get(word2); + if (prevFc == FREQUENCY_FOR_TYPED) { + // No need to update since we found no changes for this entry. + // Just skip to the next entry. + if (DBG_SAVE_RESTORE) { + Log.d(TAG, "Skip update user history: " + word1 + "," + word2 + + "," + prevFc); + } + continue; + } + } else { // bigram final NextWord nw = mUserHistoryDictionary.getBigramWord(word1, word2); if (nw != null) { final ForgettingCurveParams fcp = nw.getFcParams(); - final int tempFreq = fcp.getFc(); + final byte prevFc = word1Bigrams.get(word2); + final byte fc = (byte)fcp.getFc(); final boolean isValid = fcp.isValid(); - if (UserHistoryForgettingCurveUtils.needsToSave( - (byte)tempFreq, isValid, addLevel0Bigram)) { - freq = tempFreq; + if (prevFc > 0 && prevFc == fc) { + // No need to update since we found no changes for this entry. + // Just skip to the next entry. + if (DBG_SAVE_RESTORE) { + Log.d(TAG, "Skip update user history: " + word1 + "," + + word2 + "," + prevFc); + } + continue; + } else if (UserHistoryForgettingCurveUtils. + needsToSave(fc, isValid, addLevel0Bigram)) { + freq = fc; } else { freq = -1; } @@ -429,25 +458,74 @@ public class UserHistoryDictionary extends ExpandableDictionary { freq = -1; } } - if (freq > 0) { - if (DBG_SAVE_RESTORE) { - Log.d(TAG, "--- Save user history: " + word1 + ", " + word2); + // TODO: this process of making a text search for each pair each time + // is terribly inefficient. Optimize this. + // Find pair id + Cursor c = null; + try { + if (null != word1) { + c = db.query(MAIN_TABLE_NAME, new String[] { MAIN_COLUMN_ID }, + MAIN_COLUMN_WORD1 + "=? AND " + MAIN_COLUMN_WORD2 + "=? AND " + + MAIN_COLUMN_LOCALE + "=?", + new String[] { word1, word2, mLocale }, null, null, + null); + } else { + c = db.query(MAIN_TABLE_NAME, new String[] { MAIN_COLUMN_ID }, + MAIN_COLUMN_WORD1 + " IS NULL AND " + MAIN_COLUMN_WORD2 + + "=? AND " + MAIN_COLUMN_LOCALE + "=?", + new String[] { word2, mLocale }, null, null, null); + } + + final int pairId; + if (c.moveToFirst()) { + if (PROFILE_SAVE_RESTORE) { + ++profDelete; + } + // Delete existing pair + pairId = c.getInt(c.getColumnIndex(MAIN_COLUMN_ID)); + db.delete(FREQ_TABLE_NAME, FREQ_COLUMN_PAIR_ID + "=?", + new String[] { Integer.toString(pairId) }); + } else { + // Create new pair + Long pairIdLong = db.insert(MAIN_TABLE_NAME, null, + getContentValues(word1, word2, mLocale)); + pairId = pairIdLong.intValue(); + } + if (freq > 0) { + if (PROFILE_SAVE_RESTORE) { + ++profInsert; + } + if (DBG_SAVE_RESTORE) { + Log.d(TAG, "--- Save user history: " + word1 + ", " + word2 + + mLocale + "," + this); + } + // Insert new frequency + db.insert(FREQ_TABLE_NAME, null, + getFrequencyContentValues(pairId, freq)); + // Update an existing bigram entry in mBigramList too in order to + // synchronize the SQL DB and mBigramList. + mBigramList.updateBigram(word1, word2, (byte)freq); + } + } finally { + if (c != null) { + c.close(); } - db.insert(FREQ_TABLE_NAME, null, - getFrequencyContentValues(pairId, freq)); - } - } finally { - if (c != null) { - c.close(); } } } - } - checkPruneData(db); - sUpdatingDB = false; - - return null; + checkPruneData(db); + // Save the timestamp after we finish writing the SQL DB. + SettingsValues.setLastUserHistoryWriteTime(mPrefs, mLocale); + sUpdatingDB = false; + if (PROFILE_SAVE_RESTORE) { + final long diff = System.currentTimeMillis() - now; + Log.w(TAG, "PROF: Write User HistoryDictionary: " + mLocale + ", "+ diff + + "ms. Total: " + profTotal + ". Insert: " + profInsert + ". Delete: " + + profDelete); + } + return null; + } // synchronized } private static ContentValues getContentValues(String word1, String word2, String locale) { @@ -461,7 +539,7 @@ public class UserHistoryDictionary extends ExpandableDictionary { private static ContentValues getFrequencyContentValues(int pairId, int frequency) { ContentValues values = new ContentValues(2); values.put(FREQ_COLUMN_PAIR_ID, pairId); - values.put(FREQ_COLUMN_FREQUENCY, frequency); + values.put(COLUMN_FORGETTING_CURVE_VALUE, frequency); return values; } } diff --git a/java/src/com/android/inputmethod/latin/UserHistoryDictionaryBigramList.java b/java/src/com/android/inputmethod/latin/UserHistoryDictionaryBigramList.java index 409f921ff..28847745e 100644 --- a/java/src/com/android/inputmethod/latin/UserHistoryDictionaryBigramList.java +++ b/java/src/com/android/inputmethod/latin/UserHistoryDictionaryBigramList.java @@ -19,7 +19,6 @@ package com.android.inputmethod.latin; import android.util.Log; import java.util.HashMap; -import java.util.HashSet; import java.util.Set; /** @@ -28,10 +27,11 @@ import java.util.Set; * bigrams when we write to the SQL DB. */ public class UserHistoryDictionaryBigramList { + public static final byte FORGETTING_CURVE_INITIAL_VALUE = 0; private static final String TAG = UserHistoryDictionaryBigramList.class.getSimpleName(); - private static final HashSet<String> EMPTY_STRING_SET = new HashSet<String>(); - private final HashMap<String, HashSet<String>> mBigramMap = - new HashMap<String, HashSet<String>>(); + private static final HashMap<String, Byte> EMPTY_BIGRAM_MAP = new HashMap<String, Byte>(); + private final HashMap<String, HashMap<String, Byte>> mBigramMap = + new HashMap<String, HashMap<String, Byte>>(); private int mSize = 0; public void evictAll() { @@ -39,21 +39,50 @@ public class UserHistoryDictionaryBigramList { mBigramMap.clear(); } + /** + * Called when the user typed a word. + */ public void addBigram(String word1, String word2) { + addBigram(word1, word2, FORGETTING_CURVE_INITIAL_VALUE); + } + + /** + * Called when loaded from the SQL DB. + */ + public void addBigram(String word1, String word2, byte fcValue) { if (UserHistoryDictionary.DBG_SAVE_RESTORE) { - Log.d(TAG, "--- add bigram: " + word1 + ", " + word2); + Log.d(TAG, "--- add bigram: " + word1 + ", " + word2 + ", " + fcValue); } - final HashSet<String> set; + final HashMap<String, Byte> map; if (mBigramMap.containsKey(word1)) { - set = mBigramMap.get(word1); + map = mBigramMap.get(word1); } else { - set = new HashSet<String>(); - mBigramMap.put(word1, set); + map = new HashMap<String, Byte>(); + mBigramMap.put(word1, map); } - if (!set.contains(word2)) { + if (!map.containsKey(word2)) { ++mSize; - set.add(word2); + map.put(word2, fcValue); + } + } + + /** + * Called when inserted to the SQL DB. + */ + public void updateBigram(String word1, String word2, byte fcValue) { + if (UserHistoryDictionary.DBG_SAVE_RESTORE) { + Log.d(TAG, "--- update bigram: " + word1 + ", " + word2 + ", " + fcValue); + } + final HashMap<String, Byte> map; + if (mBigramMap.containsKey(word1)) { + map = mBigramMap.get(word1); + } else { + return; + } + if (!map.containsKey(word2)) { + return; } + map.put(word2, fcValue); } public int size() { @@ -68,20 +97,20 @@ public class UserHistoryDictionaryBigramList { return mBigramMap.keySet(); } - public HashSet<String> getBigrams(String word1) { + public HashMap<String, Byte> getBigrams(String word1) { if (!mBigramMap.containsKey(word1)) { - return EMPTY_STRING_SET; + return EMPTY_BIGRAM_MAP; } else { return mBigramMap.get(word1); } } public boolean removeBigram(String word1, String word2) { - final HashSet<String> set = getBigrams(word1); + final HashMap<String, Byte> set = getBigrams(word1); if (set.isEmpty()) { return false; } - if (set.contains(word2)) { + if (set.containsKey(word2)) { set.remove(word2); --mSize; return true; diff --git a/java/src/com/android/inputmethod/latin/spellcheck/AndroidSpellCheckerService.java b/java/src/com/android/inputmethod/latin/spellcheck/AndroidSpellCheckerService.java index 0e3bf8011..802322ccd 100644 --- a/java/src/com/android/inputmethod/latin/spellcheck/AndroidSpellCheckerService.java +++ b/java/src/com/android/inputmethod/latin/spellcheck/AndroidSpellCheckerService.java @@ -99,11 +99,13 @@ public class AndroidSpellCheckerService extends SpellCheckerService // List of the supported languages and their associated script. We won't check // words written in another script than the selected script, because we know we // don't have those in our dictionary so we will underline everything and we - // will never have any suggestions, so it makes no sense checking them. + // will never have any suggestions, so it makes no sense checking them, and this + // is done in {@link #shouldFilterOut}. Also, the script is used to choose which + // proximity to pass to the dictionary descent algorithm. + // IMPORTANT: this only contains languages - do not write countries in there. + // Only the language is searched from the map. mLanguageToScript = new TreeMap<String, Integer>(); mLanguageToScript.put("en", SCRIPT_LATIN); - mLanguageToScript.put("en_US", SCRIPT_LATIN); - mLanguageToScript.put("en_GB", SCRIPT_LATIN); mLanguageToScript.put("fr", SCRIPT_LATIN); mLanguageToScript.put("de", SCRIPT_LATIN); mLanguageToScript.put("nl", SCRIPT_LATIN); @@ -111,7 +113,7 @@ public class AndroidSpellCheckerService extends SpellCheckerService mLanguageToScript.put("es", SCRIPT_LATIN); mLanguageToScript.put("it", SCRIPT_LATIN); mLanguageToScript.put("hr", SCRIPT_LATIN); - mLanguageToScript.put("pt_BR", SCRIPT_LATIN); + mLanguageToScript.put("pt", SCRIPT_LATIN); mLanguageToScript.put("ru", SCRIPT_CYRILLIC); // TODO: Make a persian proximity, and activate the Farsi subtype. // mLanguageToScript.put("fa", SCRIPT_PERSIAN); @@ -152,7 +154,13 @@ public class AndroidSpellCheckerService extends SpellCheckerService private void startUsingContactsDictionaryLocked() { if (null == mContactsDictionary) { - mContactsDictionary = new SynchronouslyLoadedContactsDictionary(this); + if (LatinIME.USE_BINARY_CONTACTS_DICTIONARY) { + // TODO: use the right locale for each session + mContactsDictionary = + new SynchronouslyLoadedContactsBinaryDictionary(this, Locale.getDefault()); + } else { + mContactsDictionary = new SynchronouslyLoadedContactsDictionary(this); + } } final Iterator<WeakReference<DictionaryCollection>> iterator = mDictionaryCollectionsList.iterator(); @@ -430,7 +438,11 @@ public class AndroidSpellCheckerService extends SpellCheckerService // TODO: revert to the concrete type when USE_BINARY_CONTACTS_DICTIONARY is no // longer needed if (LatinIME.USE_BINARY_CONTACTS_DICTIONARY) { - mContactsDictionary = new SynchronouslyLoadedContactsBinaryDictionary(this); + // TODO: use the right locale. We can't do it right now because the + // spell checker is reusing the contacts dictionary across sessions + // without regard for their locale, so we need to fix that first. + mContactsDictionary = new SynchronouslyLoadedContactsBinaryDictionary(this, + Locale.getDefault()); } else { mContactsDictionary = new SynchronouslyLoadedContactsDictionary(this); } @@ -484,20 +496,32 @@ public class AndroidSpellCheckerService extends SpellCheckerService } private static class SuggestionsCache { + private static final char CHAR_DELIMITER = '\uFFFC'; private static final int MAX_CACHE_SIZE = 50; - // TODO: support bigram private final LruCache<String, SuggestionsParams> mUnigramSuggestionsInfoCache = new LruCache<String, SuggestionsParams>(MAX_CACHE_SIZE); - public SuggestionsParams getSuggestionsFromCache(String query) { - return mUnigramSuggestionsInfoCache.get(query); + // TODO: Support n-gram input + private static String generateKey(String query, String prevWord) { + if (TextUtils.isEmpty(query) || TextUtils.isEmpty(prevWord)) { + return query; + } + return query + CHAR_DELIMITER + prevWord; + } + + // TODO: Support n-gram input + public SuggestionsParams getSuggestionsFromCache(String query, String prevWord) { + return mUnigramSuggestionsInfoCache.get(generateKey(query, prevWord)); } - public void putSuggestionsToCache(String query, String[] suggestions, int flags) { + // TODO: Support n-gram input + public void putSuggestionsToCache( + String query, String prevWord, String[] suggestions, int flags) { if (suggestions == null || TextUtils.isEmpty(query)) { return; } - mUnigramSuggestionsInfoCache.put(query, new SuggestionsParams(suggestions, flags)); + mUnigramSuggestionsInfoCache.put( + generateKey(query, prevWord), new SuggestionsParams(suggestions, flags)); } } @@ -592,6 +616,7 @@ public class AndroidSpellCheckerService extends SpellCheckerService final ArrayList<Integer> additionalLengths = new ArrayList<Integer>(); final ArrayList<SuggestionsInfo> additionalSuggestionsInfos = new ArrayList<SuggestionsInfo>(); + String currentWord = null; for (int i = 0; i < N; ++i) { final SuggestionsInfo si = ssi.getSuggestionsInfoAt(i); final int flags = si.getSuggestionsAttributes(); @@ -601,6 +626,8 @@ public class AndroidSpellCheckerService extends SpellCheckerService final int offset = ssi.getOffsetAt(i); final int length = ssi.getLengthAt(i); final String subText = typedText.substring(offset, offset + length); + final String prevWord = currentWord; + currentWord = subText; if (!subText.contains(SINGLE_QUOTE)) { continue; } @@ -614,7 +641,8 @@ public class AndroidSpellCheckerService extends SpellCheckerService if (TextUtils.isEmpty(splitText)) { continue; } - if (mSuggestionsCache.getSuggestionsFromCache(splitText) == null) { + if (mSuggestionsCache.getSuggestionsFromCache( + splitText, prevWord) == null) { continue; } final int newLength = splitText.length(); @@ -710,7 +738,7 @@ public class AndroidSpellCheckerService extends SpellCheckerService try { final String inText = textInfo.getText(); final SuggestionsParams cachedSuggestionsParams = - mSuggestionsCache.getSuggestionsFromCache(inText); + mSuggestionsCache.getSuggestionsFromCache(inText, prevWord); if (cachedSuggestionsParams != null) { if (DBG) { Log.d(TAG, "Cache hit: " + inText + ", " + cachedSuggestionsParams.mFlags); @@ -802,7 +830,7 @@ public class AndroidSpellCheckerService extends SpellCheckerService .getValueOf_RESULT_ATTR_HAS_RECOMMENDED_SUGGESTIONS() : 0); final SuggestionsInfo retval = new SuggestionsInfo(flags, result.mSuggestions); - mSuggestionsCache.putSuggestionsToCache(text, result.mSuggestions, flags); + mSuggestionsCache.putSuggestionsToCache(text, prevWord, result.mSuggestions, flags); return retval; } catch (RuntimeException e) { // Don't kill the keyboard if there is a bug in the spell checker |