Merge "Load UserDictionary and AutoDictionary in a background thread."
diff --git a/java/src/com/android/inputmethod/latin/AutoDictionary.java b/java/src/com/android/inputmethod/latin/AutoDictionary.java
index 3d76dc3..da9dd6e 100644
--- a/java/src/com/android/inputmethod/latin/AutoDictionary.java
+++ b/java/src/com/android/inputmethod/latin/AutoDictionary.java
@@ -96,11 +96,14 @@
         return frequency >= VALIDITY_THRESHOLD;
     }
 
+    @Override
     public void close() {
         mOpenHelper.close();
+        super.close();
     }
 
-    private void loadDictionary() {
+    @Override
+    public void loadDictionaryAsync() {
         // Load the words that correspond to the current input locale
         Cursor cursor = query(COLUMN_LOCALE + "=?", new String[] { mLocale });
         if (cursor.moveToFirst()) {
@@ -183,15 +186,6 @@
         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);
diff --git a/java/src/com/android/inputmethod/latin/ContactsDictionary.java b/java/src/com/android/inputmethod/latin/ContactsDictionary.java
index f53ebf3..15edb70 100644
--- a/java/src/com/android/inputmethod/latin/ContactsDictionary.java
+++ b/java/src/com/android/inputmethod/latin/ContactsDictionary.java
@@ -35,15 +35,8 @@
 
     private ContentObserver mObserver;
 
-    private boolean mRequiresReload;
-
     private long mLastLoadedContacts;
 
-    private boolean mUpdatingContacts;
-
-    // Use this lock before touching mUpdatingContacts & mRequiresDownload
-    private Object mUpdatingLock = new Object();
-
     public ContactsDictionary(Context context) {
         super(context);
         // Perform a managed query. The Activity will handle closing and requerying the cursor
@@ -53,15 +46,10 @@
         cres.registerContentObserver(Contacts.CONTENT_URI, true, mObserver = new ContentObserver(null) {
             @Override
             public void onChange(boolean self) {
-                synchronized (mUpdatingLock) {
-                    mRequiresReload = true;
-                }
+                setRequiresReload(true);
             }
         });
-
-        synchronized (mUpdatingLock) {
-            loadDictionaryAsyncLocked();
-        }
+        loadDictionary();
     }
 
     public synchronized void close() {
@@ -69,41 +57,26 @@
             getContext().getContentResolver().unregisterContentObserver(mObserver);
             mObserver = null;
         }
+        super.close();
     }
 
-    private synchronized void loadDictionaryAsyncLocked() {
+    @Override
+    public void startDictionaryLoadingTaskLocked() {
         long now = SystemClock.uptimeMillis();
         if (mLastLoadedContacts == 0
                 || now - mLastLoadedContacts > 30 * 60 * 1000 /* 30 minutes */) {
-            if (!mUpdatingContacts) {
-                mUpdatingContacts = true;
-                mRequiresReload = false;
-                new LoadContactsTask().execute();
-            }
+            super.startDictionaryLoadingTaskLocked();
         }
     }
 
     @Override
-    public synchronized void getWords(final WordComposer codes, final WordCallback callback,
-            int[] nextLettersFrequencies) {
-        synchronized (mUpdatingLock) {
-            // If we need to update, start off a background task
-            if (mRequiresReload) loadDictionaryAsyncLocked();
-            // Currently updating contacts, don't return any results.
-            if (mUpdatingContacts) return;
+    public void loadDictionaryAsync() {
+        Cursor cursor = getContext().getContentResolver()
+                .query(Contacts.CONTENT_URI, PROJECTION, null, null, null);
+        if (cursor != null) {
+            addWords(cursor);
         }
-        super.getWords(codes, callback, nextLettersFrequencies);
-    }
-
-    @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);
+        mLastLoadedContacts = SystemClock.uptimeMillis();
     }
 
     private void addWords(Cursor cursor) {
@@ -150,27 +123,5 @@
         }
         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);
-        }
-        
-    }
 }
diff --git a/java/src/com/android/inputmethod/latin/ExpandableDictionary.java b/java/src/com/android/inputmethod/latin/ExpandableDictionary.java
index 0065937..46bc41c 100644
--- a/java/src/com/android/inputmethod/latin/ExpandableDictionary.java
+++ b/java/src/com/android/inputmethod/latin/ExpandableDictionary.java
@@ -16,7 +16,11 @@
 
 package com.android.inputmethod.latin;
 
+import com.android.inputmethod.latin.Dictionary.WordCallback;
+
 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
@@ -32,6 +36,13 @@
     public static final int MAX_WORD_LENGTH = 32;
     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 {
         char code;
         int frequency;
@@ -70,6 +81,34 @@
         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() {
         return mContext;
     }
@@ -119,6 +158,13 @@
     @Override
     public void getWords(final WordComposer codes, final WordCallback callback,
             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();
         mNextLettersFrequencies = nextLettersFrequencies;
         if (mCodes.length < mInputLength) mCodes = new int[mInputLength][];
@@ -135,6 +181,11 @@
 
     @Override
     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());
         return freq > -1;
     }
@@ -277,6 +328,24 @@
         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) {
         if (c < BASE_CHARS.length) {
             c = BASE_CHARS[c];
diff --git a/java/src/com/android/inputmethod/latin/UserDictionary.java b/java/src/com/android/inputmethod/latin/UserDictionary.java
index 4b98eac..e8ca33a 100644
--- a/java/src/com/android/inputmethod/latin/UserDictionary.java
+++ b/java/src/com/android/inputmethod/latin/UserDictionary.java
@@ -16,16 +16,11 @@
 
 package com.android.inputmethod.latin;
 
-import java.util.ArrayList;
-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,8 +35,6 @@
     private static final int INDEX_FREQUENCY = 2;
     
     private ContentObserver mObserver;
-    
-    private boolean mRequiresReload;
     private String mLocale;
 
     public UserDictionary(Context context, String locale) {
@@ -54,26 +47,27 @@
         cres.registerContentObserver(Words.CONTENT_URI, true, mObserver = new ContentObserver(null) {
             @Override
             public void onChange(boolean self) {
-                mRequiresReload = true;
+                setRequiresReload(true);
             }
         });
 
         loadDictionary();
     }
-    
+
     public synchronized void close() {
         if (mObserver != null) {
             getContext().getContentResolver().unregisterContentObserver(mObserver);
             mObserver = null;
         }
+        super.close();
     }
-    
-    private synchronized void loadDictionary() {
+
+    @Override
+    public void loadDictionaryAsync() {
         Cursor cursor = getContext().getContentResolver()
                 .query(Words.CONTENT_URI, PROJECTION, "(locale IS NULL) or (locale=?)", 
                         new String[] { mLocale }, null);
         addWords(cursor);
-        mRequiresReload = false;
     }
 
     /**
@@ -86,7 +80,8 @@
      */
     @Override
     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.
         if (word.length() >= getMaxWordLength()) return;
 
@@ -101,19 +96,17 @@
 
         getContext().getContentResolver().insert(Words.CONTENT_URI, values);
         // In case the above does a synchronous callback of the change observer
-        mRequiresReload = false;
+        setRequiresReload(false);
     }
 
     @Override
     public synchronized void getWords(final WordComposer codes, final WordCallback callback,
             int[] nextLettersFrequencies) {
-        if (mRequiresReload) loadDictionary();
         super.getWords(codes, callback, nextLettersFrequencies);
     }
 
     @Override
     public synchronized boolean isValidWord(CharSequence word) {
-        if (mRequiresReload) loadDictionary();
         return super.isValidWord(word);
     }