2010-02-24 03:01:43 +00:00
|
|
|
/*
|
2011-05-20 03:09:57 +00:00
|
|
|
* Copyright (C) 2010 The Android Open Source Project
|
2010-02-24 03:01:43 +00:00
|
|
|
*
|
|
|
|
* 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 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;
|
2010-03-23 22:59:59 +00:00
|
|
|
import android.os.AsyncTask;
|
2010-02-24 03:01:43 +00:00
|
|
|
import android.provider.BaseColumns;
|
|
|
|
import android.util.Log;
|
|
|
|
|
2010-11-29 08:57:48 +00:00
|
|
|
import java.util.HashMap;
|
|
|
|
import java.util.Map.Entry;
|
|
|
|
import java.util.Set;
|
|
|
|
|
2010-02-24 03:01:43 +00:00
|
|
|
/**
|
2011-07-14 22:57:26 +00:00
|
|
|
* This class (inherited from the old AutoDictionary) is used for user history
|
|
|
|
* based dictionary. It stores words that the user typed to supply a provision
|
|
|
|
* for suggesting and re-ordering of candidates.
|
2010-02-24 03:01:43 +00:00
|
|
|
*/
|
2011-07-14 22:57:26 +00:00
|
|
|
public class UserUnigramDictionary extends ExpandableDictionary {
|
2011-07-15 04:40:20 +00:00
|
|
|
static final boolean ENABLE_USER_UNIGRAM_DICTIONARY = false;
|
|
|
|
|
2010-02-24 03:01:43 +00:00
|
|
|
// 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;
|
|
|
|
// 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;
|
|
|
|
|
|
|
|
private LatinIME mIme;
|
2011-07-14 22:57:26 +00:00
|
|
|
// Locale for which this user unigram dictionary is storing words
|
2010-02-24 03:01:43 +00:00
|
|
|
private String mLocale;
|
|
|
|
|
2010-03-23 22:59:59 +00:00
|
|
|
private HashMap<String,Integer> mPendingWrites = new HashMap<String,Integer>();
|
|
|
|
private final Object mPendingWritesLock = new Object();
|
|
|
|
|
2011-07-14 22:57:26 +00:00
|
|
|
// TODO: we should probably change the database name
|
2010-02-24 03:01:43 +00:00
|
|
|
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";
|
|
|
|
|
2011-07-14 22:57:26 +00:00
|
|
|
/** Name of the words table in the database */
|
|
|
|
private static final String USER_UNIGRAM_DICT_TABLE_NAME = "words";
|
2010-02-24 03:01:43 +00:00
|
|
|
|
|
|
|
private static HashMap<String, String> sDictProjectionMap;
|
|
|
|
|
|
|
|
static {
|
2011-07-15 04:40:20 +00:00
|
|
|
if (ENABLE_USER_UNIGRAM_DICTIONARY) {
|
|
|
|
sDictProjectionMap = new HashMap<String, String>();
|
|
|
|
sDictProjectionMap.put(COLUMN_ID, COLUMN_ID);
|
|
|
|
sDictProjectionMap.put(COLUMN_WORD, COLUMN_WORD);
|
|
|
|
sDictProjectionMap.put(COLUMN_FREQUENCY, COLUMN_FREQUENCY);
|
|
|
|
sDictProjectionMap.put(COLUMN_LOCALE, COLUMN_LOCALE);
|
|
|
|
}
|
2010-02-24 03:01:43 +00:00
|
|
|
}
|
|
|
|
|
2010-08-04 01:28:38 +00:00
|
|
|
private static DatabaseHelper sOpenHelper = null;
|
2010-02-24 03:01:43 +00:00
|
|
|
|
2011-07-14 22:57:26 +00:00
|
|
|
public UserUnigramDictionary(Context context, LatinIME ime, String locale, int dicTypeId) {
|
2010-06-02 09:30:27 +00:00
|
|
|
super(context, dicTypeId);
|
2011-07-15 04:40:20 +00:00
|
|
|
// Super must be first statement of the constructor... I'd like not to do it if the
|
|
|
|
// user unigram dictionary is not enabled, but Java won't let me.
|
|
|
|
if (!ENABLE_USER_UNIGRAM_DICTIONARY) return;
|
2010-02-24 03:01:43 +00:00
|
|
|
mIme = ime;
|
|
|
|
mLocale = locale;
|
2010-08-04 01:28:38 +00:00
|
|
|
if (sOpenHelper == null) {
|
|
|
|
sOpenHelper = new DatabaseHelper(getContext());
|
2010-03-31 21:01:59 +00:00
|
|
|
}
|
2010-02-24 03:01:43 +00:00
|
|
|
if (mLocale != null && mLocale.length() > 1) {
|
|
|
|
loadDictionary();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
@Override
|
2010-12-10 06:24:28 +00:00
|
|
|
public synchronized boolean isValidWord(CharSequence word) {
|
2011-07-15 04:40:20 +00:00
|
|
|
if (!ENABLE_USER_UNIGRAM_DICTIONARY) return false;
|
2010-02-24 03:01:43 +00:00
|
|
|
final int frequency = getWordFrequency(word);
|
|
|
|
return frequency >= VALIDITY_THRESHOLD;
|
|
|
|
}
|
|
|
|
|
2010-03-10 19:39:06 +00:00
|
|
|
@Override
|
2010-02-24 03:01:43 +00:00
|
|
|
public void close() {
|
2011-07-15 04:40:20 +00:00
|
|
|
super.close();
|
|
|
|
if (!ENABLE_USER_UNIGRAM_DICTIONARY) return;
|
2010-03-23 22:59:59 +00:00
|
|
|
flushPendingWrites();
|
2010-03-31 21:01:59 +00:00
|
|
|
// 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();
|
2010-02-24 03:01:43 +00:00
|
|
|
}
|
|
|
|
|
2010-03-10 19:39:06 +00:00
|
|
|
@Override
|
|
|
|
public void loadDictionaryAsync() {
|
2011-07-15 04:40:20 +00:00
|
|
|
if (!ENABLE_USER_UNIGRAM_DICTIONARY) return;
|
2010-02-24 03:01:43 +00:00
|
|
|
// Load the words that correspond to the current input locale
|
|
|
|
Cursor cursor = query(COLUMN_LOCALE + "=?", new String[] { mLocale });
|
2010-03-31 21:01:59 +00:00
|
|
|
try {
|
|
|
|
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();
|
2010-02-24 03:01:43 +00:00
|
|
|
}
|
|
|
|
}
|
2010-03-31 21:01:59 +00:00
|
|
|
} finally {
|
|
|
|
cursor.close();
|
2010-02-24 03:01:43 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
@Override
|
2010-12-10 06:24:28 +00:00
|
|
|
public void addWord(String newWord, int addFrequency) {
|
2011-07-15 04:40:20 +00:00
|
|
|
if (!ENABLE_USER_UNIGRAM_DICTIONARY) return;
|
2010-12-10 06:24:28 +00:00
|
|
|
String word = newWord;
|
2010-02-24 03:01:43 +00:00
|
|
|
final int length = word.length();
|
|
|
|
// Don't add very short or very long words.
|
|
|
|
if (length < 2 || length > getMaxWordLength()) return;
|
2011-12-13 14:08:12 +00:00
|
|
|
if (mIme.isAutoCapitalized()) {
|
2010-02-24 03:01:43 +00:00
|
|
|
// 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);
|
2010-03-23 22:59:59 +00:00
|
|
|
|
|
|
|
synchronized (mPendingWritesLock) {
|
|
|
|
// Write a null frequency if it is to be deleted from the db
|
|
|
|
mPendingWrites.put(word, freq == 0 ? null : new Integer(freq));
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Schedules a background thread to write any pending words to the database.
|
|
|
|
*/
|
|
|
|
public void flushPendingWrites() {
|
2011-07-15 04:40:20 +00:00
|
|
|
if (!ENABLE_USER_UNIGRAM_DICTIONARY) return;
|
2010-03-23 22:59:59 +00:00
|
|
|
synchronized (mPendingWritesLock) {
|
|
|
|
// Nothing pending? Return
|
|
|
|
if (mPendingWrites.isEmpty()) return;
|
|
|
|
// Create a background thread to write the pending entries
|
2011-12-15 10:32:11 +00:00
|
|
|
new UpdateDbTask(sOpenHelper, mPendingWrites, mLocale).execute();
|
2010-03-23 22:59:59 +00:00
|
|
|
// Create a new map for writing new entries into while the old one is written to db
|
|
|
|
mPendingWrites = new HashMap<String, Integer>();
|
2010-02-24 03:01:43 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* 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) {
|
2011-07-14 22:57:26 +00:00
|
|
|
db.execSQL("CREATE TABLE " + USER_UNIGRAM_DICT_TABLE_NAME + " ("
|
2010-02-24 03:01:43 +00:00
|
|
|
+ COLUMN_ID + " INTEGER PRIMARY KEY,"
|
|
|
|
+ COLUMN_WORD + " TEXT,"
|
|
|
|
+ COLUMN_FREQUENCY + " INTEGER,"
|
|
|
|
+ COLUMN_LOCALE + " TEXT"
|
|
|
|
+ ");");
|
|
|
|
}
|
|
|
|
|
|
|
|
@Override
|
|
|
|
public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
|
2011-07-14 22:57:26 +00:00
|
|
|
Log.w("UserUnigramDictionary", "Upgrading database from version " + oldVersion + " to "
|
2010-02-24 03:01:43 +00:00
|
|
|
+ newVersion + ", which will destroy all old data");
|
2011-07-14 22:57:26 +00:00
|
|
|
db.execSQL("DROP TABLE IF EXISTS " + USER_UNIGRAM_DICT_TABLE_NAME);
|
2010-02-24 03:01:43 +00:00
|
|
|
onCreate(db);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2011-10-28 04:31:31 +00:00
|
|
|
private static Cursor query(String selection, String[] selectionArgs) {
|
2010-02-24 03:01:43 +00:00
|
|
|
SQLiteQueryBuilder qb = new SQLiteQueryBuilder();
|
2011-07-14 22:57:26 +00:00
|
|
|
qb.setTables(USER_UNIGRAM_DICT_TABLE_NAME);
|
2010-02-24 03:01:43 +00:00
|
|
|
qb.setProjectionMap(sDictProjectionMap);
|
|
|
|
|
|
|
|
// Get the database and run the query
|
2010-08-04 01:28:38 +00:00
|
|
|
SQLiteDatabase db = sOpenHelper.getReadableDatabase();
|
2010-02-24 03:01:43 +00:00
|
|
|
Cursor c = qb.query(db, null, selection, selectionArgs, null, null,
|
|
|
|
DEFAULT_SORT_ORDER);
|
|
|
|
return c;
|
|
|
|
}
|
|
|
|
|
2010-03-23 22:59:59 +00:00
|
|
|
/**
|
|
|
|
* Async task to write pending words to the database so that it stays in sync with
|
|
|
|
* the in-memory trie.
|
|
|
|
*/
|
|
|
|
private static class UpdateDbTask extends AsyncTask<Void, Void, Void> {
|
|
|
|
private final HashMap<String, Integer> mMap;
|
|
|
|
private final DatabaseHelper mDbHelper;
|
|
|
|
private final String mLocale;
|
|
|
|
|
2011-12-15 10:32:11 +00:00
|
|
|
public UpdateDbTask(DatabaseHelper openHelper, HashMap<String, Integer> pendingWrites,
|
|
|
|
String locale) {
|
2010-03-23 22:59:59 +00:00
|
|
|
mMap = pendingWrites;
|
|
|
|
mLocale = locale;
|
2010-03-31 21:01:59 +00:00
|
|
|
mDbHelper = openHelper;
|
2010-03-23 22:59:59 +00:00
|
|
|
}
|
2010-02-24 03:01:43 +00:00
|
|
|
|
2010-03-23 22:59:59 +00:00
|
|
|
@Override
|
|
|
|
protected Void doInBackground(Void... v) {
|
|
|
|
SQLiteDatabase db = mDbHelper.getWritableDatabase();
|
|
|
|
// Write all the entries to the db
|
|
|
|
Set<Entry<String,Integer>> mEntries = mMap.entrySet();
|
2010-03-31 21:01:59 +00:00
|
|
|
for (Entry<String,Integer> entry : mEntries) {
|
|
|
|
Integer freq = entry.getValue();
|
2011-07-14 22:57:26 +00:00
|
|
|
db.delete(USER_UNIGRAM_DICT_TABLE_NAME, COLUMN_WORD + "=? AND " + COLUMN_LOCALE
|
|
|
|
+ "=?", new String[] { entry.getKey(), mLocale });
|
2010-03-31 21:01:59 +00:00
|
|
|
if (freq != null) {
|
2011-07-14 22:57:26 +00:00
|
|
|
db.insert(USER_UNIGRAM_DICT_TABLE_NAME, null,
|
2010-03-31 21:01:59 +00:00
|
|
|
getContentValues(entry.getKey(), freq, mLocale));
|
2010-03-23 22:59:59 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
return null;
|
|
|
|
}
|
2010-02-24 03:01:43 +00:00
|
|
|
|
2011-10-28 04:31:31 +00:00
|
|
|
private static ContentValues getContentValues(String word, int frequency, String locale) {
|
2010-03-23 22:59:59 +00:00
|
|
|
ContentValues values = new ContentValues(4);
|
|
|
|
values.put(COLUMN_WORD, word);
|
|
|
|
values.put(COLUMN_FREQUENCY, frequency);
|
|
|
|
values.put(COLUMN_LOCALE, locale);
|
|
|
|
return values;
|
|
|
|
}
|
2010-02-24 03:01:43 +00:00
|
|
|
}
|
|
|
|
}
|