Using content observer to update contact filter list

Bug: 3194334
Change-Id: I90aef198739622a4d4ab606fa94f5fd4da6b72f3
diff --git a/src/com/android/contacts/list/ContactListFilterController.java b/src/com/android/contacts/list/ContactListFilterController.java
index b9e3c59..a8c780b 100644
--- a/src/com/android/contacts/list/ContactListFilterController.java
+++ b/src/com/android/contacts/list/ContactListFilterController.java
@@ -25,9 +25,8 @@
 import android.content.SharedPreferences;
 import android.content.res.TypedArray;
 import android.os.Bundle;
-import android.os.Handler;
-import android.os.Message;
 import android.preference.PreferenceManager;
+import android.text.TextUtils;
 import android.util.SparseArray;
 import android.view.LayoutInflater;
 import android.view.View;
@@ -53,37 +52,19 @@
         void onContactListFilterCustomizationRequest();
     }
 
-    private static final int MESSAGE_REFRESH_FILTERS = 0;
-
-    /**
-     * The delay before the contact filter list is refreshed. This is needed because
-     * during contact sync we will get lots of notifications in rapid succession. This
-     * delay will prevent the slowly changing list of filters from reloading too often.
-     */
-    private static final int FILTER_SPINNER_REFRESH_DELAY_MILLIS = 5000;
-
     private Context mContext;
     private LoaderManager mLoaderManager;
     private boolean mEnabled = true;
     private List<ContactListFilterListener> mListeners = new ArrayList<ContactListFilterListener>();
     private ListPopupWindow mPopup;
     private int mPopupWidth = -1;
+    private List<ContactListFilter> mCachedFilters;
     private SparseArray<ContactListFilter> mFilters;
     private int mNextFilterId = 1;
     private View mAnchor;
     private FilterListAdapter mFilterListAdapter;
     private ContactListFilter mFilter;
     private boolean mFiltersLoaded;
-    private final Handler mHandler = new Handler() {
-
-        @Override
-        public void handleMessage(Message msg) {
-            if (msg.what == MESSAGE_REFRESH_FILTERS) {
-                loadFilters();
-            }
-        }
-    };
-
     private int mAccountCount;
 
     public ContactListFilterController(Activity activity) {
@@ -121,22 +102,16 @@
     }
 
     public void startLoading() {
-        // Set the "ready" flag right away - we only want to start the loader once
-        mFiltersLoaded = false;
         if (mFilter == null) {
             mFilter = ContactListFilter.restoreFromPreferences(getSharedPreferences());
         }
-        loadFilters();
+        mLoaderManager.initLoader(R.id.contact_list_filter_loader, null, this);
     }
 
     private SharedPreferences getSharedPreferences() {
         return PreferenceManager.getDefaultSharedPreferences(mContext);
     }
 
