diff --git a/src/com/android/inputmethod/latin/AutoDictionary.java b/src/com/android/inputmethod/latin/AutoDictionary.java new file mode 100644 index 000000000..3d76dc301 --- /dev/null +++ b/src/com/android/inputmethod/latin/AutoDictionary.java @@ -0,0 +1,217 @@ +/* + * Copyright (C) 2010 Google Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not + * use this file except in compliance with the License. You may obtain a copy of + * the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations under + * the License. + */ + +package com.android.inputmethod.latin; + +import java.util.HashMap; + +import android.content.ContentValues; +import android.content.Context; +import android.database.Cursor; +import android.database.sqlite.SQLiteDatabase; +import android.database.sqlite.SQLiteOpenHelper; +import android.database.sqlite.SQLiteQueryBuilder; +import android.provider.BaseColumns; +import android.provider.UserDictionary.Words; +import android.util.Log; + +/** + * Stores new words temporarily until they are promoted to the user dictionary + * for longevity. Words in the auto dictionary are used to determine if it's ok + * to accept a word that's not in the main or user dictionary. Using a new word + * repeatedly will promote it to the user dictionary. + */ +public class AutoDictionary extends ExpandableDictionary { + // Weight added to a user picking a new word from the suggestion strip + static final int FREQUENCY_FOR_PICKED = 3; + // Weight added to a user typing a new word that doesn't get corrected (or is reverted) + static final int FREQUENCY_FOR_TYPED = 1; + // A word that is frequently typed and gets promoted to the user dictionary, uses this + // frequency. + static final int FREQUENCY_FOR_AUTO_ADD = 250; + // If the user touches a typed word 2 times or more, it will become valid. + private static final int VALIDITY_THRESHOLD = 2 * FREQUENCY_FOR_PICKED; + // If the user touches a typed word 4 times or more, it will be added to the user dict. + private static final int PROMOTION_THRESHOLD = 4 * FREQUENCY_FOR_PICKED; + + private LatinIME mIme; + // Locale for which this auto dictionary is storing words + private String mLocale; + + private static final String DATABASE_NAME = "auto_dict.db"; + private static final int DATABASE_VERSION = 1; + + // These are the columns in the dictionary + // TODO: Consume less space by using a unique id for locale instead of the whole + // 2-5 character string. + private static final String COLUMN_ID = BaseColumns._ID; + private static final String COLUMN_WORD = "word"; + private static final String COLUMN_FREQUENCY = "freq"; + private static final String COLUMN_LOCALE = "locale"; + + /** Sort by descending order of frequency. */ + public static final String DEFAULT_SORT_ORDER = COLUMN_FREQUENCY + " DESC"; + + /** Name of the words table in the auto_dict.db */ + private static final String AUTODICT_TABLE_NAME = "words"; + + private static HashMap sDictProjectionMap; + + static { + sDictProjectionMap = new HashMap(); + sDictProjectionMap.put(COLUMN_ID, COLUMN_ID); + sDictProjectionMap.put(COLUMN_WORD, COLUMN_WORD); + sDictProjectionMap.put(COLUMN_FREQUENCY, COLUMN_FREQUENCY); + sDictProjectionMap.put(COLUMN_LOCALE, COLUMN_LOCALE); + } + + private DatabaseHelper mOpenHelper; + + public AutoDictionary(Context context, LatinIME ime, String locale) { + super(context); + mIme = ime; + mLocale = locale; + mOpenHelper = new DatabaseHelper(getContext()); + if (mLocale != null && mLocale.length() > 1) { + loadDictionary(); + } + } + + @Override + public boolean isValidWord(CharSequence word) { + final int frequency = getWordFrequency(word); + return frequency >= VALIDITY_THRESHOLD; + } + + public void close() { + mOpenHelper.close(); + } + + private void loadDictionary() { + // Load the words that correspond to the current input locale + Cursor cursor = query(COLUMN_LOCALE + "=?", new String[] { mLocale }); + if (cursor.moveToFirst()) { + int wordIndex = cursor.getColumnIndex(COLUMN_WORD); + int frequencyIndex = cursor.getColumnIndex(COLUMN_FREQUENCY); + while (!cursor.isAfterLast()) { + String word = cursor.getString(wordIndex); + int frequency = cursor.getInt(frequencyIndex); + // Safeguard against adding really long words. Stack may overflow due + // to recursive lookup + if (word.length() < getMaxWordLength()) { + super.addWord(word, frequency); + } + cursor.moveToNext(); + } + } + cursor.close(); + } + + @Override + public void addWord(String word, int addFrequency) { + final int length = word.length(); + // Don't add very short or very long words. + if (length < 2 || length > getMaxWordLength()) return; + if (mIme.getCurrentWord().isAutoCapitalized()) { + // Remove caps before adding + word = Character.toLowerCase(word.charAt(0)) + word.substring(1); + } + int freq = getWordFrequency(word); + freq = freq < 0 ? addFrequency : freq + addFrequency; + super.addWord(word, freq); + if (freq >= PROMOTION_THRESHOLD) { + mIme.promoteToUserDictionary(word, FREQUENCY_FOR_AUTO_ADD); + // Delete the word (for input locale) from the auto dictionary db, as it + // is now in the user dictionary provider. + delete(COLUMN_WORD + "=? AND " + COLUMN_LOCALE + "=?", + new String[] { word, mLocale }); + } else { + update(word, freq, mLocale); + } + } + + /** + * This class helps open, create, and upgrade the database file. + */ + private static class DatabaseHelper extends SQLiteOpenHelper { + + DatabaseHelper(Context context) { + super(context, DATABASE_NAME, null, DATABASE_VERSION); + } + + @Override + public void onCreate(SQLiteDatabase db) { + db.execSQL("CREATE TABLE " + AUTODICT_TABLE_NAME + " (" + + COLUMN_ID + " INTEGER PRIMARY KEY," + + COLUMN_WORD + " TEXT," + + COLUMN_FREQUENCY + " INTEGER," + + COLUMN_LOCALE + " TEXT" + + ");"); + } + + @Override + public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) { + Log.w("AutoDictionary", "Upgrading database from version " + oldVersion + " to " + + newVersion + ", which will destroy all old data"); + db.execSQL("DROP TABLE IF EXISTS " + AUTODICT_TABLE_NAME); + onCreate(db); + } + } + + private Cursor query(String selection, String[] selectionArgs) { + SQLiteQueryBuilder qb = new SQLiteQueryBuilder(); + qb.setTables(AUTODICT_TABLE_NAME); + qb.setProjectionMap(sDictProjectionMap); + + // Get the database and run the query + SQLiteDatabase db = mOpenHelper.getReadableDatabase(); + Cursor c = qb.query(db, null, selection, selectionArgs, null, null, + DEFAULT_SORT_ORDER); + return c; + } + + private boolean insert(ContentValues values) { + SQLiteDatabase db = mOpenHelper.getWritableDatabase(); + long rowId = db.insert(AUTODICT_TABLE_NAME, Words.WORD, values); + if (rowId > 0) { + return true; + } + return false; + } + + private int delete(String where, String[] whereArgs) { + SQLiteDatabase db = mOpenHelper.getWritableDatabase(); + int count = db.delete(AUTODICT_TABLE_NAME, where, whereArgs); + return count; + } + + private int update(String word, int frequency, String locale) { + SQLiteDatabase db = mOpenHelper.getWritableDatabase(); + long count = db.delete(AUTODICT_TABLE_NAME, COLUMN_WORD + "=? AND " + COLUMN_LOCALE + "=?", + new String[] { word, locale }); + count = db.insert(AUTODICT_TABLE_NAME, null, + getContentValues(word, frequency, locale)); + return (int) count; + } + + private ContentValues getContentValues(String word, int frequency, String locale) { + ContentValues values = new ContentValues(4); + values.put(COLUMN_WORD, word); + values.put(COLUMN_FREQUENCY, frequency); + values.put(COLUMN_LOCALE, locale); + return values; + } +} diff --git a/src/com/android/inputmethod/latin/InputLanguageSelection.java b/src/com/android/inputmethod/latin/InputLanguageSelection.java index 2c5fec6e8..73298e33c 100644 --- a/src/com/android/inputmethod/latin/InputLanguageSelection.java +++ b/src/com/android/inputmethod/latin/InputLanguageSelection.java @@ -19,7 +19,6 @@ package com.android.inputmethod.latin; import java.text.Collator; import java.util.ArrayList; import java.util.Arrays; -import java.util.List; import java.util.Locale; import android.content.SharedPreferences; @@ -29,7 +28,7 @@ import android.preference.CheckBoxPreference; import android.preference.PreferenceActivity; import android.preference.PreferenceGroup; import android.preference.PreferenceManager; -import android.util.Log; +import android.text.TextUtils; public class InputLanguageSelection extends PreferenceActivity { @@ -91,7 +90,9 @@ public class InputLanguageSelection extends PreferenceActivity { } private String get5Code(Locale locale) { - return locale.getLanguage() + "_" + locale.getCountry(); + String country = locale.getCountry(); + return locale.getLanguage() + + (TextUtils.isEmpty(country) ? "" : "_" + country); } @Override diff --git a/src/com/android/inputmethod/latin/KeyboardSwitcher.java b/src/com/android/inputmethod/latin/KeyboardSwitcher.java index 529edeb81..38979edde 100644 --- a/src/com/android/inputmethod/latin/KeyboardSwitcher.java +++ b/src/com/android/inputmethod/latin/KeyboardSwitcher.java @@ -180,6 +180,7 @@ public class KeyboardSwitcher { void setKeyboardMode(int mode, int imeOptions, boolean enableVoice, boolean isSymbols) { + if (mInputView == null) return; mMode = mode; mImeOptions = imeOptions; mHasVoice = enableVoice; diff --git a/src/com/android/inputmethod/latin/LanguageSwitcher.java b/src/com/android/inputmethod/latin/LanguageSwitcher.java index 68d0d15cc..12045125f 100644 --- a/src/com/android/inputmethod/latin/LanguageSwitcher.java +++ b/src/com/android/inputmethod/latin/LanguageSwitcher.java @@ -21,6 +21,7 @@ import java.util.Locale; import android.content.SharedPreferences; import android.content.SharedPreferences.Editor; import android.preference.PreferenceManager; +import android.text.TextUtils; /** * Keeps track of list of selected input languages and the current @@ -88,8 +89,9 @@ public class LanguageSwitcher { private void loadDefaults() { mDefaultInputLocale = mIme.getResources().getConfiguration().locale; - mDefaultInputLanguage = mDefaultInputLocale.getLanguage() + "_" - + mDefaultInputLocale.getCountry(); + String country = mDefaultInputLocale.getCountry(); + mDefaultInputLanguage = mDefaultInputLocale.getLanguage() + + (TextUtils.isEmpty(country) ? "" : "_" + country); } private void constructLocales() { diff --git a/src/com/android/inputmethod/latin/LatinIME.java b/src/com/android/inputmethod/latin/LatinIME.java index 5022a2cba..e08af1ad9 100644 --- a/src/com/android/inputmethod/latin/LatinIME.java +++ b/src/com/android/inputmethod/latin/LatinIME.java @@ -130,21 +130,10 @@ public class LatinIME extends InputMethodService // ignored, since it may in fact be two key presses in quick succession. private static final long MIN_MILLIS_AFTER_TYPING_BEFORE_SWIPE = 1000; - // If we detect a swipe gesture, and the user types N ms later, cancel the - // swipe since it was probably a false trigger. - private static final long MIN_MILLIS_AFTER_SWIPE_TO_WAIT_FOR_TYPING = 500; - // How many continuous deletes at which to start deleting at a higher speed. private static final int DELETE_ACCELERATE_AT = 20; // Key events coming any faster than this are long-presses. private static final int QUICK_PRESS = 200; - // Weight added to a user picking a new word from the suggestion strip - static final int FREQUENCY_FOR_PICKED = 3; - // Weight added to a user typing a new word that doesn't get corrected (or is reverted) - static final int FREQUENCY_FOR_TYPED = 1; - // A word that is frequently typed and get's promoted to the user dictionary, uses this - // frequency. - static final int FREQUENCY_FOR_AUTO_ADD = 250; static final int KEYCODE_ENTER = '\n'; static final int KEYCODE_SPACE = ' '; @@ -329,14 +318,14 @@ public class LatinIME extends InputMethodService mSuggest = new Suggest(this, R.raw.main); updateAutoTextEnabled(saveLocale); if (mUserDictionary != null) mUserDictionary.close(); - mUserDictionary = new UserDictionary(this); + mUserDictionary = new UserDictionary(this, mLocale); if (mContactsDictionary == null) { mContactsDictionary = new ContactsDictionary(this); } - // TODO: Save and restore the dictionary for the current input language. - if (mAutoDictionary == null) { - mAutoDictionary = new AutoDictionary(this); + if (mAutoDictionary != null) { + mAutoDictionary.close(); } + mAutoDictionary = new AutoDictionary(this, this, mLocale); mSuggest.setUserDictionary(mUserDictionary); mSuggest.setContactsDictionary(mContactsDictionary); mSuggest.setAutoDictionary(mAutoDictionary); @@ -815,7 +804,7 @@ public class LatinIME extends InputMethodService } mCommittedLength = mComposing.length(); TextEntryState.acceptedTyped(mComposing); - checkAddToDictionary(mComposing, FREQUENCY_FOR_TYPED); + checkAddToDictionary(mComposing, AutoDictionary.FREQUENCY_FOR_TYPED); } updateSuggestions(); } @@ -1196,7 +1185,6 @@ public class LatinIME extends InputMethodService private boolean isPredictionOn() { boolean predictionOn = mPredictionOn; - //if (isFullscreenMode()) predictionOn &= mPredictionLandscape; return predictionOn; } @@ -1522,7 +1510,7 @@ public class LatinIME extends InputMethodService } } // Add the word to the auto dictionary if it's not a known word - checkAddToDictionary(suggestion, FREQUENCY_FOR_PICKED); + checkAddToDictionary(suggestion, AutoDictionary.FREQUENCY_FOR_PICKED); mPredicting = false; mCommittedLength = suggestion.length(); setNextSuggestions(); @@ -1683,11 +1671,6 @@ public class LatinIME extends InputMethodService && !mVoiceInput.isBlacklistedField(fieldContext); } - private boolean fieldIsRecommendedForVoice(FieldContext fieldContext) { - // TODO: Move this logic into the VoiceInput method. - return !mPasswordText && !mEmailText && mVoiceInput.isRecommendedField(fieldContext); - } - private boolean shouldShowVoiceButton(FieldContext fieldContext, EditorInfo attribute) { return ENABLE_VOICE_BUTTON && fieldCanDoVoice(fieldContext) && !(attribute != null && attribute.privateImeOptions != null @@ -1718,21 +1701,6 @@ public class LatinIME extends InputMethodService > MIN_MILLIS_AFTER_TYPING_BEFORE_SWIPE; } - /* - * Only trigger a swipe action if the user hasn't typed X millis before - * now, and if they don't type Y millis after the swipe is detected. This - * delays the onset of the swipe action by Y millis. - */ - private void conservativelyTriggerSwipeAction(final Runnable action) { - if (userHasNotTypedRecently()) { - mSwipeTriggerTimeMillis = System.currentTimeMillis(); - mHandler.sendMessageDelayed( - mHandler.obtainMessage(MSG_START_LISTENING_AFTER_SWIPE), - MIN_MILLIS_AFTER_SWIPE_TO_WAIT_FOR_TYPING); - } - } - - private void playKeyClick(int primaryCode) { // if mAudioManager is null, we don't have the ringer state yet // mAudioManager will be set by updateRingerMode @@ -1796,6 +1764,10 @@ public class LatinIME extends InputMethodService mUserDictionary.addWord(word, frequency); } + WordComposer getCurrentWord() { + return mWord; + } + private void updateCorrectionMode() { mHasDictionary = mSuggest != null ? mSuggest.hasMainDictionary() : false; mAutoCorrectOn = (mAutoCorrectEnabled || mQuickFixes) @@ -1972,38 +1944,4 @@ public class LatinIME extends InputMethodService System.out.println("CPS = " + ((CPS_BUFFER_SIZE * 1000f) / total)); } - class AutoDictionary extends ExpandableDictionary { - // If the user touches a typed word 2 times or more, it will become valid. - private static final int VALIDITY_THRESHOLD = 2 * FREQUENCY_FOR_PICKED; - // If the user touches a typed word 5 times or more, it will be added to the user dict. - private static final int PROMOTION_THRESHOLD = 4 * FREQUENCY_FOR_PICKED; - - public AutoDictionary(Context context) { - super(context); - } - - @Override - public boolean isValidWord(CharSequence word) { - final int frequency = getWordFrequency(word); - return frequency >= VALIDITY_THRESHOLD; - } - - @Override - public void addWord(String word, int addFrequency) { - final int length = word.length(); - // Don't add very short or very long words. - if (length < 2 || length > getMaxWordLength()) return; - if (mWord.isAutoCapitalized()) { - // Remove caps before adding - word = Character.toLowerCase(word.charAt(0)) - + word.substring(1); - } - int freq = getWordFrequency(word); - freq = freq < 0 ? addFrequency : freq + addFrequency; - super.addWord(word, freq); - if (freq >= PROMOTION_THRESHOLD) { - LatinIME.this.promoteToUserDictionary(word, FREQUENCY_FOR_AUTO_ADD); - } - } - } } diff --git a/src/com/android/inputmethod/latin/UserDictionary.java b/src/com/android/inputmethod/latin/UserDictionary.java index edd82aaa3..4b98eacce 100644 --- a/src/com/android/inputmethod/latin/UserDictionary.java +++ b/src/com/android/inputmethod/latin/UserDictionary.java @@ -21,9 +21,11 @@ import java.util.List; import java.util.Locale; import android.content.ContentResolver; +import android.content.ContentValues; import android.content.Context; import android.database.ContentObserver; import android.database.Cursor; +import android.net.Uri; import android.provider.UserDictionary.Words; public class UserDictionary extends ExpandableDictionary { @@ -40,9 +42,11 @@ public class UserDictionary extends ExpandableDictionary { private ContentObserver mObserver; private boolean mRequiresReload; - - public UserDictionary(Context context) { + private String mLocale; + + public UserDictionary(Context context, String locale) { super(context); + mLocale = locale; // Perform a managed query. The Activity will handle closing and requerying the cursor // when needed. ContentResolver cres = context.getContentResolver(); @@ -67,7 +71,7 @@ public class UserDictionary extends ExpandableDictionary { private synchronized void loadDictionary() { Cursor cursor = getContext().getContentResolver() .query(Words.CONTENT_URI, PROJECTION, "(locale IS NULL) or (locale=?)", - new String[] { Locale.getDefault().toString() }, null); + new String[] { mLocale }, null); addWords(cursor); mRequiresReload = false; } @@ -88,7 +92,14 @@ public class UserDictionary extends ExpandableDictionary { super.addWord(word, frequency); - Words.addWord(getContext(), word, frequency, Words.LOCALE_TYPE_CURRENT); + // Update the user dictionary provider + ContentValues values = new ContentValues(5); + values.put(Words.WORD, word); + values.put(Words.FREQUENCY, frequency); + values.put(Words.LOCALE, mLocale); + values.put(Words.APP_ID, 0); + + getContext().getContentResolver().insert(Words.CONTENT_URI, values); // In case the above does a synchronous callback of the change observer mRequiresReload = false; }