Full text search: building full index for all contacts

Bug: 2078420
Change-Id: Ief6db4f4ecc0b6e5adb37a0654a72383099dc138
diff --git a/src/com/android/providers/contacts/ContactsDatabaseHelper.java b/src/com/android/providers/contacts/ContactsDatabaseHelper.java
index 2007aa3..4b9900a 100644
--- a/src/com/android/providers/contacts/ContactsDatabaseHelper.java
+++ b/src/com/android/providers/contacts/ContactsDatabaseHelper.java
@@ -1093,7 +1093,7 @@
         setProperty(db, ContactDirectoryManager.PROPERTY_DIRECTORY_SCAN_COMPLETE, "0");
     }
 
-    private void createSearchIndexTable(SQLiteDatabase db) {
+    public void createSearchIndexTable(SQLiteDatabase db) {
         db.execSQL("DROP TABLE IF EXISTS " + Tables.SEARCH_INDEX);
         db.execSQL("CREATE VIRTUAL TABLE " + Tables.SEARCH_INDEX
                 + " USING FTS4 ("
diff --git a/src/com/android/providers/contacts/ContactsProvider2.java b/src/com/android/providers/contacts/ContactsProvider2.java
index af0b3ee..7422b9b 100644
--- a/src/com/android/providers/contacts/ContactsProvider2.java
+++ b/src/com/android/providers/contacts/ContactsProvider2.java
@@ -153,9 +153,10 @@
     private static final int BACKGROUND_TASK_UPDATE_ACCOUNTS = 3;
     private static final int BACKGROUND_TASK_UPDATE_LOCALE = 4;
     private static final int BACKGROUND_TASK_UPGRADE_AGGREGATION_ALGORITHM = 5;
-    private static final int BACKGROUND_TASK_UPDATE_PROVIDER_STATUS = 6;
-    private static final int BACKGROUND_TASK_UPDATE_DIRECTORIES = 7;
-    private static final int BACKGROUND_TASK_CHANGE_LOCALE = 8;
+    private static final int BACKGROUND_TASK_UPDATE_SEARCH_INDEX = 6;
+    private static final int BACKGROUND_TASK_UPDATE_PROVIDER_STATUS = 7;
+    private static final int BACKGROUND_TASK_UPDATE_DIRECTORIES = 8;
+    private static final int BACKGROUND_TASK_CHANGE_LOCALE = 9;
 
     /** Default for the maximum number of returned aggregation suggestions. */
     private static final int DEFAULT_MAX_SUGGESTIONS = 5;
@@ -1045,6 +1046,7 @@
         scheduleBackgroundTask(BACKGROUND_TASK_UPDATE_ACCOUNTS);
         scheduleBackgroundTask(BACKGROUND_TASK_UPDATE_LOCALE);
         scheduleBackgroundTask(BACKGROUND_TASK_UPGRADE_AGGREGATION_ALGORITHM);
+        scheduleBackgroundTask(BACKGROUND_TASK_UPDATE_SEARCH_INDEX);
         scheduleBackgroundTask(BACKGROUND_TASK_UPDATE_PROVIDER_STATUS);
         scheduleBackgroundTask(BACKGROUND_TASK_OPEN_WRITE_ACCESS);
 
@@ -1165,6 +1167,11 @@
                 break;
             }
 
+            case BACKGROUND_TASK_UPDATE_SEARCH_INDEX: {
+                updateSearchIndexInBackground();
+                break;
+            }
+
             case BACKGROUND_TASK_UPDATE_PROVIDER_STATUS: {
                 updateProviderStatus();
                 break;
@@ -1236,6 +1243,10 @@
         updateLocaleInBackground();
     }
 
+    protected void updateSearchIndexInBackground() {
+        mSearchIndexManager.updateIndex();
+    }
+
     protected void updateDirectoriesInBackground(boolean rescan) {
         mContactDirectoryManager.scanAllPackages(rescan);
     }
diff --git a/src/com/android/providers/contacts/SearchIndexManager.java b/src/com/android/providers/contacts/SearchIndexManager.java
index 8e238bc..80082a6 100644
--- a/src/com/android/providers/contacts/SearchIndexManager.java
+++ b/src/com/android/providers/contacts/SearchIndexManager.java
@@ -23,7 +23,11 @@
 import android.content.ContentValues;
 import android.database.Cursor;
 import android.database.sqlite.SQLiteDatabase;
+import android.os.SystemClock;
+import android.provider.ContactsContract.Contacts;
 import android.provider.ContactsContract.Data;
+import android.provider.ContactsContract.ProviderStatus;
+import android.provider.ContactsContract.RawContacts;
 import android.text.TextUtils;
 import android.util.Log;
 
@@ -34,9 +38,11 @@
  * Maintains a search index for comprehensive contact search.
  */
 public class SearchIndexManager {
-
     private static final String TAG = "ContactsFTS";
 
+    private static final String PROPERTY_SEARCH_INDEX_VERSION = "search_index";
+    private static final int SEARCH_INDEX_VERSION = 1;
+
     private static final class ContactIndexQuery {
         public static final String[] COLUMNS = {
                 Data.CONTACT_ID,
@@ -172,6 +178,40 @@
         mDbHelper = (ContactsDatabaseHelper) mContactsProvider.getDatabaseHelper();
     }
 
+    public void updateIndex() {
+        if (getSearchIndexVersion() == SEARCH_INDEX_VERSION) {
+            return;
+        }
+        SQLiteDatabase db = mDbHelper.getWritableDatabase();
+        db.beginTransaction();
+        try {
+            if (getSearchIndexVersion() != SEARCH_INDEX_VERSION) {
+                rebuildIndex(db);
+                setSearchIndexVersion(SEARCH_INDEX_VERSION);
+                db.setTransactionSuccessful();
+            }
+        } finally {
+            db.endTransaction();
+        }
+    }
+
+    private void rebuildIndex(SQLiteDatabase db) {
+        mContactsProvider.setProviderStatus(ProviderStatus.STATUS_UPGRADING);
+        long start = SystemClock.currentThreadTimeMillis();
+        int count = 0;
+        try {
+            mDbHelper.createSearchIndexTable(db);
+            count = buildIndex(db, RawContacts.CONTACT_ID + " IN "
+                    + "(SELECT " + Contacts._ID + " FROM " + Tables.DEFAULT_DIRECTORY + ")", false);
+        } finally {
+            mContactsProvider.setProviderStatus(ProviderStatus.STATUS_NORMAL);
+
+            long end = SystemClock.currentThreadTimeMillis();
+            Log.i(TAG, "Rebuild contact search index in " + (end - start) + "ms, "
+                    + count + " contacts");
+        }
+    }
+
     public void updateIndexForRawContacts(Set<Long> rawContactIds) {
         mSb.setLength(0);
         mSb.append(Data.RAW_CONTACT_ID + " IN (");
@@ -181,9 +221,13 @@
         mSb.setLength(mSb.length() - 1);
         mSb.append(')');
 
-        SQLiteDatabase db = mDbHelper.getWritableDatabase();
+        buildIndex(mDbHelper.getWritableDatabase(), mSb.toString(), true);
+    }
+
+    private int buildIndex(SQLiteDatabase db, String selection, boolean replace) {
+        int count = 0;
         Cursor cursor = db.query(Tables.DATA_JOIN_MIMETYPE_RAW_CONTACTS,
-                ContactIndexQuery.COLUMNS, mSb.toString(), null, null, null,
+                ContactIndexQuery.COLUMNS, selection, null, null, null,
                 Data.CONTACT_ID + ", " + DataColumns.MIMETYPE_ID + ", " + Data.IS_SUPER_PRIMARY
                         + ", " + DataColumns.CONCRETE_ID);
         mIndexBuilder.setCursor(cursor);
@@ -194,7 +238,8 @@
                 long contactId = cursor.getLong(0);
                 if (contactId != currentContactId) {
                     if (currentContactId != -1) {
-                        saveContactIndex(db, currentContactId, mIndexBuilder);
+                        saveContactIndex(db, currentContactId, mIndexBuilder, replace);
+                        count++;
                     }
                     currentContactId = contactId;
                     mIndexBuilder.reset();
@@ -207,25 +252,38 @@
                 }
             }
             if (currentContactId != -1) {
-                saveContactIndex(db, currentContactId, mIndexBuilder);
+                saveContactIndex(db, currentContactId, mIndexBuilder, replace);
+                count++;
             }
         } finally {
             cursor.close();
         }
+        return count;
     }
 
-    private void saveContactIndex(SQLiteDatabase db, long contactId, IndexBuilder builder) {
-        Log.d(TAG, "INDEX: " + contactId + ": " + builder.toString());
-
+    private void saveContactIndex(
+            SQLiteDatabase db, long contactId, IndexBuilder builder, boolean replace) {
         mValues.clear();
         mValues.put(SearchIndexColumns.CONTENT, builder.getContent());
         mValues.put(SearchIndexColumns.TOKENS, builder.getTokens());
-        mSelectionArgs1[0] = String.valueOf(contactId);
-        int count = db.update(Tables.SEARCH_INDEX, mValues,
-                SearchIndexColumns.CONTACT_ID + "=CAST(? AS int)", mSelectionArgs1);
-        if (count == 0) {
+        if (replace) {
+            mSelectionArgs1[0] = String.valueOf(contactId);
+            int count = db.update(Tables.SEARCH_INDEX, mValues,
+                    SearchIndexColumns.CONTACT_ID + "=CAST(? AS int)", mSelectionArgs1);
+            if (count == 0) {
+                mValues.put(SearchIndexColumns.CONTACT_ID, contactId);
+                db.insert(Tables.SEARCH_INDEX, null, mValues);
+            }
+        } else {
             mValues.put(SearchIndexColumns.CONTACT_ID, contactId);
             db.insert(Tables.SEARCH_INDEX, null, mValues);
         }
     }
+    private int getSearchIndexVersion() {
+        return Integer.parseInt(mDbHelper.getProperty(PROPERTY_SEARCH_INDEX_VERSION, "0"));
+    }
+
+    private void setSearchIndexVersion(int version) {
+        mDbHelper.setProperty(PROPERTY_SEARCH_INDEX_VERSION, String.valueOf(version));
+    }
 }