-    private void loadFilters() {
-        mLoaderManager.restartLoader(R.id.contact_list_filter_loader, null, this);
-    }
-
     @Override
     public ContactListFilterLoader onCreateLoader(int id, Bundle args) {
         return new ContactListFilterLoader(mContext);
@@ -145,6 +120,34 @@
     @Override
     public void onLoadFinished(
             Loader<List<ContactListFilter>> loader, List<ContactListFilter> filters) {
+        int count = filters.size();
+        if (mCachedFilters != null && mCachedFilters.size() == count) {
+            boolean changed = false;
+            for (int i = 0; i < filters.size(); i++) {
+                ContactListFilter filter1 = mCachedFilters.get(i);
+                ContactListFilter filter2 = filters.get(i);
+                if (!filter1.equals(filter2)) {
+                    changed = true;
+                    break;
+                }
+
+                // Group title is intentionally not included in the "equals" algorithm for
+                // ContactListFilter, because we want stability of filter identity
+                // across label changes.  However, here we do care about the label changes.
+                if (filter1.filterType == ContactListFilter.FILTER_TYPE_GROUP &&
+                        !TextUtils.equals(filter1.title, filter2.title)) {
+                    changed = true;
+                    break;
+                }
+            }
+
+            if (!changed) {
+                return;
+            }
+        }
+
+        mCachedFilters = filters;
+
         if (mFilters == null) {
             mFilters = new SparseArray<ContactListFilter>(filters.size());
         } else {
@@ -154,7 +157,6 @@
         boolean filterValid = mFilter != null && !mFilter.isValidationRequired();
 
         mAccountCount = 0;
-        int count = filters.size();
         for (int index = 0; index < count; index++) {
             if (filters.get(index).filterType == ContactListFilter.FILTER_TYPE_ACCOUNT) {
                 mAccountCount++;
@@ -210,19 +212,11 @@
             mFilterListAdapter.notifyDataSetChanged();
         }
 
-        if (filterChanged) {
-            mFiltersLoaded = true;
-            notifyContactListFilterChanged();
-        } else if (!mFiltersLoaded) {
-            mFiltersLoaded = true;
-            notifyContacListFiltersLoaded();
-        }
-    }
+        mFiltersLoaded = true;
+        notifyContacListFiltersLoaded();
 
-    public void postDelayedRefresh() {
-        if (!mHandler.hasMessages(MESSAGE_REFRESH_FILTERS)) {
-            mHandler.sendEmptyMessageDelayed(
-                    MESSAGE_REFRESH_FILTERS, FILTER_SPINNER_REFRESH_DELAY_MILLIS);
+        if (filterChanged) {
+            notifyContactListFilterChanged();
         }
     }
 
diff --git a/src/com/android/contacts/list/ContactListFilterLoader.java b/src/com/android/contacts/list/ContactListFilterLoader.java
index c0c2fac..adb7c7f 100644
--- a/src/com/android/contacts/list/ContactListFilterLoader.java
+++ b/src/com/android/contacts/list/ContactListFilterLoader.java
@@ -16,8 +16,8 @@
 
 package com.android.contacts.list;
 
-import com.android.contacts.model.AccountTypes;
 import com.android.contacts.model.AccountType;
+import com.android.contacts.model.AccountTypes;
 
 import android.accounts.Account;
 import android.content.AsyncTaskLoader;
@@ -25,6 +25,7 @@
 import android.content.Context;
 import android.database.Cursor;
 import android.graphics.drawable.Drawable;
+import android.provider.ContactsContract.Contacts;
 import android.provider.ContactsContract.Groups;
 
 import java.util.ArrayList;
@@ -57,6 +58,10 @@
                 Groups.DELETED + "=0 AND " + Groups.FAVORITES + "=0";
     }
 
+    private boolean mStopped;
+    private ForceLoadContentObserver mObserver;
+    private ArrayList<ContactListFilter> mResults;
+
     public ContactListFilterLoader(Context context) {
         super(context);
     }
@@ -111,22 +116,53 @@
 
         Collections.sort(results);
 
+        mResults = results;
         return results;
     }
 
+    /* Runs on the UI thread */
+    @Override
+    public void deliverResult(List<ContactListFilter> results) {
+        if (!mStopped) {
+            super.deliverResult(results);
+        }
+    }
+
+    @Override
+    public void startLoading() {
+        if (mObserver == null) {
+            mObserver = new ForceLoadContentObserver();
+            getContext().getContentResolver().registerContentObserver(
+                    Contacts.CONTENT_URI, true, mObserver);
+        }
+
+        mStopped = false;
+
+        if (mResults != null) {
+            deliverResult(mResults);
+        } else {
+            forceLoad();
+        }
+    }
+
+    @Override
+    public void stopLoading() {
+        if (mObserver != null) {
+            getContext().getContentResolver().unregisterContentObserver(mObserver);
+            mObserver = null;
+        }
+
+        mResults = null;
+
+        // Attempt to cancel the current load task if possible.
+        cancelLoad();
+
+        // Make sure that any outstanding loads clean themselves up properly
+        mStopped = true;
+    }
+
     @Override
     public void destroy() {
         stopLoading();
     }
-
-    @Override
-    public void startLoading() {
-        cancelLoad();
-        forceLoad();
-    }
-
-    @Override
-    public void stopLoading() {
-        cancelLoad();
-    }
 }