Check whether contacts have changed using hashCode().

Bug: 13755213
Change-Id: Ie2f7b7f9dc8bd3fce395618877d9f234287dcb21
diff --git a/java/src/com/android/inputmethod/latin/ContactsBinaryDictionary.java b/java/src/com/android/inputmethod/latin/ContactsBinaryDictionary.java
index 9bc01a2..e04fcda 100644
--- a/java/src/com/android/inputmethod/latin/ContactsBinaryDictionary.java
+++ b/java/src/com/android/inputmethod/latin/ContactsBinaryDictionary.java
@@ -31,9 +31,12 @@
 
 import com.android.inputmethod.annotations.UsedForTesting;
 import com.android.inputmethod.latin.personalization.AccountUtils;
+import com.android.inputmethod.latin.utils.CollectionUtils;
+import com.android.inputmethod.latin.utils.ExecutorUtils;
 import com.android.inputmethod.latin.utils.StringUtils;
 
 import java.io.File;
+import java.util.ArrayList;
 import java.util.List;
 import java.util.Locale;
 
@@ -60,7 +63,10 @@
     private static final int INDEX_NAME = 1;
 
     /** The number of contacts in the most recent dictionary rebuild. */
-    static private int sContactCountAtLastRebuild = 0;
+    private int mContactCountAtLastRebuild = 0;
+
+    /** The hash code of ArrayList of contacts names in the most recent dictionary rebuild. */
+    private int mHashCodeAtLastRebuild = 0;
 
     private ContentObserver mObserver;
 
@@ -96,7 +102,14 @@
                 new ContentObserver(null) {
                     @Override
                     public void onChange(boolean self) {
-                        setNeedsToReload();
+                        ExecutorUtils.getExecutor("Check Contacts").execute(new Runnable() {
+                            @Override
+                            public void run() {
+                                if (haveContentsChanged()) {
+                                    setNeedsToRecreate();
+                                }
+                            }
+                        });
                     }
                 });
     }
