PageRenderTime 51ms CodeModel.GetById 16ms app.highlight 29ms RepoModel.GetById 1ms app.codeStats 0ms

/ime/latinime/src/com/googlecode/eyesfree/inputmethod/latin/AutoDictionary.java

http://eyes-free.googlecode.com/
Java | 259 lines | 170 code | 31 blank | 58 comment | 17 complexity | bf54b64d36de9acf57df0d07b172ed51 MD5 | raw file
  1/*
  2 * Copyright (C) 2010 Google Inc.
  3 *
  4 * Licensed under the Apache License, Version 2.0 (the "License"); you may not
  5 * use this file except in compliance with the License. You may obtain a copy of
  6 * the License at
  7 *
  8 * http://www.apache.org/licenses/LICENSE-2.0
  9 *
 10 * Unless required by applicable law or agreed to in writing, software
 11 * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
 12 * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
 13 * License for the specific language governing permissions and limitations under
 14 * the License.
 15 */
 16
 17package com.googlecode.eyesfree.inputmethod.latin;
 18
 19import android.content.ContentValues;
 20import android.content.Context;
 21import android.database.Cursor;
 22import android.database.sqlite.SQLiteDatabase;
 23import android.database.sqlite.SQLiteOpenHelper;
 24import android.database.sqlite.SQLiteQueryBuilder;
 25import android.os.AsyncTask;
 26import android.provider.BaseColumns;
 27import android.util.Log;
 28
 29import java.util.HashMap;
 30import java.util.Map.Entry;
 31import java.util.Set;
 32
 33/**
 34 * Stores new words temporarily until they are promoted to the user dictionary
 35 * for longevity. Words in the auto dictionary are used to determine if it's ok
 36 * to accept a word that's not in the main or user dictionary. Using a new word
 37 * repeatedly will promote it to the user dictionary.
 38 */
 39public class AutoDictionary extends ExpandableDictionary {
 40    // Weight added to a user picking a new word from the suggestion strip
 41    static final int FREQUENCY_FOR_PICKED = 3;
 42    // Weight added to a user typing a new word that doesn't get corrected (or is reverted)
 43    static final int FREQUENCY_FOR_TYPED = 1;
 44    // A word that is frequently typed and gets promoted to the user dictionary, uses this
 45    // frequency.
 46    static final int FREQUENCY_FOR_AUTO_ADD = 250;
 47    // If the user touches a typed word 2 times or more, it will become valid.
 48    private static final int VALIDITY_THRESHOLD = 2 * FREQUENCY_FOR_PICKED;
 49    // If the user touches a typed word 4 times or more, it will be added to the user dict.
 50    private static final int PROMOTION_THRESHOLD = 4 * FREQUENCY_FOR_PICKED;
 51
 52    private LatinIME mIme;
 53    // Locale for which this auto dictionary is storing words
 54    private String mLocale;
 55
 56    private HashMap<String,Integer> mPendingWrites = new HashMap<String,Integer>();
 57    private final Object mPendingWritesLock = new Object();
 58
 59    private static final String DATABASE_NAME = "auto_dict.db";
 60    private static final int DATABASE_VERSION = 1;
 61
 62    // These are the columns in the dictionary
 63    // TODO: Consume less space by using a unique id for locale instead of the whole
 64    // 2-5 character string.
 65    private static final String COLUMN_ID = BaseColumns._ID;
 66    private static final String COLUMN_WORD = "word";
 67    private static final String COLUMN_FREQUENCY = "freq";
 68    private static final String COLUMN_LOCALE = "locale";
 69
 70    /** Sort by descending order of frequency. */
 71    public static final String DEFAULT_SORT_ORDER = COLUMN_FREQUENCY + " DESC";
 72
 73    /** Name of the words table in the auto_dict.db */
 74    private static final String AUTODICT_TABLE_NAME = "words";
 75
 76    private static HashMap<String, String> sDictProjectionMap;
 77
 78    static {
 79        sDictProjectionMap = new HashMap<String, String>();
 80        sDictProjectionMap.put(COLUMN_ID, COLUMN_ID);
 81        sDictProjectionMap.put(COLUMN_WORD, COLUMN_WORD);
 82        sDictProjectionMap.put(COLUMN_FREQUENCY, COLUMN_FREQUENCY);
 83        sDictProjectionMap.put(COLUMN_LOCALE, COLUMN_LOCALE);
 84    }
 85
 86    private static DatabaseHelper sOpenHelper = null;
 87
 88    public AutoDictionary(Context context, LatinIME ime, String locale, int dicTypeId) {
 89        super(context, dicTypeId);
 90        mIme = ime;
 91        mLocale = locale;
 92        if (sOpenHelper == null) {
 93            sOpenHelper = new DatabaseHelper(getContext());
 94        }
 95        if (mLocale != null && mLocale.length() > 1) {
 96            loadDictionary();
 97        }
 98    }
 99
100    @Override
101    public boolean isValidWord(CharSequence word) {
102        final int frequency = getWordFrequency(word);
103        return frequency >= VALIDITY_THRESHOLD;
104    }
105
106    @Override
107    public void close() {
108        flushPendingWrites();
109        // Don't close the database as locale changes will require it to be reopened anyway
110        // Also, the database is written to somewhat frequently, so it needs to be kept alive
111        // throughout the life of the process.
112        // mOpenHelper.close();
113        super.close();
114    }
115
116    @Override
117    public void loadDictionaryAsync() {
118        // Load the words that correspond to the current input locale
119        Cursor cursor = query(COLUMN_LOCALE + "=?", new String[] { mLocale });
120        try {
121            if (cursor.moveToFirst()) {
122                int wordIndex = cursor.getColumnIndex(COLUMN_WORD);
123                int frequencyIndex = cursor.getColumnIndex(COLUMN_FREQUENCY);
124                while (!cursor.isAfterLast()) {
125                    String word = cursor.getString(wordIndex);
126                    int frequency = cursor.getInt(frequencyIndex);
127                    // Safeguard against adding really long words. Stack may overflow due
128                    // to recursive lookup
129                    if (word.length() < getMaxWordLength()) {
130                        super.addWord(word, frequency);
131                    }
132                    cursor.moveToNext();
133                }
134            }
135        } finally {
136            cursor.close();
137        }
138    }
139
140    @Override
141    public void addWord(String word, int addFrequency) {
142        final int length = word.length();
143        // Don't add very short or very long words.
144        if (length < 2 || length > getMaxWordLength()) return;
145        if (mIme.getCurrentWord().isAutoCapitalized()) {
146            // Remove caps before adding
147            word = Character.toLowerCase(word.charAt(0)) + word.substring(1);
148        }
149        int freq = getWordFrequency(word);
150        freq = freq < 0 ? addFrequency : freq + addFrequency;
151        super.addWord(word, freq);
152
153        if (freq >= PROMOTION_THRESHOLD) {
154            mIme.promoteToUserDictionary(word, FREQUENCY_FOR_AUTO_ADD);
155            freq = 0;
156        }
157
158        synchronized (mPendingWritesLock) {
159            // Write a null frequency if it is to be deleted from the db
160            mPendingWrites.put(word, freq == 0 ? null : Integer.valueOf(freq));
161        }
162    }
163
164    /**
165     * Schedules a background thread to write any pending words to the database.
166     */
167    public void flushPendingWrites() {
168        synchronized (mPendingWritesLock) {
169            // Nothing pending? Return
170            if (mPendingWrites.isEmpty()) return;
171            // Create a background thread to write the pending entries
172            new UpdateDbTask(getContext(), sOpenHelper, mPendingWrites, mLocale).execute();
173            // Create a new map for writing new entries into while the old one is written to db
174            mPendingWrites = new HashMap<String, Integer>();
175        }
176    }
177
178    /**
179     * This class helps open, create, and upgrade the database file.
180     */
181    private static class DatabaseHelper extends SQLiteOpenHelper {
182
183        DatabaseHelper(Context context) {
184            super(context, DATABASE_NAME, null, DATABASE_VERSION);
185        }
186
187        @Override
188        public void onCreate(SQLiteDatabase db) {
189            db.execSQL("CREATE TABLE " + AUTODICT_TABLE_NAME + " ("
190                    + COLUMN_ID + " INTEGER PRIMARY KEY,"
191                    + COLUMN_WORD + " TEXT,"
192                    + COLUMN_FREQUENCY + " INTEGER,"
193                    + COLUMN_LOCALE + " TEXT"
194                    + ");");
195        }
196
197        @Override
198        public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
199            Log.w("AutoDictionary", "Upgrading database from version " + oldVersion + " to "
200                    + newVersion + ", which will destroy all old data");
201            db.execSQL("DROP TABLE IF EXISTS " + AUTODICT_TABLE_NAME);
202            onCreate(db);
203        }
204    }
205
206    private Cursor query(String selection, String[] selectionArgs) {
207        SQLiteQueryBuilder qb = new SQLiteQueryBuilder();
208        qb.setTables(AUTODICT_TABLE_NAME);
209        qb.setProjectionMap(sDictProjectionMap);
210
211        // Get the database and run the query
212        SQLiteDatabase db = sOpenHelper.getReadableDatabase();
213        Cursor c = qb.query(db, null, selection, selectionArgs, null, null,
214                DEFAULT_SORT_ORDER);
215        return c;
216    }
217
218    /**
219     * Async task to write pending words to the database so that it stays in sync with
220     * the in-memory trie.
221     */
222    private static class UpdateDbTask extends AsyncTask<Void, Void, Void> {
223        private final HashMap<String, Integer> mMap;
224        private final DatabaseHelper mDbHelper;
225        private final String mLocale;
226
227        public UpdateDbTask(Context context, DatabaseHelper openHelper,
228                HashMap<String, Integer> pendingWrites, String locale) {
229            mMap = pendingWrites;
230            mLocale = locale;
231            mDbHelper = openHelper;
232        }
233
234        @Override
235        protected Void doInBackground(Void... v) {
236            SQLiteDatabase db = mDbHelper.getWritableDatabase();
237            // Write all the entries to the db
238            Set<Entry<String,Integer>> mEntries = mMap.entrySet();
239            for (Entry<String,Integer> entry : mEntries) {
240                Integer freq = entry.getValue();
241                db.delete(AUTODICT_TABLE_NAME, COLUMN_WORD + "=? AND " + COLUMN_LOCALE + "=?",
242                        new String[] { entry.getKey(), mLocale });
243                if (freq != null) {
244                    db.insert(AUTODICT_TABLE_NAME, null,
245                            getContentValues(entry.getKey(), freq, mLocale));
246                }
247            }
248            return null;
249        }
250
251        private ContentValues getContentValues(String word, int frequency, String locale) {
252            ContentValues values = new ContentValues(4);
253            values.put(COLUMN_WORD, word);
254            values.put(COLUMN_FREQUENCY, frequency);
255            values.put(COLUMN_LOCALE, locale);
256            return values;
257        }
258    }
259}