Moving contact filter spinner to the action bar

Change-Id: I29506d0e71e15e4804899146855f22690969d1f5
diff --git a/res/layout-xlarge/contacts_list_content.xml b/res/layout-xlarge/contacts_list_content.xml
index cb5f52e..a025751 100644
--- a/res/layout-xlarge/contacts_list_content.xml
+++ b/res/layout-xlarge/contacts_list_content.xml
@@ -19,66 +19,47 @@
     android:id="@+id/pinned_header_list_layout"
     android:layout_width="match_parent"
     android:layout_height="match_parent"
-    android:orientation="vertical"
+    android:orientation="horizontal"
     >
 
     <view
-        class="com.android.contacts.widget.NotifyingSpinner"
-        android:id="@+id/filter_spinner"
-        android:layout_width="match_parent"
-        android:layout_height="wrap_content"
-        android:layout_marginLeft="44dip"
-        android:prompt="@string/list_filter_prompt"
-        android:background="@drawable/filter_selector_background"
-        android:spinnerMode="dropdown"
-        android:visibility="gone"
+        class="com.android.contacts.list.ContactListAizyView"
+        android:id="@+id/contacts_list_aizy"
+        android:layout_width="40dip"
+        android:layout_height="match_parent"
     />
 
     <LinearLayout
-        android:layout_width="match_parent"
+        android:layout_width="0px"
         android:layout_height="match_parent"
-        android:orientation="horizontal"
+        android:orientation="vertical"
+        android:layout_weight="1"
         >
 
-        <view
-            class="com.android.contacts.list.ContactListAizyView"
-            android:id="@+id/contacts_list_aizy"
-            android:layout_width="40dip"
-            android:layout_height="match_parent"
+        <View
+            android:layout_width="match_parent"
+            android:layout_height="2dip"
+            android:layout_marginTop="1dip"
+            android:layout_marginBottom="1dip"
+            android:background="#7e7e87"
         />
 
-        <LinearLayout
-            android:layout_width="0px"
-            android:layout_height="match_parent"
-            android:orientation="vertical"
+        <view
+            class="com.android.contacts.list.ContactEntryListView"
+            android:id="@android:id/list"
+            android:layout_width="match_parent"
+            android:layout_height="0dip"
+            android:fastScrollEnabled="true"
             android:layout_weight="1"
-            >
+            android:scrollingCache="false"
+        />
 
-            <View
-                android:layout_width="match_parent"
-                android:layout_height="2dip"
-                android:layout_marginTop="1dip"
-                android:layout_marginBottom="1dip"
-                android:background="#7e7e87"
-            />
+        <include layout="@layout/contacts_list_empty"/>
 
-            <view
-                class="com.android.contacts.list.ContactEntryListView"
-                android:id="@android:id/list"
-                android:layout_width="match_parent"
-                android:layout_height="0dip"
-                android:fastScrollEnabled="true"
-                android:layout_weight="1"
-                android:scrollingCache="false"
-            />
-
-            <include layout="@layout/contacts_list_empty"/>
-
-            <ViewStub android:id="@+id/footer_stub"
-                android:layout="@layout/footer_panel"
-                android:layout_width="fill_parent"
-                android:layout_height="wrap_content"
-            />
-        </LinearLayout>
+        <ViewStub android:id="@+id/footer_stub"
+            android:layout="@layout/footer_panel"
+            android:layout_width="fill_parent"
+            android:layout_height="wrap_content"
+        />
     </LinearLayout>
 </LinearLayout>
diff --git a/res/layout/contacts_list_content.xml b/res/layout/contacts_list_content.xml
index 79c11dd..56e8340 100644
--- a/res/layout/contacts_list_content.xml
+++ b/res/layout/contacts_list_content.xml
@@ -30,15 +30,6 @@
             >
 
         <view
-            class="com.android.contacts.widget.NotifyingSpinner"
-            android:id="@+id/filter_spinner"
-            android:layout_width="match_parent"
-            android:layout_height="wrap_content"
-            android:prompt="@string/list_filter_prompt"
-            android:visibility="gone"
-        />
-
-        <view
             class="com.android.contacts.list.ContactEntryListView"
             android:id="@android:id/list"
             android:layout_width="match_parent"
diff --git a/res/layout/filter_spinner.xml b/res/layout/filter_spinner.xml
index eb2b444..21c591f 100644
--- a/res/layout/filter_spinner.xml
+++ b/res/layout/filter_spinner.xml
@@ -18,19 +18,9 @@
     xmlns:android="http://schemas.android.com/apk/res/android"
     class="com.android.contacts.list.FilterSpinnerItemView"
     android:layout_height="wrap_content"
-    android:layout_width="fill_parent"
-    android:paddingTop="12dip"
-    android:paddingBottom="12dip"
+    android:layout_width="match_parent"
     android:gravity="left">
 
-    <TextView
-        android:layout_width="wrap_content"
-        android:layout_height="wrap_content"
-        android:textAppearance="?android:attr/textAppearanceMedium"
-        android:textColor="?android:attr/textColorTertiary"
-        android:gravity="center_vertical"
-        android:text="@string/list_filter_label"/>
-
     <ImageView
         android:id="@+id/icon"
         android:scaleType="fitCenter"
diff --git a/res/layout/navigation_bar.xml b/res/layout/navigation_bar.xml
new file mode 100644
index 0000000..c90f563
--- /dev/null
+++ b/res/layout/navigation_bar.xml
@@ -0,0 +1,53 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2010 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+  
+          http://www.apache.org/licenses/LICENSE-2.0
+  
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+
+<LinearLayout
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    android:id="@+id/navigation_bar"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent"
+    android:orientation="horizontal">
+
+    <FrameLayout
+        android:layout_width="wrap_content"
+        android:layout_height="match_parent"
+        android:paddingRight="10dip"
+        android:minWidth="240dip">
+        <TextView
+            android:id="@+id/search_label"
+            android:layout_width="wrap_content"
+            android:layout_height="match_parent"
+            android:text="@string/search_label"
+            android:gravity="center_vertical" />
+
+        <view
+            class="com.android.contacts.widget.NotifyingSpinner"
+            android:id="@+id/filter_spinner"
+            android:layout_width="wrap_content"
+            android:layout_height="match_parent"
+            android:prompt="@string/list_filter_prompt"
+            android:background="@drawable/filter_selector_background" />
+    </FrameLayout>
+
+    <view
+        class="android.widget.SearchView"
+        android:id="@+id/search_view"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:layout_gravity="center_vertical" />
+    />
+
+</LinearLayout>
diff --git a/res/values/strings.xml b/res/values/strings.xml
index 9cf4398..3a0b175 100644
--- a/res/values/strings.xml
+++ b/res/values/strings.xml
@@ -1330,6 +1330,9 @@
     <!-- The description of the directory where the contact was found [CHAR LIMIT=100]-->
     <string name="contact_directory_account_description">from <xliff:g id="type" example="Corporate Directory">%1$s</xliff:g> (<xliff:g id="name" example="me@acme.com">%2$s</xliff:g>)</string>
 