@@ -143,7 +156,7 @@
                 return;
             }
             if (cursor.moveToFirst()) {
-                sContactCountAtLastRebuild = getContactCount();
+                mContactCountAtLastRebuild = getContactCount();
                 addWordsLocked(cursor);
             }
         } catch (final SQLiteException e) {
@@ -167,9 +180,11 @@
 
     private void addWordsLocked(final Cursor cursor) {
         int count = 0;
+        final ArrayList<String> names = CollectionUtils.newArrayList();
         while (!cursor.isAfterLast() && count < MAX_CONTACT_COUNT) {
             String name = cursor.getString(INDEX_NAME);
             if (isValidName(name)) {
+                names.add(name);
                 addNameLocked(name);
                 ++count;
             } else {
@@ -179,6 +194,7 @@
             }
             cursor.moveToNext();
         }
+        mHashCodeAtLastRebuild = names.hashCode();
     }
 
     private int getContactCount() {
@@ -258,8 +274,7 @@
         return end;
     }
 
-    @Override
-    protected boolean haveContentsChanged() {
+    private boolean haveContentsChanged() {
         final long startTime = SystemClock.uptimeMillis();
         final int contactCount = getContactCount();
         if (contactCount > MAX_CONTACT_COUNT) {
@@ -268,9 +283,9 @@
             // TODO: Sort and check only the MAX_CONTACT_COUNT most recent contacts?
             return false;
         }
-        if (contactCount != sContactCountAtLastRebuild) {
+        if (contactCount != mContactCountAtLastRebuild) {
             if (DEBUG) {
-                Log.d(TAG, "Contact count changed: " + sContactCountAtLastRebuild + " to "
+                Log.d(TAG, "Contact count changed: " + mContactCountAtLastRebuild + " to "
                         + contactCount);
             }
             return true;
@@ -283,20 +298,20 @@
         if (null == cursor) {
             return false;
         }
+        final ArrayList<String> names = CollectionUtils.newArrayList();
         try {
             if (cursor.moveToFirst()) {
                 while (!cursor.isAfterLast()) {
                     String name = cursor.getString(INDEX_NAME);
-                    if (isValidName(name) && !isNameInDictionaryLocked(name)) {
-                        if (DEBUG) {
-                            Log.d(TAG, "Contact name missing: " + name + " (runtime = "
-                                    + (SystemClock.uptimeMillis() - startTime) + " ms)");
-                        }
-                        return true;
+                    if (isValidName(name)) {
+                        names.add(name);
                     }
                     cursor.moveToNext();
                 }
             }
+            if (names.hashCode() != mHashCodeAtLastRebuild) {
+                return true;
+            }
         } finally {
             cursor.close();
         }
@@ -313,33 +328,4 @@
         }
         return false;
     }
-
-    /**
-     * Checks if the words in a name are in the current binary dictionary.
-     */
-    private boolean isNameInDictionaryLocked(final String name) {
-        int len = StringUtils.codePointCount(name);
-        String prevWord = null;
-        for (int i = 0; i < len; i++) {
-            if (Character.isLetter(name.codePointAt(i))) {
-                int end = getWordEndPosition(name, len, i);
-                String word = name.substring(i, end);
-                i = end - 1;
-                final int wordLen = StringUtils.codePointCount(word);
-                if (wordLen < MAX_WORD_LENGTH && wordLen > 1) {
-                    if (!TextUtils.isEmpty(prevWord) && mUseFirstLastBigrams) {
-                        if (!isValidBigramLocked(prevWord, word)) {
-                            return false;
-                        }
-                    } else {
-                        if (!isValidWordLocked(word)) {
-                            return false;
-                        }
-                    }
-                    prevWord = word;
-                }
-            }
-        }
-        return true;
-    }
 }
diff --git a/java/src/com/android/inputmethod/latin/ExpandableBinaryDictionary.java b/java/src/com/android/inputmethod/latin/ExpandableBinaryDictionary.java
index c825ca4..e253782 100644
--- a/java/src/com/android/inputmethod/latin/ExpandableBinaryDictionary.java
+++ b/java/src/com/android/inputmethod/latin/ExpandableBinaryDictionary.java
@@ -92,8 +92,8 @@
     /** Indicates whether a task for reloading the dictionary has been scheduled. */
     private final AtomicBoolean mIsReloading;
 
-    /** Indicates whether the current dictionary needs to be reloaded. */
-    private boolean mNeedsToReload;
+    /** Indicates whether the current dictionary needs to be recreated. */
+    private boolean mNeedsToRecreate;
 
     private final ReentrantReadWriteLock mLock;
 
@@ -107,13 +107,6 @@
      */
     protected abstract void loadInitialContentsLocked();
 
-    /**
-     * Indicates that the source dictionary contents have changed and a rebuild of the binary file
-     * is required. If it returns false, the next reload will only read the current binary
-     * dictionary from file.
-     */
-    protected abstract boolean haveContentsChanged();
-
     private boolean matchesExpectedBinaryDictFormatVersionForThisType(final int formatVersion) {
         return formatVersion == FormatSpec.VERSION4;
     }
@@ -147,7 +140,7 @@
         mDictFile = getDictFile(context, dictName, dictFile);
         mBinaryDictionary = null;
         mIsReloading = new AtomicBoolean();
-        mNeedsToReload = false;
+        mNeedsToRecreate = false;
         mLock = new ReentrantReadWriteLock();
     }
 
@@ -486,11 +479,11 @@
     }
 
     /**
-     * Marks that the dictionary needs to be reloaded.
+     * Marks that the dictionary needs to be recreated.
      *
      */
-    protected void setNeedsToReload() {
-        mNeedsToReload = true;
+    protected void setNeedsToRecreate() {
+        mNeedsToRecreate = true;
     }
 
     /**
@@ -508,7 +501,7 @@
      * Returns whether a dictionary reload is required.
      */
     private boolean isReloadRequired() {
-        return mBinaryDictionary == null || mNeedsToReload;
+        return mBinaryDictionary == null || mNeedsToRecreate;
     }
 
     /**
@@ -520,8 +513,7 @@
                 @Override
                 public void run() {
                     try {
-                        // TODO: Quit checking contents in ExpandableBinaryDictionary.
-                        if (!mDictFile.exists() || (mNeedsToReload && haveContentsChanged())) {
+                        if (!mDictFile.exists() || mNeedsToRecreate) {
                             // If the dictionary file does not exist or contents have been updated,
                             // generate a new one.
                             createNewDictionaryLocked();
@@ -533,12 +525,12 @@
                                     && matchesExpectedBinaryDictFormatVersionForThisType(
                                             mBinaryDictionary.getFormatVersion()))) {
                                 // Binary dictionary or its format version is not valid. Regenerate
-                                // the dictionary file. writeBinaryDictionary will remove the
+                                // the dictionary file. createNewDictionaryLocked will remove the
                                 // existing files if appropriate.
                                 createNewDictionaryLocked();
                             }
                         }
-                        mNeedsToReload = false;
+                        mNeedsToRecreate = false;
                     } finally {
                         mIsReloading.set(false);
                     }
diff --git a/java/src/com/android/inputmethod/latin/UserBinaryDictionary.java b/java/src/com/android/inputmethod/latin/UserBinaryDictionary.java
index e1cda69..c8ffbe4 100644
--- a/java/src/com/android/inputmethod/latin/UserBinaryDictionary.java
+++ b/java/src/com/android/inputmethod/latin/UserBinaryDictionary.java
@@ -98,7 +98,7 @@
             // devices. On older versions of the platform, the hook above will be called instead.
             @Override
             public void onChange(final boolean self, final Uri uri) {
-                setNeedsToReload();
+                setNeedsToRecreate();
             }
         };
         cres.registerContentObserver(Words.CONTENT_URI, true, mObserver);
@@ -272,9 +272,4 @@
             }
         }
     }
-
-    @Override
-    protected boolean haveContentsChanged() {
-        return true;
-    }
 }
diff --git a/java/src/com/android/inputmethod/latin/personalization/DecayingExpandableBinaryDictionaryBase.java b/java/src/com/android/inputmethod/latin/personalization/DecayingExpandableBinaryDictionaryBase.java
index 38c28a7..06bdba0 100644
--- a/java/src/com/android/inputmethod/latin/personalization/DecayingExpandableBinaryDictionaryBase.java
+++ b/java/src/com/android/inputmethod/latin/personalization/DecayingExpandableBinaryDictionaryBase.java
@@ -74,11 +74,6 @@
     }
 
     @Override
-    protected boolean haveContentsChanged() {
-        return false;
-    }
-
-    @Override
     protected void loadInitialContentsLocked() {
         // No initial contents.
     }