Fix contact cache rebuilding

Don't toss and rebuild the ContactInfoCache on every contact change.
With this change, we mark a contact as stale and the next time we're
asked for the contact, we asynchronously update the contact in the
background. Helps speedup bug 2167799.

Change-Id: Idc1271776cddd43fedcd7f31608a3302c8861813
diff --git a/src/com/android/mms/data/Contact.java b/src/com/android/mms/data/Contact.java
index 054e2ac..774c6c9 100644
--- a/src/com/android/mms/data/Contact.java
+++ b/src/com/android/mms/data/Contact.java
@@ -21,7 +21,6 @@
 import com.android.mms.ui.MessageUtils;
 import com.android.mms.util.ContactInfoCache;
 import com.android.mms.util.TaskStack;
-import com.android.mms.util.AddressUtils;
 import com.android.mms.LogTag;
 
 public class Contact {
@@ -30,15 +29,15 @@
 
     private static final TaskStack sTaskStack = new TaskStack();
 
-    private static final ContentObserver sContactsObserver = new ContentObserver(new Handler()) {
-        @Override
-        public void onChange(boolean selfUpdate) {
-            if (Log.isLoggable(LogTag.APP, Log.VERBOSE)) {
-                log("contact changed, invalidate cache");
-            }
-            invalidateCache();
-        }
-    };
+//    private static final ContentObserver sContactsObserver = new ContentObserver(new Handler()) {
+//        @Override
+//        public void onChange(boolean selfUpdate) {
+//            if (Log.isLoggable(LogTag.APP, Log.VERBOSE)) {
+//                log("contact changed, invalidate cache");
+//            }
+//            invalidateCache();
+//        }
+//    };
 
     private static final ContentObserver sPresenceObserver = new ContentObserver(new Handler()) {
         @Override
@@ -60,6 +59,7 @@
     private int mPresenceResId;      // TODO: make this a state instead of a res ID
     private String mPresenceText;
     private BitmapDrawable mAvatar;
+    private boolean mIsStale;
 
     @Override
     public synchronized String toString() {
@@ -78,6 +78,7 @@
         mLabel = "";
         mPersonId = 0;
         mPresenceResId = 0;
+        mIsStale = true;
     }
 
     private static void logWithTrace(String msg, Object... format) {
@@ -114,6 +115,8 @@
         if (contact == null) {
             contact = new Contact(number);
             Cache.put(contact);
+        }
+        if (contact.mIsStale) {
             asyncUpdateContact(contact, canBlock);
         }
         return contact;
@@ -123,17 +126,21 @@
         if (Log.isLoggable(LogTag.APP, Log.VERBOSE)) {
             log("invalidateCache");
         }
-        
+
         // force invalidate the contact info cache, so we will query for fresh info again.
         // This is so we can get fresh presence info again on the screen, since the presence
         // info changes pretty quickly, and we can't get change notifications when presence is
         // updated in the ContactsProvider.
         ContactInfoCache.getInstance().invalidateCache();
 
-        // Queue updates for the whole cache.
-        sTaskStack.clear();
-
-        asyncUpdateContacts(Cache.getContacts(), false);
+        // While invalidating our local Cache doesn't remove the contacts, it will mark them
+        // stale so the next time we're asked for a particular contact, we'll return that
+        // stale contact and at the same time, fire off an asyncUpdateContact to update
+        // that contact's info in the background. UI elements using the contact typically
+        // call addListener() so they immediately get notified when the contact has been
+        // updated with the latest info. They redraw themselves when we call the
+        // listener's onUpdate().
+        Cache.invalidate();
     }
 
     private static String emptyIfNull(String s) {
@@ -184,7 +191,7 @@
         }
         return false;
     }
-    
+
     private static void asyncUpdateContact(final Contact c, boolean canBlock) {
         if (c == null) {
             return;
@@ -207,30 +214,11 @@
         }
     }
 
-    private static void asyncUpdateContacts(final List<Contact> contacts, boolean canBlock) {
-        Runnable r = new Runnable() {
-            public void run() {
-                if (Log.isLoggable(LogTag.APP, Log.VERBOSE)) {
-                    log("asyncUpdateContacts...");
-                }
-
-                for (Contact c : contacts) {
-                    updateContact(c);
-                }
-            }
-        };
-
-        if (canBlock) {
-            r.run();
-        } else {
-            sTaskStack.push(r);
-        }
-    }
-
     private static void updateContact(final Contact c) {
         if (c == null) {
             return;
         }
+        c.mIsStale = false;
 
         // Check to see if this is the local ("me") number.
         if (handleLocalNumber(c)) {
@@ -462,6 +450,16 @@
                 return new ArrayList<Contact>(sInstance.mCache);
             }
         }
+
+        static void invalidate() {
+            // Don't remove the contacts. Just mark them stale so we'll update their
+            // info, particularly their presence.
+            synchronized (sInstance) {
+                for (Contact c : sInstance.mCache) {
+                    c.mIsStale = true;
+                }
+            }
+        }
     }
 
     private static void log(String msg) {
diff --git a/src/com/android/mms/util/ContactInfoCache.java b/src/com/android/mms/util/ContactInfoCache.java
index 77320a5..58dc121 100644
--- a/src/com/android/mms/util/ContactInfoCache.java
+++ b/src/com/android/mms/util/ContactInfoCache.java
@@ -20,17 +20,13 @@
 import com.android.mms.ui.MessageUtils;
 import com.google.android.mms.util.SqliteWrapper;
 
-import android.content.ContentResolver;
 import android.content.ContentUris;
 import android.content.Context;
-import android.database.ContentObserver;
 import android.database.Cursor;
 import android.graphics.Bitmap;
 import android.graphics.BitmapFactory;
 import android.graphics.drawable.BitmapDrawable;
 import android.net.Uri;
-import android.os.Handler;
-import android.provider.ContactsContract;
 import android.provider.ContactsContract.Contacts;
 import android.provider.ContactsContract.Data;
 import android.provider.ContactsContract.Presence;
@@ -43,9 +39,7 @@
 
 import java.io.IOException;
 import java.io.InputStream;
-import java.util.ArrayList;
 import java.util.HashMap;
-import java.util.List;
 import java.util.Map;
 import java.util.regex.Matcher;
 
@@ -62,7 +56,6 @@
 
     private static final boolean LOCAL_DEBUG = false;
 
-    private static final long REBUILD_DELAY = 5000; // 5 seconds
     private static final String SEPARATOR = ";";
 
     // query params for caller id lookup
@@ -182,19 +175,6 @@
 
     private ContactInfoCache(Context context) {
         mContext = context;
-
-        ContentResolver resolver = context.getContentResolver();
-        resolver.registerContentObserver(Data.CONTENT_URI, true,
-                new ContentObserver(new Handler()) {
-                    @Override
-                    public void onChange(boolean selfUpdate) {
-                        synchronized (mCacheRebuildLock) {
-                            mPhoneCacheInvalidated = true;
-                            mEmailCacheInvalidated = true;
-                            startCacheRebuilder();
-                        }
-                    }
-                });
     }
 
     /**
@@ -524,100 +504,6 @@
         return entry;
     }
 
-    /**
-     * Start the background cache rebuilding thread if there is not one yet.
-     */
-    private void startCacheRebuilder() {
-        if (mCacheRebuilder == null) {
-            mCacheRebuilder = new Thread(new Runnable() {
-                    public void run() {
-                        rebuildCache();
-                    }
-            });
-            mCacheRebuilder.start();
-        }
-    }
-
-    /**
-     * Get the list of phone/email candidates for the cache rebuilding. This is
-     * a snapshot of the keys in the cache.
-     */
-    private void getRebuildList(List<String> phones, List<String> emails) {
-        synchronized (mCache) {
-            for (String name : mCache.keySet()) {
-                if (Mms.isEmailAddress(name)) {
-                    if (emails != null) {
-                        emails.add(name);
-                    }
-                } else {
-                    if (phones != null) {
-                        phones.add(name);
-                    }
-                }
-            }
-        }
-    }
-
-    /**
-     * The actual work of rebuilding the cache, i.e. syncing our cache with
-     * the contacts database.
-     */
-    private void rebuildCache() {
-        List<String> phones;
-        List<String> emails;
-
-        for (;;) {
-            // simulate the Nagle's algorithm:
-            // delay for a while to prevent from getting too busy, when, say,
-            // there is a big contacts sync going on
-            try {
-                Thread.sleep(REBUILD_DELAY);
-            } catch (InterruptedException ie) {
-            }
-
-            phones = null;
-            emails = null;
-            synchronized (mCacheRebuildLock) {
-                // if nothing changed during our sync, stop this thread
-                // otherwise, just keep working on it.
-                if (!(mPhoneCacheInvalidated || mEmailCacheInvalidated)) {
-                    mCacheRebuilder = null;
-                    return;
-                }
-                if (mPhoneCacheInvalidated) {
-                    phones = new ArrayList<String>();
-                    mPhoneCacheInvalidated = false;
-                }
-                if (mEmailCacheInvalidated) {
-                    emails = new ArrayList<String>();
-                    mEmailCacheInvalidated = false;
-                }
-            }
-            // retrieve the list of phone/email candidates for syncing
-            // which is a snapshot of the keys in the cache
-            getRebuildList(phones, emails);
-            // now sync
-            if (phones != null) {
-                if (LOCAL_DEBUG) log("rebuild cache for phone numbers...");
-                for (String phone : phones) {
-                    CacheEntry entry = queryContactInfoByNumber(phone);
-                    synchronized (mCache) {
-                        mCache.put(phone, entry);
-                    }
-                }
-            }
-            if (emails != null) {
-                if (LOCAL_DEBUG) log("rebuild cache for emails...");
-                for (String email : emails) {
-                    CacheEntry entry = queryEmailDisplayName(email);
-                    synchronized (mCache) {
-                        mCache.put(email, entry);
-                    }
-                }
-            }
-        }
-    }
-
     private void log(String msg) {
         Log.d(TAG, "[ContactInfoCache] " + msg);
     }