+    <!-- The label displayed in the Contacts action bar when in search mode [CHAR LIMIT=64] -->
+    <string name="search_label">Searching all contact</string>
+
     <!-- The label in section header in the contact list for a contact directory [CHAR LIMIT=128] -->
     <string name="directory_search_label">Directory</string>
 
@@ -1339,9 +1342,6 @@
     <!-- Toast shown when creating a personal copy of a contact [CHAR LIMIT=100] -->
     <string name="toast_making_personal_copy">Creating a personal copy</string>
 
-    <!-- Label for the contact filter selector. [CHAR LIMIT=32] -->
-    <string name="list_filter_label">Displaying:</string>
-
     <!-- Prompt for selection of a contact list filter [CHAR LIMIT=64] -->
     <string name="list_filter_prompt">Choose contact list</string>
 
diff --git a/src/com/android/contacts/activities/ActionBarAdapter.java b/src/com/android/contacts/activities/ActionBarAdapter.java
index 5eb422d..f2181b3 100644
--- a/src/com/android/contacts/activities/ActionBarAdapter.java
+++ b/src/com/android/contacts/activities/ActionBarAdapter.java
@@ -17,225 +17,127 @@
 package com.android.contacts.activities;
 
 import com.android.contacts.R;
+import com.android.contacts.list.ContactListFilterController;
 import com.android.contacts.list.ContactsRequest;
+import com.android.contacts.widget.NotifyingSpinner;
 
 import android.app.ActionBar;
-import android.app.ActionBar.Tab;
-import android.app.ActionBar.TabListener;
-import android.app.FragmentTransaction;
 import android.content.Context;
 import android.os.Bundle;
+import android.text.TextUtils;
+import android.view.LayoutInflater;
 import android.view.View;
-import android.view.View.OnFocusChangeListener;
-import android.widget.EditText;
 import android.widget.SearchView;
 import android.widget.SearchView.OnCloseListener;
 import android.widget.SearchView.OnQueryChangeListener;
-
-import java.util.HashMap;
+import android.widget.TextView;
 
 /**
  * Adapter for the action bar at the top of the Contacts activity.
  */
-public class ActionBarAdapter implements TabListener, OnQueryChangeListener, OnCloseListener {
+public class ActionBarAdapter implements OnQueryChangeListener, OnCloseListener {
 
     public interface Listener {
         void onAction();
     }
 
-    private static final String EXTRA_KEY_DEFAULT_MODE = "navBar.defaultMode";
-    private static final String EXTRA_KEY_MODE = "navBar.mode";
+    private static final String EXTRA_KEY_SEARCH_MODE = "navBar.searchMode";
     private static final String EXTRA_KEY_QUERY = "navBar.query";
 
-    private static final String KEY_MODE_CONTACTS = "mode_contacts";
-    private static final String KEY_MODE_FAVORITES = "mode_favorites";
+    private static final String KEY_MODE_DEFAULT = "mode_default";
     private static final String KEY_MODE_SEARCH = "mode_search";
 
-    private int mMode = ContactBrowserMode.MODE_CONTACTS;
-    private int mDefaultMode = ContactBrowserMode.MODE_CONTACTS;
+    private boolean mSearchMode;
     private String mQueryString;
-    private HashMap<Integer, Bundle> mSavedStateByMode = new HashMap<Integer, Bundle>();
+    private Bundle mSavedStateForSearchMode;
+    private Bundle mSavedStateForDefaultMode;
+
+    private View mNavigationBar;
+    private TextView mSearchLabel;
+    private SearchView mSearchView;
 
     private final Context mContext;
 
     private Listener mListener;
-
-    private ActionBar mActionBar;
-    private Tab mSearchTab;
-    private Tab mContactsTab;
-    private Tab mFavoritesTab;
-    private SearchView mSearchView;
-    private boolean mActive;
-    private EditText mQueryTextView;
+    private NotifyingSpinner mFilterSpinner;
 
     public ActionBarAdapter(Context context) {
         mContext = context;
     }
 
     public void onCreate(Bundle savedState, ContactsRequest request, ActionBar actionBar) {
-        mActionBar = actionBar;
-        mDefaultMode = -1;
-        mMode = -1;
         mQueryString = null;
         if (savedState != null) {
-            mDefaultMode = savedState.getInt(EXTRA_KEY_DEFAULT_MODE, -1);
-            mMode = savedState.getInt(EXTRA_KEY_MODE, -1);
+            mSearchMode = savedState.getBoolean(EXTRA_KEY_SEARCH_MODE);
             mQueryString = savedState.getString(EXTRA_KEY_QUERY);
-            restoreSavedState(savedState, ContactBrowserMode.MODE_CONTACTS, KEY_MODE_CONTACTS);
-            restoreSavedState(savedState, ContactBrowserMode.MODE_FAVORITES, KEY_MODE_FAVORITES);
-            restoreSavedState(savedState, ContactBrowserMode.MODE_SEARCH, KEY_MODE_SEARCH);
-        }
-
-        int actionCode = request.getActionCode();
-        if (mDefaultMode == -1) {
-            mDefaultMode = actionCode == ContactsRequest.ACTION_DEFAULT
-                    ? ContactBrowserMode.MODE_CONTACTS
-                    : ContactBrowserMode.MODE_FAVORITES;
-        }
-        if (mMode == -1) {
-            mMode = request.isSearchMode() ? ContactBrowserMode.MODE_SEARCH : mDefaultMode;
-        }
-        if (mQueryString == null) {
+            mSavedStateForDefaultMode = savedState.getParcelable(KEY_MODE_DEFAULT);
+            mSavedStateForSearchMode = savedState.getParcelable(KEY_MODE_SEARCH);
+        } else {
+            mSearchMode = request.isSearchMode();
             mQueryString = request.getQueryString();
         }
 
-        mActionBar.setTabNavigationMode();
+        mNavigationBar = LayoutInflater.from(mContext).inflate(R.layout.navigation_bar, null);
+        actionBar.setCustomNavigationMode(mNavigationBar);
 
-        mContactsTab = mActionBar.newTab();
-        mContactsTab.setText(mContext.getString(R.string.contactsList));
-        mContactsTab.setTabListener(this);
-        mActionBar.addTab(mContactsTab);
+        mFilterSpinner = (NotifyingSpinner) mNavigationBar.findViewById(R.id.filter_spinner);
+        mSearchLabel = (TextView) mNavigationBar.findViewById(R.id.search_label);
+        mSearchView = (SearchView) mNavigationBar.findViewById(R.id.search_view);
+        mSearchView.setIconifiedByDefault(false);
+        mSearchView.setEnabled(false);
+        mSearchView.setOnQueryChangeListener(this);
+        mSearchView.setOnCloseListener(this);
+        mSearchView.setQuery(mQueryString, false);
 
-        mFavoritesTab = mActionBar.newTab();
-        mFavoritesTab.setTabListener(this);
-        mFavoritesTab.setText(mContext.getString(R.string.strequentList));
-        mActionBar.addTab(mFavoritesTab);
-
-        mSearchTab = mActionBar.newTab();
-        mSearchTab.setTabListener(this);
-
-        mSearchView = new SearchView(mContext);
-        setSearchSelectionListener(mSearchView);
-        mSearchView.setIconified(mMode != ContactBrowserMode.MODE_SEARCH);
-
-        mSearchTab.setCustomView(mSearchView);
-        mActionBar.addTab(mSearchTab);
-
-        mActive = true;
-
-        update();
-    }
-
-    private void setSearchSelectionListener(SearchView search) {
-        mQueryTextView = (EditText) search.findViewById(com.android.internal.R.id.search_src_text);
-        mQueryTextView.setOnFocusChangeListener(new OnFocusChangeListener() {
-
-            @Override
-            public void onFocusChange(View v, boolean hasFocus) {
-                if (hasFocus) {
-                    setMode(ContactBrowserMode.MODE_SEARCH);
-                } else {
-                    setMode(mDefaultMode);
-                }
-            }
-        });
+        updateVisibility();
     }
 
     public void setListener(Listener listener) {
         mListener = listener;
     }
 
-    public int getMode() {
-        return mMode;
+    public void setContactListFilterController(ContactListFilterController controller) {
+        controller.setFilterSpinner(mFilterSpinner);
     }
 
-    public void setMode(int mode) {
-        if (mMode != mode) {
-            mMode = mode;
-            update();
+    public boolean isSearchMode() {
+        return mSearchMode;
+    }
+
+    public void setSearchMode(boolean flag) {
+        if (mSearchMode != flag) {
+            mSearchMode = flag;
+            updateVisibility();
             if (mListener != null) {
                 mListener.onAction();
             }
         }
     }
 
-    public int getDefaultMode() {
-        return mDefaultMode;
-    }
-
-    public void setDefaultMode(int defaultMode) {
-        mDefaultMode = defaultMode;
-    }
-
     public String getQueryString() {
         return mQueryString;
     }
 
     public void setQueryString(String query) {
         mQueryString = query;
-        setSearchViewQuery(query);
-    }
-
-    public void update() {
-        if (!mActive) {
-            return;
-        }
-
-        switch(mMode) {
-            case ContactBrowserMode.MODE_CONTACTS:
-                mActionBar.selectTab(mContactsTab);
-                mSearchView.setOnCloseListener(null);
-                mSearchView.setOnQueryChangeListener(null);
-                mSearchView.setIconified(true);
-                break;
-            case ContactBrowserMode.MODE_FAVORITES:
-                mActionBar.selectTab(mFavoritesTab);
-                mSearchView.setOnCloseListener(null);
-                mSearchView.setOnQueryChangeListener(null);
-                mSearchView.setIconified(true);
-                break;
-            case ContactBrowserMode.MODE_SEARCH:
-                setSearchViewQuery(mQueryString);
-                mSearchView.setOnCloseListener(this);
-                mSearchView.setOnQueryChangeListener(this);
-                mActionBar.selectTab(mSearchTab);
-                break;
-        }
-    }
-
-    private void setSearchViewQuery(String query) {
         mSearchView.setQuery(query, false);
-        // TODO Expose API on SearchView to do this
-        mQueryTextView.selectAll();
     }
 
-    @Override
-    public void onTabSelected(Tab tab, FragmentTransaction ft) {
-        if (!mActive) {
-            return;
+    public void updateVisibility() {
+        if (mSearchMode) {
+            mSearchLabel.setVisibility(View.VISIBLE);
+            mFilterSpinner.setVisibility(View.GONE);
+        } else {
+            mSearchLabel.setVisibility(View.GONE);
+            mFilterSpinner.setVisibility(View.VISIBLE);
         }
-
-        if (tab == mSearchTab) {
-            setMode(ContactBrowserMode.MODE_SEARCH);
-        } else if (tab == mContactsTab) {
-            setMode(ContactBrowserMode.MODE_CONTACTS);
-            setDefaultMode(ContactBrowserMode.MODE_CONTACTS);
-        } else if (tab == mFavoritesTab) {
-            setMode(ContactBrowserMode.MODE_FAVORITES);
-            setDefaultMode(ContactBrowserMode.MODE_FAVORITES);
-        } else {        // mCancelSearchButton
-            setMode(mDefaultMode);
-        }
-    }
-
-    @Override
-    public void onTabUnselected(Tab tab, FragmentTransaction ft) {
-        // Nothing to do
     }
 
     @Override
     public boolean onQueryTextChanged(String queryString) {
         mQueryString = queryString;
+        mSearchMode = !TextUtils.isEmpty(queryString);
+        updateVisibility();
         if (mListener != null) {
             mListener.onAction();
         }
@@ -244,52 +146,39 @@
 
     @Override
     public boolean onSubmitQuery(String query) {
-        // Ignore submit query request
         return true;
     }
 
     @Override
     public boolean onClose() {
-        mSearchView.setOnCloseListener(null);
-        mSearchView.setOnQueryChangeListener(null);
-        setMode(mDefaultMode);
-        return false;  // OK to close
+        setSearchMode(false);
+        return false;
     }
 
-    public void saveStateForMode(int mode, Bundle state) {
-        mSavedStateByMode.put(mode, state);
+    public Bundle getSavedStateForSearchMode() {
+        return mSavedStateForSearchMode;
     }
 
-    public Bundle getSavedStateForMode(int mode) {
-        return mSavedStateByMode.get(mode);
+    public void setSavedStateForSearchMode(Bundle state) {
+        mSavedStateForSearchMode = state;
     }
 
-    public void clearSavedState(int mode) {
-        mSavedStateByMode.remove(mode);
+    public Bundle getSavedStateForDefaultMode() {
+        return mSavedStateForDefaultMode;
+    }
+
+    public void setSavedStateForDefaultMode(Bundle state) {
+        mSavedStateForDefaultMode = state;
     }
 
     public void onSaveInstanceState(Bundle outState) {
-        outState.putInt(EXTRA_KEY_DEFAULT_MODE, mDefaultMode);
-        outState.putInt(EXTRA_KEY_MODE, mMode);
+        outState.putBoolean(EXTRA_KEY_SEARCH_MODE, mSearchMode);
         outState.putString(EXTRA_KEY_QUERY, mQueryString);
-        saveInstanceState(outState, ContactBrowserMode.MODE_CONTACTS, KEY_MODE_CONTACTS);
-        saveInstanceState(outState, ContactBrowserMode.MODE_FAVORITES, KEY_MODE_FAVORITES);
-        saveInstanceState(outState, ContactBrowserMode.MODE_SEARCH, KEY_MODE_SEARCH);
-    }
-
-    private void saveInstanceState(Bundle outState, int mode, String key) {
-        Bundle state = mSavedStateByMode.get(mode);
-        if (state != null) {
-            outState.putParcelable(key, state);
+        if (mSavedStateForDefaultMode != null) {
+            outState.putParcelable(KEY_MODE_DEFAULT, mSavedStateForDefaultMode);
         }
-    }
-
-    private void restoreSavedState(Bundle savedState, int mode, String key) {
-        Bundle bundle = savedState.getParcelable(key);
-        if (bundle == null) {
-            mSavedStateByMode.remove(mode);
-        } else {
-            mSavedStateByMode.put(mode, bundle);
+        if (mSavedStateForSearchMode != null) {
+            outState.putParcelable(KEY_MODE_SEARCH, mSavedStateForSearchMode);
         }
     }
 }
diff --git a/src/com/android/contacts/activities/ContactBrowserActivity.java b/src/com/android/contacts/activities/ContactBrowserActivity.java
index e9da31d..ab6576e 100644
--- a/src/com/android/contacts/activities/ContactBrowserActivity.java
+++ b/src/com/android/contacts/activities/ContactBrowserActivity.java
@@ -23,6 +23,7 @@
 import com.android.contacts.list.ContactBrowseListContextMenuAdapter;
 import com.android.contacts.list.ContactBrowseListFragment;
 import com.android.contacts.list.ContactEntryListFragment;
+import com.android.contacts.list.ContactListFilterController;
 import com.android.contacts.list.ContactsIntentResolver;
 import com.android.contacts.list.ContactsRequest;
 import com.android.contacts.list.DefaultContactBrowseListFragment;
@@ -73,8 +74,6 @@
 
     private static final String TAG = "ContactBrowserActivity";
 
-    private static final String KEY_MODE = "mode";
-
     private static final int SUBACTIVITY_NEW_CONTACT = 2;
     private static final int SUBACTIVITY_SETTINGS = 3;
     private static final int SUBACTIVITY_EDIT_CONTACT = 4;
@@ -83,6 +82,8 @@
 
     private static final int DEFAULT_DIRECTORY_RESULT_LIMIT = 20;
 
+    private static final String KEY_SEARCH_MODE = "searchMode";
+
     private DialogManager mDialogManager = new DialogManager(this);
 
     private ContactsIntentResolver mIntentResolver;
@@ -93,10 +94,7 @@
     private boolean mHasActionBar;
     private ActionBarAdapter mActionBarAdapter;
 
-    /**
-     * Contact browser mode, see {@link ContactBrowserMode}.
-     */
-    private int mMode = -1;
+    private boolean mSearchMode;
 
     private ContactBrowseListFragment mListFragment;
     private ContactNoneFragment mEmptyFragment;
@@ -115,9 +113,11 @@
 
     private boolean mSearchInitiated;
 
+    private ContactListFilterController mContactListFilterController;
 
     public ContactBrowserActivity() {
         mIntentResolver = new ContactsIntentResolver(this);
+        mContactListFilterController = new ContactListFilterController(this);
     }
 
     @Override
@@ -125,6 +125,10 @@
         if (fragment instanceof ContactBrowseListFragment) {
             mListFragment = (ContactBrowseListFragment)fragment;
             mListFragment.setOnContactListActionListener(new ContactBrowserActionListener());
+            if (mListFragment instanceof DefaultContactBrowseListFragment) {
+                ((DefaultContactBrowseListFragment) mListFragment).setContactListFilterController(
+                        mContactListFilterController);
+            }
         } else if (fragment instanceof ContactNoneFragment) {
             mEmptyFragment = (ContactNoneFragment)fragment;
         } else if (fragment instanceof ContactDetailFragment) {
@@ -141,7 +145,7 @@
         super.onCreate(savedState);
 
         if (savedState != null) {
-            mMode = savedState.getInt(KEY_MODE);
+            mSearchMode = savedState.getBoolean(KEY_SEARCH_MODE);
         }
 
         // Extract relevant information from the intent
@@ -172,6 +176,8 @@
             mActionBarAdapter = new ActionBarAdapter(this);
             mActionBarAdapter.onCreate(savedState, mRequest, getActionBar());
             mActionBarAdapter.setListener(this);
+            mActionBarAdapter.setContactListFilterController(mContactListFilterController);
+            // TODO: request may ask for FREQUENT - set the filter accordingly
         }
 
         configureListFragment();
@@ -191,9 +197,9 @@
             }
 
             if (mHasActionBar) {
-                if (mActionBarAdapter.getMode() != ContactBrowserMode.MODE_CONTACTS) {
-                    mActionBarAdapter.clearSavedState(ContactBrowserMode.MODE_CONTACTS);
-                    mActionBarAdapter.setMode(ContactBrowserMode.MODE_CONTACTS);
+                if (mActionBarAdapter.isSearchMode()) {
+                    mActionBarAdapter.setSavedStateForSearchMode(null);
+                    mActionBarAdapter.setSearchMode(false);
                 }
             }
             setSelectedContactUri(uri);
@@ -220,56 +226,47 @@
     }
 
     private void configureListFragment() {
-        int mode = -1;
+        boolean searchMode = mSearchMode;
         if (mHasActionBar) {
-            mode = mActionBarAdapter.getMode();
-            if (mode == ContactBrowserMode.MODE_SEARCH
-                    && TextUtils.isEmpty(mActionBarAdapter.getQueryString())) {
-                mode = mActionBarAdapter.getDefaultMode();
-            }
+            searchMode = mActionBarAdapter.isSearchMode();
         } else {
-            int actionCode = mRequest.getActionCode();
-            if (actionCode == ContactsRequest.ACTION_FREQUENT ||
-                    actionCode == ContactsRequest.ACTION_STARRED ||
-                    actionCode == ContactsRequest.ACTION_STREQUENT) {
-                mode = ContactBrowserMode.MODE_FAVORITES;
-            } else {
-                mode = ContactBrowserMode.MODE_CONTACTS;
-            }
+// TODO: reenable FREQUENT, STARRED and STREQUENT
+//            int actionCode = mRequest.getActionCode();
+//            if (actionCode == ContactsRequest.ACTION_FREQUENT ||
+//                    actionCode == ContactsRequest.ACTION_STARRED ||
+//                    actionCode == ContactsRequest.ACTION_STREQUENT) {
+//                mode = ContactBrowserMode.MODE_FAVORITES;
+//            } else {
+//                mode = ContactBrowserMode.MODE_CONTACTS;
+//            }
         }
 
-        boolean replaceList = (mode != mMode);
+        boolean replaceList = mListFragment == null || (mSearchMode != searchMode);
         if (replaceList) {
             closeListFragment();
-            mMode = mode;
-            switch (mMode) {
-                case ContactBrowserMode.MODE_CONTACTS: {
-                    mListFragment = createListFragment(ContactsRequest.ACTION_DEFAULT);
-                    break;
-                }
-                case ContactBrowserMode.MODE_FAVORITES: {
-                    int favoritesAction = mRequest.getActionCode();
-                    if (favoritesAction == ContactsRequest.ACTION_DEFAULT) {
-                        favoritesAction = ContactsRequest.ACTION_STREQUENT;
-                    }
-                    mListFragment = createListFragment(favoritesAction);
-                    break;
-                }
-                case ContactBrowserMode.MODE_SEARCH: {
-                    mListFragment = createContactSearchFragment();
-                    break;
-                }
+            mSearchMode = searchMode;
+            if (mSearchMode) {
+                mListFragment = createContactSearchFragment();
+            } else {
+                mListFragment = createListFragment(ContactsRequest.ACTION_DEFAULT);
             }
         }
 
         if (mHasActionBar) {
-            Bundle savedStateForMode = mActionBarAdapter.getSavedStateForMode(mMode);
-            if (savedStateForMode != null) {
-                mListFragment.restoreSavedState(savedStateForMode);
-                mActionBarAdapter.clearSavedState(mMode);
-            }
-            if (mMode == ContactBrowserMode.MODE_SEARCH) {
+            if (mSearchMode) {
+                Bundle savedState = mActionBarAdapter.getSavedStateForSearchMode();
+                if (savedState != null) {
+                    mListFragment.restoreSavedState(savedState);
+                    mActionBarAdapter.setSavedStateForSearchMode(null);
+                }
+
                 mListFragment.setQueryString(mActionBarAdapter.getQueryString());
+            } else {
+                Bundle savedState = mActionBarAdapter.getSavedStateForDefaultMode();
+                if (savedState != null) {
+                    mListFragment.restoreSavedState(savedState);
+                    mActionBarAdapter.setSavedStateForDefaultMode(null);
+                }
             }
         }
 
@@ -295,7 +292,11 @@
             if (mHasActionBar) {
                 Bundle state = new Bundle();
                 mListFragment.onSaveInstanceState(state);
-                mActionBarAdapter.saveStateForMode(mMode, state);
+                if (mSearchMode) {
+                    mActionBarAdapter.setSavedStateForSearchMode(state);
+                } else {
+                    mActionBarAdapter.setSavedStateForDefaultMode(state);
+                }
             }
 
             mListFragment = null;
@@ -830,9 +831,9 @@
                 if (unicodeChar != 0) {
                     String query = new String(new int[]{ unicodeChar }, 0, 1);
                     if (mHasActionBar) {
-                        if (mActionBarAdapter.getMode() != ContactBrowserMode.MODE_SEARCH) {
+                        if (!mActionBarAdapter.isSearchMode()) {
                             mActionBarAdapter.setQueryString(query);
-                            mActionBarAdapter.setMode(ContactBrowserMode.MODE_SEARCH);
+                            mActionBarAdapter.setSearchMode(true);
                             return true;
                         }
                     } else if (!mRequest.isSearchMode()) {
@@ -867,7 +868,7 @@
     @Override
     protected void onSaveInstanceState(Bundle outState) {
         super.onSaveInstanceState(outState);
-        outState.putInt(KEY_MODE, mMode);
+        outState.putBoolean(KEY_SEARCH_MODE, mSearchMode);
         if (mActionBarAdapter != null) {
             mActionBarAdapter.onSaveInstanceState(outState);
         }
diff --git a/src/com/android/contacts/activities/ContactBrowserMode.java b/src/com/android/contacts/activities/ContactBrowserMode.java
deleted file mode 100644
index c135c1d..0000000
--- a/src/com/android/contacts/activities/ContactBrowserMode.java
+++ /dev/null
@@ -1,28 +0,0 @@
-/*
- * Copyright (C) 2010 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.contacts.activities;
-
-/**
- * Contact browser mode constants.
- */
-public class ContactBrowserMode {
-
-    public static final int MODE_CONTACTS = 0;
-    public static final int MODE_FAVORITES = 1;
-    public static final int MODE_SEARCH = 2;
-
-}
diff --git a/src/com/android/contacts/list/ContactListFilterController.java b/src/com/android/contacts/list/ContactListFilterController.java
new file mode 100644
index 0000000..d10faac
--- /dev/null
+++ b/src/com/android/contacts/list/ContactListFilterController.java
@@ -0,0 +1,326 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.contacts.list;
+
+import com.android.contacts.R;
+import com.android.contacts.widget.NotifyingSpinner;
+
+import android.app.Activity;
+import android.app.LoaderManager;
+import android.app.LoaderManager.LoaderCallbacks;
+import android.content.Context;
+import android.content.Loader;
+import android.content.SharedPreferences;
+import android.os.Bundle;
+import android.os.Handler;
+import android.os.Message;
+import android.preference.PreferenceManager;
+import android.util.SparseArray;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.AdapterView;
+import android.widget.AdapterView.OnItemSelectedListener;
+import android.widget.BaseAdapter;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Controls a list of {@link ContactListFilter}'s.
+ */
+public class ContactListFilterController
+        implements NotifyingSpinner.SelectionListener, OnItemSelectedListener,
+        LoaderCallbacks<List<ContactListFilter>>{
+
+    public interface ContactListFilterListener {
+        void onContactListFiltersLoaded();
+        void onContactListFilterChanged();
+        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 ContactListFilterListener mListener;
+
+    private SparseArray<ContactListFilter> mFilters;
+    private ArrayList<ContactListFilter> mFilterList;
+    private int mNextFilterId = 1;
+    private NotifyingSpinner mFilterSpinner;
+    private FilterSpinnerAdapter mFilterSpinnerAdapter;
+    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();
+            }
+        }
+    };
+
+    public ContactListFilterController(Activity activity) {
+        mContext = activity;
+        mLoaderManager = activity.getLoaderManager();
+    }
+
+    public void setListener(ContactListFilterListener listener) {
+        mListener = listener;
+    }
+
+    public void setFilterSpinner(NotifyingSpinner filterSpinner) {
+        mFilterSpinner = filterSpinner;
+        mFilterSpinner.setOnItemSelectedListener(this);
+    }
+
+    public ContactListFilter getFilter() {
+        return mFilter;
+    }
+
+    public List<ContactListFilter> getFilterList() {
+        return mFilterList;
+    }
+
+    public void onItemSelected(AdapterView<?> parent, View view, int position, long id) {
+        setContactListFilter((int) id);
+    }
+
+    public void onNothingSelected(AdapterView<?> parent) {
+        setContactListFilter(0);
+    }
+
+    public boolean isLoaded() {
+        return mFiltersLoaded;
+    }
+
+    public void startLoading() {
+        // Set the "ready" flag right away - we only want to start the loader once
+        mFiltersLoaded = false;
+        mFilter = ContactListFilter.restoreFromPreferences(getSharedPreferences());
+        loadFilters();
+    }
+
+    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);
+    }
+
+    @Override
+    public void onLoadFinished(
+            Loader<List<ContactListFilter>> loader, List<ContactListFilter> filters) {
+        if (mFilters == null) {
+            mFilters = new SparseArray<ContactListFilter>(filters.size());
+            mFilterList = new ArrayList<ContactListFilter>();
+        } else {
+            mFilters.clear();
+            mFilterList.clear();
+        }
+
+        boolean filterValid = mFilter != null
+                && (mFilter.filterType == ContactListFilter.FILTER_TYPE_ALL_ACCOUNTS
+                        || mFilter.filterType == ContactListFilter.FILTER_TYPE_CUSTOM);
+
+        int accountCount = 0;
+        int count = filters.size();
+        for (int index = 0; index < count; index++) {
+            if (filters.get(index).filterType == ContactListFilter.FILTER_TYPE_ACCOUNT) {
+                accountCount++;
+            }
+        }
+
+        if (accountCount > 1) {
+            mFilters.append(mNextFilterId++,
+                    new ContactListFilter(ContactListFilter.FILTER_TYPE_ALL_ACCOUNTS));
+            mFilters.append(mNextFilterId++,
+                    new ContactListFilter(ContactListFilter.FILTER_TYPE_CUSTOM));
+        }
+
+        for (int index = 0; index < count; index++) {
+            ContactListFilter filter = filters.get(index);
+
+            boolean firstAndOnly = accountCount == 1
+                    && filter.filterType == ContactListFilter.FILTER_TYPE_ACCOUNT;
+
+            // If we only have one account, don't show it as "account", instead show it as "all"
+            if (firstAndOnly) {
+                filter = new ContactListFilter(ContactListFilter.FILTER_TYPE_ALL_ACCOUNTS);
+            }
+
+            mFilters.append(mNextFilterId++, filter);
+            mFilterList.add(filter);
+            filterValid |= filter.equals(mFilter);
+
+            if (firstAndOnly) {
+                mFilters.append(mNextFilterId++,
+                        new ContactListFilter(ContactListFilter.FILTER_TYPE_CUSTOM));
+            }
+        }
+
+        boolean filterChanged = false;
+        if (mFilter == null  || !filterValid) {
+            filterChanged = mFilter != null;
+            mFilter = getDefaultFilter();
+        }
+
+        if (mFilterSpinnerAdapter == null) {
+            mFilterSpinnerAdapter = new FilterSpinnerAdapter();
+            mFilterSpinner.setAdapter(mFilterSpinnerAdapter);
+        } else {
+            mFilterSpinnerAdapter.notifyDataSetChanged();
+        }
+
+        if (filterChanged) {
+            mFiltersLoaded = true;
+            mListener.onContactListFilterChanged();
+        } else if (!mFiltersLoaded) {
+            mFiltersLoaded = true;
+            mListener.onContactListFiltersLoaded();
+        }
+
+        updateFilterView();
+    }
+
+    public void postDelayedRefresh() {
+        if (!mHandler.hasMessages(MESSAGE_REFRESH_FILTERS)) {
+            mHandler.sendEmptyMessageDelayed(
+                    MESSAGE_REFRESH_FILTERS, FILTER_SPINNER_REFRESH_DELAY_MILLIS);
+        }
+    }
+
+    protected void setContactListFilter(int filterId) {
+        ContactListFilter filter;
+        if (filterId == ContactListFilter.FILTER_TYPE_ALL_ACCOUNTS) {
+            filter = new ContactListFilter(ContactListFilter.FILTER_TYPE_ALL_ACCOUNTS);
+        } else if (filterId == ContactListFilter.FILTER_TYPE_CUSTOM) {
+            filter = new ContactListFilter(ContactListFilter.FILTER_TYPE_CUSTOM);
+        } else {
+            filter = mFilters.get(filterId);
+            if (filter == null) {
+                filter = getDefaultFilter();
+            }
+        }
+
+        if (!filter.equals(mFilter)) {
+            mFilter = filter;
+            ContactListFilter.storeToPreferences(getSharedPreferences(), mFilter);
+            updateFilterView();
+            mListener.onContactListFilterChanged();
+        }
+    }
+
+    @Override
+    public void onSetSelection(NotifyingSpinner spinner, int position) {
+        ContactListFilter filter = mFilters.valueAt(position);
+        if (filter.filterType == ContactListFilter.FILTER_TYPE_CUSTOM) {
+            mListener.onContactListFilterCustomizationRequest();
+        }
+    }
+
+    public void selectCustomFilter() {
+        mFilter = new ContactListFilter(ContactListFilter.FILTER_TYPE_CUSTOM);
+        updateFilterView();
+        mListener.onContactListFilterChanged();
+    }
+
+    private ContactListFilter getDefaultFilter() {
+        return mFilters.valueAt(0);
+    }
+
+    protected void updateFilterView() {
+        if (mFiltersLoaded) {
+            mFilterSpinner.setSetSelectionListener(null);
+            if (mFilter != null && mFilters != null) {
+                int size = mFilters.size();
+                for (int i = 0; i < size; i++) {
+                    if (mFilters.valueAt(i).equals(mFilter)) {
+                        mFilterSpinner.setSelection(i);
+                        break;
+                    }
+                }
+            }
+            mFilterSpinner.setSetSelectionListener(this);
+        }
+    }
+
+    private class FilterSpinnerAdapter extends BaseAdapter {
+        private LayoutInflater mLayoutInflater;
+
+        public FilterSpinnerAdapter() {
+            mLayoutInflater = LayoutInflater.from(mContext);
+        }
+
+        @Override
+        public int getCount() {
+            return mFilters.size();
+        }
+
+        @Override
+        public long getItemId(int position) {
+            return mFilters.keyAt(position);
+        }
+
+        @Override
+        public Object getItem(int position) {
+            return mFilters.valueAt(position);
+        }
+
+        @Override
+        public View getDropDownView(int position, View convertView, ViewGroup parent) {
+            return getView(position, convertView, parent, true);
+        }
+
+        @Override
+        public View getView(int position, View convertView, ViewGroup parent) {
+            return getView(position, convertView, parent, false);
+        }
+
+        public View getView(int position, View convertView, ViewGroup parent, boolean dropdown) {
+            FilterSpinnerItemView view;
+            if (dropdown) {
+                if (convertView != null) {
+                    view = (FilterSpinnerItemView) convertView;
+                } else {
+                    view = (FilterSpinnerItemView) mLayoutInflater.inflate(
+                            R.layout.filter_spinner_item, parent, false);
+                }
+            } else {
+                view = (FilterSpinnerItemView) mLayoutInflater.inflate(
+                        R.layout.filter_spinner, parent, false);
+            }
+            view.setContactListFilter(mFilters.valueAt(position));
+            view.bindView(dropdown);
+            return view;
+        }
+    }
+}
diff --git a/src/com/android/contacts/list/ContactGroupFilterLoader.java b/src/com/android/contacts/list/ContactListFilterLoader.java
similarity index 96%
rename from src/com/android/contacts/list/ContactGroupFilterLoader.java
rename to src/com/android/contacts/list/ContactListFilterLoader.java
index 757994e..3bd334f 100644
--- a/src/com/android/contacts/list/ContactGroupFilterLoader.java
+++ b/src/com/android/contacts/list/ContactListFilterLoader.java
@@ -35,7 +35,7 @@
 /**
  * A loader for the data needed for the group selector.
  */
-public class ContactGroupFilterLoader extends AsyncTaskLoader<List<ContactListFilter>> {
+public class ContactListFilterLoader extends AsyncTaskLoader<List<ContactListFilter>> {
 
     private static final class GroupQuery {
         public static final String[] COLUMNS = {
@@ -56,7 +56,7 @@
                 Groups.DELETED + "=0 AND " + Groups.FAVORITES + "=0";
     }
 
-    public ContactGroupFilterLoader(Context context) {
+    public ContactListFilterLoader(Context context) {
         super(context);
     }
 
diff --git a/src/com/android/contacts/list/DefaultContactBrowseListFragment.java b/src/com/android/contacts/list/DefaultContactBrowseListFragment.java
index ff06d85..ca93e0c 100644
--- a/src/com/android/contacts/list/DefaultContactBrowseListFragment.java
+++ b/src/com/android/contacts/list/DefaultContactBrowseListFragment.java
@@ -17,38 +17,26 @@
 
 import com.android.contacts.R;
 import com.android.contacts.preference.ContactsPreferences;
-import com.android.contacts.widget.NotifyingSpinner;
 
 import android.app.Activity;
-import android.app.LoaderManager.LoaderCallbacks;
 import android.content.Intent;
-import android.content.Loader;
 import android.content.SharedPreferences;
 import android.database.Cursor;
 import android.os.Bundle;
-import android.os.Handler;
-import android.os.Message;
 import android.preference.PreferenceManager;
-import android.util.SparseArray;
 import android.view.LayoutInflater;
 import android.view.View;
 import android.view.ViewGroup;
-import android.widget.AdapterView;
-import android.widget.AdapterView.OnItemSelectedListener;
-import android.widget.BaseAdapter;
 import android.widget.FrameLayout;
 import android.widget.ProgressBar;
 import android.widget.TextView;
 
-import java.util.ArrayList;
-import java.util.List;
-
 /**
  * Fragment containing a contact list used for browsing (as compared to
  * picking a contact with one of the PICK intents).
  */
 public class DefaultContactBrowseListFragment extends ContactBrowseListFragment
-        implements OnItemSelectedListener, NotifyingSpinner.SelectionListener {
+        implements ContactListFilterController.ContactListFilterListener {
 
     private static final String KEY_EDIT_MODE = "editMode";
     private static final String KEY_CREATE_CONTACT_ENABLED = "createContactEnabled";
@@ -58,15 +46,6 @@
 
     private static final int REQUEST_CODE_CUSTOMIZE_FILTER = 3;
 
-    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 boolean mEditMode;
     private boolean mCreateContactEnabled;
     private int mDisplayWithPhonesOnlyOption = ContactsRequest.DISPLAY_ONLY_WITH_PHONES_DISABLED;
@@ -75,38 +54,7 @@
     private View mSearchHeaderView;
 
     private boolean mFilterEnabled;
-    private SparseArray<ContactListFilter> mFilters;
-    private ArrayList<ContactListFilter> mFilterList;
-    private int mNextFilterId = 1;
-    private NotifyingSpinner mFilterSpinner;
-    private FilterSpinnerAdapter mFilterSpinnerAdapter;
-    private ContactListFilter mFilter;
-    private boolean mFiltersLoaded;
-    private SharedPreferences mPrefs;
-    private final Handler mHandler = new Handler() {
-
-        @Override
-        public void handleMessage(Message msg) {
-            if (msg.what == MESSAGE_REFRESH_FILTERS) {
-                loadFilters();
-            }
-        }
-    };
-
-    private LoaderCallbacks<List<ContactListFilter>> mGroupFilterLoaderCallbacks =
-            new LoaderCallbacks<List<ContactListFilter>>() {
-
-        @Override
-        public ContactGroupFilterLoader onCreateLoader(int id, Bundle args) {
-            return new ContactGroupFilterLoader(getContext());
-        }
-
-        @Override
-        public void onLoadFinished(
-                Loader<List<ContactListFilter>> loader, List<ContactListFilter> data) {
-            onGroupFilterLoadFinished(data);
-        }
-    };
+    private ContactListFilterController mFilterController;
 
     public DefaultContactBrowseListFragment() {
         setPhotoLoaderEnabled(true);
@@ -114,6 +62,11 @@
         setAizyEnabled(true);
     }
 
+    public void setContactListFilterController(ContactListFilterController filterController) {
+        mFilterController = filterController;
+        mFilterController.setListener(this);
+    }
+
     @Override
     public void onSaveInstanceState(Bundle outState) {
         super.onSaveInstanceState(outState);
@@ -137,7 +90,7 @@
         mDisplayWithPhonesOnlyOption = savedState.getInt(KEY_DISPLAY_WITH_PHONES_ONLY);
         mVisibleContactsRestrictionEnabled =
                 savedState.getBoolean(KEY_VISIBLE_CONTACTS_RESTRICTION);
-        mFilterEnabled = savedState.getBoolean(KEY_FILTER_ENABLED);
+        setFilterEnabled(savedState.getBoolean(KEY_FILTER_ENABLED));
     }
 
     @Override
@@ -204,7 +157,9 @@
         if (adapter != null) {
             adapter.setContactsWithPhoneNumbersOnly(isShowingContactsWithPhonesOnly());
             adapter.setVisibleContactsOnly(mVisibleContactsRestrictionEnabled);
-            adapter.setFilter(mFilter, mFilterList);
+            if (mFilterEnabled && mFilterController != null) {
+                adapter.setFilter(mFilterController.getFilter(), mFilterController.getFilterList());
+            }
         }
     }
 
@@ -226,21 +181,6 @@
         headerContainer.addView(mSearchHeaderView);
         getListView().addHeaderView(headerContainer);
         checkHeaderViewVisibility();
-        configureFilterSpinner();
-    }
-
-    protected void configureFilterSpinner() {
-        mFilterSpinner = (NotifyingSpinner)getView().findViewById(R.id.filter_spinner);
-        if (mFilterSpinner == null) {
-            return;
-        }
-
-        if (!mFilterEnabled) {
-            mFilterSpinner.setVisibility(View.GONE);
-            return;
-        }
-
-        mFilterSpinner.setOnItemSelectedListener(this);
     }
 
     @Override
@@ -323,234 +263,48 @@
         this.mCreateContactEnabled = flag;
     }
 
-    public void onItemSelected(AdapterView<?> parent, View view, int position, long id) {
-        setContactListFilter((int) id);
-    }
-
-    public void onNothingSelected(AdapterView<?> parent) {
-        setContactListFilter(0);
-    }
-
-    @Override
-    public void onStart() {
-        mPrefs = PreferenceManager.getDefaultSharedPreferences(getContext());
-        if (mFilterEnabled) {
-            mFiltersLoaded = false;
-            mFilter = ContactListFilter.restoreFromPreferences(mPrefs);
-        }
-        super.onStart();
-    }
-
     @Override
     protected void startLoading() {
-        // We need to load filters before we can load the list contents
-        if (mFilterEnabled && !mFiltersLoaded) {
-            loadFilters();
-        } else {
+        if (mFilterController != null && !mFilterController.isLoaded()) {
+            mFilterController.startLoading();
+        }
+
+        if (!mFilterEnabled || mFilterController == null || mFilterController.isLoaded()) {
             super.startLoading();
         }
     }
 
-    private void loadFilters() {
-        getLoaderManager().restartLoader(
-                R.id.contact_list_filter_loader, null, mGroupFilterLoaderCallbacks);
-    }
-
-    protected void onGroupFilterLoadFinished(List<ContactListFilter> filters) {
-        if (mFilters == null) {
-            mFilters = new SparseArray<ContactListFilter>(filters.size());
-            mFilterList = new ArrayList<ContactListFilter>();
-        } else {
-            mFilters.clear();
-            mFilterList.clear();
-        }
-
-        boolean filterValid = mFilter != null
-                && (mFilter.filterType == ContactListFilter.FILTER_TYPE_ALL_ACCOUNTS
-                        || mFilter.filterType == ContactListFilter.FILTER_TYPE_CUSTOM);
-
-        int accountCount = 0;
-        int count = filters.size();
-        for (int index = 0; index < count; index++) {
-            if (filters.get(index).filterType == ContactListFilter.FILTER_TYPE_ACCOUNT) {
-                accountCount++;
-            }
-        }
-
-        if (accountCount > 1) {
-            mFilters.append(mNextFilterId++,
-                    new ContactListFilter(ContactListFilter.FILTER_TYPE_ALL_ACCOUNTS));
-            mFilters.append(mNextFilterId++,
-                    new ContactListFilter(ContactListFilter.FILTER_TYPE_CUSTOM));
-        }
-
-        for (int index = 0; index < count; index++) {
-            ContactListFilter filter = filters.get(index);
-
-            boolean firstAndOnly = accountCount == 1
-                    && filter.filterType == ContactListFilter.FILTER_TYPE_ACCOUNT;
-
-            // If we only have one account, don't show it as "account", instead show it as "all"
-            if (firstAndOnly) {
-                filter = new ContactListFilter(ContactListFilter.FILTER_TYPE_ALL_ACCOUNTS);
-            }
-
-            mFilters.append(mNextFilterId++, filter);
-            mFilterList.add(filter);
-            filterValid |= filter.equals(mFilter);
-
-            if (firstAndOnly) {
-                mFilters.append(mNextFilterId++,
-                        new ContactListFilter(ContactListFilter.FILTER_TYPE_CUSTOM));
-            }
-        }
-
-        boolean filterChanged = false;
-        if (mFilter == null  || !filterValid) {
-            filterChanged = mFilter != null;
-            mFilter = getDefaultFilter();
-        }
-
-        if (mFilterSpinnerAdapter == null) {
-            mFilterSpinnerAdapter = new FilterSpinnerAdapter();
-            mFilterSpinner.setAdapter(mFilterSpinnerAdapter);
-        } else {
-            mFilterSpinnerAdapter.notifyDataSetChanged();
-        }
-
-        if (filterChanged) {
-            mFiltersLoaded = true;
-            reloadData();
-        } else if (!mFiltersLoaded) {
-            mFiltersLoaded = true;
-            startLoading();
-        }
-
-        updateFilterView();
-    }
-
     @Override
     protected void onPartitionLoaded(int partitionIndex, Cursor data) {
         super.onPartitionLoaded(partitionIndex, data);
-        if (mFilterEnabled && !mHandler.hasMessages(MESSAGE_REFRESH_FILTERS)) {
-            mHandler.sendEmptyMessageDelayed(
-                    MESSAGE_REFRESH_FILTERS, FILTER_SPINNER_REFRESH_DELAY_MILLIS);
-        }
-    }
-
-    protected void setContactListFilter(int filterId) {
-        ContactListFilter filter;
-        if (filterId == ContactListFilter.FILTER_TYPE_ALL_ACCOUNTS) {
-            filter = new ContactListFilter(ContactListFilter.FILTER_TYPE_ALL_ACCOUNTS);
-        } else if (filterId == ContactListFilter.FILTER_TYPE_CUSTOM) {
-            filter = new ContactListFilter(ContactListFilter.FILTER_TYPE_CUSTOM);
-        } else {
-            filter = mFilters.get(filterId);
-            if (filter == null) {
-                filter = getDefaultFilter();
-            }
-        }
-
-        if (!filter.equals(mFilter)) {
-            mFilter = filter;
-            ContactListFilter.storeToPreferences(mPrefs, mFilter);
-            updateFilterView();
-            reloadData();
+        if (mFilterController != null) {
+            mFilterController.postDelayedRefresh();
         }
     }
 
     @Override
-    public void onSetSelection(NotifyingSpinner spinner, int position) {
-        ContactListFilter filter = mFilters.valueAt(position);
-        if (filter.filterType == ContactListFilter.FILTER_TYPE_CUSTOM) {
-            startActivityForResult(new Intent(getContext(), CustomContactListFilterActivity.class),
-                    REQUEST_CODE_CUSTOMIZE_FILTER);
+    public void onContactListFiltersLoaded() {
+        if (mFilterEnabled) {
+            // Filters have been loaded - now we can start loading the list itself
+            startLoading();
         }
     }
 
     @Override
+    public void onContactListFilterChanged() {
+        reloadData();
+    }
+
+    @Override
+    public void onContactListFilterCustomizationRequest() {
+        startActivityForResult(new Intent(getContext(), CustomContactListFilterActivity.class),
+                REQUEST_CODE_CUSTOMIZE_FILTER);
+    }
+
+    @Override
     public void onActivityResult(int requestCode, int resultCode, Intent data) {
         if (requestCode == REQUEST_CODE_CUSTOMIZE_FILTER && resultCode == Activity.RESULT_OK) {
-            mFilter = new ContactListFilter(ContactListFilter.FILTER_TYPE_CUSTOM);
-            updateFilterView();
-            reloadData();
-        }
-    }
-
-    private ContactListFilter getDefaultFilter() {
-        return mFilters.valueAt(0);
-    }
-
-    protected void updateFilterView() {
-        if (mFiltersLoaded) {
-            if (mFilters.size() == 0) {
-                mFilterSpinner.setVisibility(View.GONE);
-                return;
-            }
-
-            mFilterSpinner.setSetSelectionListener(null);
-            if (mFilter != null && mFilters != null) {
-                int size = mFilters.size();
-                for (int i = 0; i < size; i++) {
-                    if (mFilters.valueAt(i).equals(mFilter)) {
-                        mFilterSpinner.setSelection(i);
-                        break;
-                    }
-                }
-            }
-            mFilterSpinner.setSetSelectionListener(this);
-            mFilterSpinner.setVisibility(View.VISIBLE);
-        }
-    }
-
-    private class FilterSpinnerAdapter extends BaseAdapter {
-        private LayoutInflater mLayoutInflater;
-
-        public FilterSpinnerAdapter() {
-            mLayoutInflater = LayoutInflater.from(getContext());
-        }
-
-        @Override
-        public int getCount() {
-            return mFilters.size();
-        }
-
-        @Override
-        public long getItemId(int position) {
-            return mFilters.keyAt(position);
-        }
-
-        @Override
-        public Object getItem(int position) {
-            return mFilters.valueAt(position);
-        }
-
-        @Override
-        public View getDropDownView(int position, View convertView, ViewGroup parent) {
-            return getView(position, convertView, parent, true);
-        }
-
-        @Override
-        public View getView(int position, View convertView, ViewGroup parent) {
-            return getView(position, convertView, parent, false);
-        }
-
-        public View getView(int position, View convertView, ViewGroup parent, boolean dropdown) {
-            FilterSpinnerItemView view;
-            if (dropdown) {
-                if (convertView != null) {
-                    view = (FilterSpinnerItemView) convertView;
-                } else {
-                    view = (FilterSpinnerItemView) mLayoutInflater.inflate(
-                            R.layout.filter_spinner_item, parent, false);
-                }
-            } else {
-                view = (FilterSpinnerItemView) mLayoutInflater.inflate(
-                        R.layout.filter_spinner, parent, false);
-            }
-            view.setContactListFilter(mFilters.valueAt(position));
-            view.bindView(dropdown);
-            return view;
+            mFilterController.selectCustomFilter();
         }
     }
 }