Load UserDictionary and AutoDictionary in a background thread.
This is to avoid ANRs during bootup, as some of the providers may not have been initialized yet. Refactored the ContactsDictionary and moved the async loading code to ExpandableDictionary to share with the other dicts. Bug: 2501133 Change-Id: I20393edb6fdf5df2f54ebac8dd04419a592177a2main
parent
97e2d11039
commit
283a77f633
|
@ -96,11 +96,14 @@ public class AutoDictionary extends ExpandableDictionary {
|
||||||
return frequency >= VALIDITY_THRESHOLD;
|
return frequency >= VALIDITY_THRESHOLD;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
public void close() {
|
public void close() {
|
||||||
mOpenHelper.close();
|
mOpenHelper.close();
|
||||||
|
super.close();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void loadDictionary() {
|
@Override
|
||||||
|
public void loadDictionaryAsync() {
|
||||||
// Load the words that correspond to the current input locale
|
// Load the words that correspond to the current input locale
|
||||||
Cursor cursor = query(COLUMN_LOCALE + "=?", new String[] { mLocale });
|
Cursor cursor = query(COLUMN_LOCALE + "=?", new String[] { mLocale });
|
||||||
if (cursor.moveToFirst()) {
|
if (cursor.moveToFirst()) {
|
||||||
|
@ -183,15 +186,6 @@ public class AutoDictionary extends ExpandableDictionary {
|
||||||
return c;
|
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) {
|
private int delete(String where, String[] whereArgs) {
|
||||||
SQLiteDatabase db = mOpenHelper.getWritableDatabase();
|
SQLiteDatabase db = mOpenHelper.getWritableDatabase();
|
||||||
int count = db.delete(AUTODICT_TABLE_NAME, where, whereArgs);
|
int count = db.delete(AUTODICT_TABLE_NAME, where, whereArgs);
|
||||||
|
|
|
@ -35,15 +35,8 @@ public class ContactsDictionary extends ExpandableDictionary {
|
||||||
|
|
||||||
private ContentObserver mObserver;
|
private ContentObserver mObserver;
|
||||||
|
|
||||||
private boolean mRequiresReload;
|
|
||||||
|
|
||||||
private long mLastLoadedContacts;
|
private long mLastLoadedContacts;
|
||||||
|
|
||||||
private boolean mUpdatingContacts;
|
|
||||||
|
|
||||||
// Use this lock before touching mUpdatingContacts & mRequiresDownload
|
|
||||||
private Object mUpdatingLock = new Object();
|
|
||||||
|
|
||||||
public ContactsDictionary(Context context) {
|
public ContactsDictionary(Context context) {
|
||||||
super(context);
|
super(context);
|
||||||
// Perform a managed query. The Activity will handle closing and requerying the cursor
|
// Perform a managed query. The Activity will handle closing and requerying the cursor
|
||||||
|
@ -53,15 +46,10 @@ public class ContactsDictionary extends ExpandableDictionary {
|
||||||
cres.registerContentObserver(Contacts.CONTENT_URI, true, mObserver = new ContentObserver(null) {
|
cres.registerContentObserver(Contacts.CONTENT_URI, true, mObserver = new ContentObserver(null) {
|
||||||
@Override
|
@Override
|
||||||
public void onChange(boolean self) {
|
public void onChange(boolean self) {
|
||||||
synchronized (mUpdatingLock) {
|
setRequiresReload(true);
|
||||||
mRequiresReload = true;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
loadDictionary();
|
||||||
synchronized (mUpdatingLock) {
|
|
||||||
loadDictionaryAsyncLocked();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public synchronized void close() {
|
public synchronized void close() {
|
||||||
|
@ -69,41 +57,26 @@ public class ContactsDictionary extends ExpandableDictionary {
|
||||||
getContext().getContentResolver().unregisterContentObserver(mObserver);
|
getContext().getContentResolver().unregisterContentObserver(mObserver);
|
||||||
mObserver = null;
|
mObserver = null;
|
||||||
}
|
}
|
||||||
|
super.close();
|
||||||
}
|
}
|
||||||
|
|
||||||
private synchronized void loadDictionaryAsyncLocked() {
|
@Override
|
||||||
|
public void startDictionaryLoadingTaskLocked() {
|
||||||
long now = SystemClock.uptimeMillis();
|
long now = SystemClock.uptimeMillis();
|
||||||
if (mLastLoadedContacts == 0
|
if (mLastLoadedContacts == 0
|
||||||
|| now - mLastLoadedContacts > 30 * 60 * 1000 /* 30 minutes */) {
|
|| now - mLastLoadedContacts > 30 * 60 * 1000 /* 30 minutes */) {
|
||||||
if (!mUpdatingContacts) {
|
super.startDictionaryLoadingTaskLocked();
|
||||||
mUpdatingContacts = true;
|
|
||||||
mRequiresReload = false;
|
|
||||||
new LoadContactsTask().execute();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public synchronized void getWords(final WordComposer codes, final WordCallback callback,
|
public void loadDictionaryAsync() {
|
||||||
int[] nextLettersFrequencies) {
|
Cursor cursor = getContext().getContentResolver()
|
||||||
synchronized (mUpdatingLock) {
|
.query(Contacts.CONTENT_URI, PROJECTION, null, null, null);
|
||||||
// If we need to update, start off a background task
|
if (cursor != null) {
|
||||||
if (mRequiresReload) loadDictionaryAsyncLocked();
|
addWords(cursor);
|
||||||
// Currently updating contacts, don't return any results.
|
|
||||||
if (mUpdatingContacts) return;
|
|
||||||
}
|
}
|
||||||
super.getWords(codes, callback, nextLettersFrequencies);
|
mLastLoadedContacts = SystemClock.uptimeMillis();
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public synchronized boolean isValidWord(CharSequence word) {
|
|
||||||
synchronized (mUpdatingLock) {
|
|
||||||
// If we need to update, start off a background task
|
|
||||||
if (mRequiresReload) loadDictionaryAsyncLocked();
|
|
||||||
if (mUpdatingContacts) return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
return super.isValidWord(word);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void addWords(Cursor cursor) {
|
private void addWords(Cursor cursor) {
|
||||||
|
@ -150,27 +123,5 @@ public class ContactsDictionary extends ExpandableDictionary {
|
||||||
}
|
}
|
||||||
cursor.close();
|
cursor.close();
|
||||||
}
|
}
|
||||||
|
|
||||||
private class LoadContactsTask extends AsyncTask<Void, Void, Void> {
|
|
||||||
@Override
|
|
||||||
protected Void doInBackground(Void... v) {
|
|
||||||
Cursor cursor = getContext().getContentResolver()
|
|
||||||
.query(Contacts.CONTENT_URI, PROJECTION, null, null, null);
|
|
||||||
if (cursor != null) {
|
|
||||||
addWords(cursor);
|
|
||||||
}
|
|
||||||
mLastLoadedContacts = SystemClock.uptimeMillis();
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected void onPostExecute(Void result) {
|
|
||||||
// TODO Auto-generated method stub
|
|
||||||
synchronized (mUpdatingLock) {
|
|
||||||
mUpdatingContacts = false;
|
|
||||||
}
|
|
||||||
super.onPostExecute(result);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -16,7 +16,11 @@
|
||||||
|
|
||||||
package com.android.inputmethod.latin;
|
package com.android.inputmethod.latin;
|
||||||
|
|
||||||
|
import com.android.inputmethod.latin.Dictionary.WordCallback;
|
||||||
|
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
|
import android.os.AsyncTask;
|
||||||
|
import android.os.SystemClock;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Base class for an in-memory dictionary that can grow dynamically and can
|
* Base class for an in-memory dictionary that can grow dynamically and can
|
||||||
|
@ -32,6 +36,13 @@ public class ExpandableDictionary extends Dictionary {
|
||||||
public static final int MAX_WORD_LENGTH = 32;
|
public static final int MAX_WORD_LENGTH = 32;
|
||||||
private static final char QUOTE = '\'';
|
private static final char QUOTE = '\'';
|
||||||
|
|
||||||
|
private boolean mRequiresReload;
|
||||||
|
|
||||||
|
private boolean mUpdatingDictionary;
|
||||||
|
|
||||||
|
// Use this lock before touching mUpdatingDictionary & mRequiresDownload
|
||||||
|
private Object mUpdatingLock = new Object();
|
||||||
|
|
||||||
static class Node {
|
static class Node {
|
||||||
char code;
|
char code;
|
||||||
int frequency;
|
int frequency;
|
||||||
|
@ -70,6 +81,34 @@ public class ExpandableDictionary extends Dictionary {
|
||||||
mCodes = new int[MAX_WORD_LENGTH][];
|
mCodes = new int[MAX_WORD_LENGTH][];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void loadDictionary() {
|
||||||
|
synchronized (mUpdatingLock) {
|
||||||
|
startDictionaryLoadingTaskLocked();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void startDictionaryLoadingTaskLocked() {
|
||||||
|
if (!mUpdatingDictionary) {
|
||||||
|
mUpdatingDictionary = true;
|
||||||
|
mRequiresReload = false;
|
||||||
|
new LoadDictionaryTask().execute();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setRequiresReload(boolean reload) {
|
||||||
|
synchronized (mUpdatingLock) {
|
||||||
|
mRequiresReload = reload;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean getRequiresReload() {
|
||||||
|
return mRequiresReload;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Override to load your dictionary here, on a background thread. */
|
||||||
|
public void loadDictionaryAsync() {
|
||||||
|
}
|
||||||
|
|
||||||
Context getContext() {
|
Context getContext() {
|
||||||
return mContext;
|
return mContext;
|
||||||
}
|
}
|
||||||
|
@ -119,6 +158,13 @@ public class ExpandableDictionary extends Dictionary {
|
||||||
@Override
|
@Override
|
||||||
public void getWords(final WordComposer codes, final WordCallback callback,
|
public void getWords(final WordComposer codes, final WordCallback callback,
|
||||||
int[] nextLettersFrequencies) {
|
int[] nextLettersFrequencies) {
|
||||||
|
synchronized (mUpdatingLock) {
|
||||||
|
// If we need to update, start off a background task
|
||||||
|
if (mRequiresReload) startDictionaryLoadingTaskLocked();
|
||||||
|
// Currently updating contacts, don't return any results.
|
||||||
|
if (mUpdatingDictionary) return;
|
||||||
|
}
|
||||||
|
|
||||||
mInputLength = codes.size();
|
mInputLength = codes.size();
|
||||||
mNextLettersFrequencies = nextLettersFrequencies;
|
mNextLettersFrequencies = nextLettersFrequencies;
|
||||||
if (mCodes.length < mInputLength) mCodes = new int[mInputLength][];
|
if (mCodes.length < mInputLength) mCodes = new int[mInputLength][];
|
||||||
|
@ -135,6 +181,11 @@ public class ExpandableDictionary extends Dictionary {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public synchronized boolean isValidWord(CharSequence word) {
|
public synchronized boolean isValidWord(CharSequence word) {
|
||||||
|
synchronized (mUpdatingLock) {
|
||||||
|
// If we need to update, start off a background task
|
||||||
|
if (mRequiresReload) startDictionaryLoadingTaskLocked();
|
||||||
|
if (mUpdatingDictionary) return false;
|
||||||
|
}
|
||||||
final int freq = getWordFrequencyRec(mRoots, word, 0, word.length());
|
final int freq = getWordFrequencyRec(mRoots, word, 0, word.length());
|
||||||
return freq > -1;
|
return freq > -1;
|
||||||
}
|
}
|
||||||
|
@ -277,6 +328,24 @@ public class ExpandableDictionary extends Dictionary {
|
||||||
mRoots = new NodeArray();
|
mRoots = new NodeArray();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private class LoadDictionaryTask extends AsyncTask<Void, Void, Void> {
|
||||||
|
@Override
|
||||||
|
protected Void doInBackground(Void... v) {
|
||||||
|
loadDictionaryAsync();
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void onPostExecute(Void result) {
|
||||||
|
// TODO Auto-generated method stub
|
||||||
|
synchronized (mUpdatingLock) {
|
||||||
|
mUpdatingDictionary = false;
|
||||||
|
}
|
||||||
|
super.onPostExecute(result);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
static char toLowerCase(char c) {
|
static char toLowerCase(char c) {
|
||||||
if (c < BASE_CHARS.length) {
|
if (c < BASE_CHARS.length) {
|
||||||
c = BASE_CHARS[c];
|
c = BASE_CHARS[c];
|
||||||
|
|
|
@ -16,16 +16,11 @@
|
||||||
|
|
||||||
package com.android.inputmethod.latin;
|
package com.android.inputmethod.latin;
|
||||||
|
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.Locale;
|
|
||||||
|
|
||||||
import android.content.ContentResolver;
|
import android.content.ContentResolver;
|
||||||
import android.content.ContentValues;
|
import android.content.ContentValues;
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
import android.database.ContentObserver;
|
import android.database.ContentObserver;
|
||||||
import android.database.Cursor;
|
import android.database.Cursor;
|
||||||
import android.net.Uri;
|
|
||||||
import android.provider.UserDictionary.Words;
|
import android.provider.UserDictionary.Words;
|
||||||
|
|
||||||
public class UserDictionary extends ExpandableDictionary {
|
public class UserDictionary extends ExpandableDictionary {
|
||||||
|
@ -40,8 +35,6 @@ public class UserDictionary extends ExpandableDictionary {
|
||||||
private static final int INDEX_FREQUENCY = 2;
|
private static final int INDEX_FREQUENCY = 2;
|
||||||
|
|
||||||
private ContentObserver mObserver;
|
private ContentObserver mObserver;
|
||||||
|
|
||||||
private boolean mRequiresReload;
|
|
||||||
private String mLocale;
|
private String mLocale;
|
||||||
|
|
||||||
public UserDictionary(Context context, String locale) {
|
public UserDictionary(Context context, String locale) {
|
||||||
|
@ -54,26 +47,27 @@ public class UserDictionary extends ExpandableDictionary {
|
||||||
cres.registerContentObserver(Words.CONTENT_URI, true, mObserver = new ContentObserver(null) {
|
cres.registerContentObserver(Words.CONTENT_URI, true, mObserver = new ContentObserver(null) {
|
||||||
@Override
|
@Override
|
||||||
public void onChange(boolean self) {
|
public void onChange(boolean self) {
|
||||||
mRequiresReload = true;
|
setRequiresReload(true);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
loadDictionary();
|
loadDictionary();
|
||||||
}
|
}
|
||||||
|
|
||||||
public synchronized void close() {
|
public synchronized void close() {
|
||||||
if (mObserver != null) {
|
if (mObserver != null) {
|
||||||
getContext().getContentResolver().unregisterContentObserver(mObserver);
|
getContext().getContentResolver().unregisterContentObserver(mObserver);
|
||||||
mObserver = null;
|
mObserver = null;
|
||||||
}
|
}
|
||||||
|
super.close();
|
||||||
}
|
}
|
||||||
|
|
||||||
private synchronized void loadDictionary() {
|
@Override
|
||||||
|
public void loadDictionaryAsync() {
|
||||||
Cursor cursor = getContext().getContentResolver()
|
Cursor cursor = getContext().getContentResolver()
|
||||||
.query(Words.CONTENT_URI, PROJECTION, "(locale IS NULL) or (locale=?)",
|
.query(Words.CONTENT_URI, PROJECTION, "(locale IS NULL) or (locale=?)",
|
||||||
new String[] { mLocale }, null);
|
new String[] { mLocale }, null);
|
||||||
addWords(cursor);
|
addWords(cursor);
|
||||||
mRequiresReload = false;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -86,7 +80,8 @@ public class UserDictionary extends ExpandableDictionary {
|
||||||
*/
|
*/
|
||||||
@Override
|
@Override
|
||||||
public synchronized void addWord(String word, int frequency) {
|
public synchronized void addWord(String word, int frequency) {
|
||||||
if (mRequiresReload) loadDictionary();
|
// Force load the dictionary here synchronously
|
||||||
|
if (getRequiresReload()) loadDictionaryAsync();
|
||||||
// Safeguard against adding long words. Can cause stack overflow.
|
// Safeguard against adding long words. Can cause stack overflow.
|
||||||
if (word.length() >= getMaxWordLength()) return;
|
if (word.length() >= getMaxWordLength()) return;
|
||||||
|
|
||||||
|
@ -101,19 +96,17 @@ public class UserDictionary extends ExpandableDictionary {
|
||||||
|
|
||||||
getContext().getContentResolver().insert(Words.CONTENT_URI, values);
|
getContext().getContentResolver().insert(Words.CONTENT_URI, values);
|
||||||
// In case the above does a synchronous callback of the change observer
|
// In case the above does a synchronous callback of the change observer
|
||||||
mRequiresReload = false;
|
setRequiresReload(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public synchronized void getWords(final WordComposer codes, final WordCallback callback,
|
public synchronized void getWords(final WordComposer codes, final WordCallback callback,
|
||||||
int[] nextLettersFrequencies) {
|
int[] nextLettersFrequencies) {
|
||||||
if (mRequiresReload) loadDictionary();
|
|
||||||
super.getWords(codes, callback, nextLettersFrequencies);
|
super.getWords(codes, callback, nextLettersFrequencies);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public synchronized boolean isValidWord(CharSequence word) {
|
public synchronized boolean isValidWord(CharSequence word) {
|
||||||
if (mRequiresReload) loadDictionary();
|
|
||||||
return super.isValidWord(word);
|
return super.isValidWord(word);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue