Merge "Refactoring ContactEntryListFragment/Adapter"
diff --git a/src/com/android/contacts/list/ContactEntryListAdapter.java b/src/com/android/contacts/list/ContactEntryListAdapter.java
index f7f4fdb..ff402a2 100644
--- a/src/com/android/contacts/list/ContactEntryListAdapter.java
+++ b/src/com/android/contacts/list/ContactEntryListAdapter.java
@@ -23,17 +23,20 @@
 
 import android.content.Context;
 import android.content.CursorLoader;
+import android.content.pm.PackageManager;
 import android.database.Cursor;
+import android.net.Uri;
 import android.os.Bundle;
 import android.provider.ContactsContract.ContactCounts;
+import android.provider.ContactsContract.Directory;
 import android.text.TextUtils;
+import android.util.Log;
 import android.view.LayoutInflater;
 import android.view.View;
 import android.view.ViewGroup;
 import android.widget.TextView;
 
-import java.util.HashMap;
-import java.util.Iterator;
+import java.util.HashSet;
 
 /**
  * Common base class for various contact-related lists, e.g. contact list, phone number list
@@ -43,6 +46,23 @@
 
     private static final String TAG = "ContactEntryListAdapter";
 
+    private static final class DirectoryQuery {
+        public static final Uri URI = Directory.CONTENT_URI;
+        public static final String ORDER_BY = Directory._ID;
+
+        public static final String[] PROJECTION = {
+            Directory._ID,
+            Directory.PACKAGE_NAME,
+            Directory.TYPE_RESOURCE_ID,
+            Directory.DISPLAY_NAME,
+        };
+
+        public static final int ID = 0;
+        public static final int PACKAGE_NAME = 1;
+        public static final int TYPE_RESOURCE_ID = 2;
+        public static final int DISPLAY_NAME = 3;
+    }
+
     /**
      * The animation is used here to allocate animated name text views.
      */
@@ -61,28 +81,34 @@
     private boolean mLoading = true;
     private boolean mEmptyListEnabled = true;
 
-    private HashMap<Integer, DirectoryPartition> mPartitions;
-
     public ContactEntryListAdapter(Context context) {
         super(context, R.layout.list_section, R.id.header_text);
         addPartitions();
     }
 
-    /**
-     * Adds all partitions this adapter will handle. The default implementation
-     * creates one partition with no header.
-     */
     protected void addPartitions() {
-        addPartition(false, false);
+        addPartition(createDefaultDirectoryPartition());
     }
 
-    public void addDirectoryPartition(DirectoryPartition partition) {
-        if (mPartitions == null) {
-            mPartitions = new HashMap<Integer, DirectoryPartition>();
+    protected DirectoryPartition createDefaultDirectoryPartition() {
+        DirectoryPartition partition = new DirectoryPartition(true, true);
+        partition.setDirectoryId(Directory.DEFAULT);
+        partition.setDirectoryType(getContext().getString(R.string.contactsList));
+        partition.setPriorityDirectory(true);
+        return partition;
+    }
+
+    private int getPartitionByDirectoryId(long id) {
+        int count = getPartitionCount();
+        for (int i = 0; i < count; i++) {
+            Partition partition = getPartition(i);
+            if (partition instanceof DirectoryPartition) {
+                if (((DirectoryPartition)partition).getDirectoryId() == id) {
+                    return i;
+                }
+            }
         }
-        int partitionIndex = getPartitionCount();
-        mPartitions.put(partitionIndex, partition);
-        addPartition(partition.getShowIfEmpty(), partition.getDirectoryType() != null);
+        return -1;
     }
 
     public abstract String getContactDisplayName(int position);
@@ -93,10 +119,13 @@
      */
     public void onDataReload() {
         boolean notify = false;
-        if (mPartitions != null) {
-            for (DirectoryPartition partition : mPartitions.values()) {
-                if (!partition.isLoading()) {
-                    partition.setLoading(true);
+        int count = getPartitionCount();
+        for (int i = 0; i < count; i++) {
+            Partition partition = getPartition(i);
+            if (partition instanceof DirectoryPartition) {
+                DirectoryPartition directoryPartition = (DirectoryPartition)partition;
+                if (!directoryPartition.isLoading()) {
+                    directoryPartition.setLoading(true);
                     notify = true;
                 }
             }
@@ -111,12 +140,21 @@
     }
 
     public void setSearchMode(boolean flag) {
-        if (mSearchMode != flag) {
-            mSearchMode = flag;
+        mSearchMode = flag;
 
-            // Search mode change may mean a new data type in the list.
-            // Let's drop current data to avoid confusion
-            resetPartitions();
+        int defaultPartitionIndex = -1;
+        int count = getPartitionCount();
+        for (int i = 0; i < count; i++) {
+            Partition partition = getPartition(i);
+            if (partition instanceof DirectoryPartition &&
+                    ((DirectoryPartition)partition).getDirectoryId() == Directory.DEFAULT) {
+                defaultPartitionIndex = i;
+                break;
+            }
+        }
+        if (defaultPartitionIndex != -1) {
+            setShowIfEmpty(defaultPartitionIndex, flag);
+            setHasHeader(defaultPartitionIndex, flag);
         }
     }
 
@@ -192,11 +230,74 @@
         mEmptyListEnabled = flag;
     }
 
+    public void configureDirectoryLoader(CursorLoader loader) {
+        loader.setUri(DirectoryQuery.URI);
+        loader.setProjection(DirectoryQuery.PROJECTION);
+        loader.setSortOrder(DirectoryQuery.ORDER_BY);
+    }
+
+    /**
+     * Updates partitions according to the directory meta-data contained in the supplied
+     * cursor.  Takes ownership of the cursor and will close it.
+     */
+    public void changeDirectories(Cursor cursor) {
+        HashSet<Long> directoryIds = new HashSet<Long>();
+
+        // TODO preserve the order of partition to match those of the cursor
+        // Phase I: add new directories
+        try {
+            while (cursor.moveToNext()) {
+                long id = cursor.getLong(DirectoryQuery.ID);
+                directoryIds.add(id);
+                if (getPartitionByDirectoryId(id) == -1) {
+                    DirectoryPartition partition = createDirectoryPartition(cursor);
+                    addPartition(partition);
+                }
+            }
+        } finally {
+            cursor.close();
+        }
+
+        // Phase II: remove deleted directories
+        int count = getPartitionCount();
+        for (int i = count; --i >= 0; ) {
+            Partition partition = getPartition(i);
+            if (partition instanceof DirectoryPartition) {
+                long id = ((DirectoryPartition)partition).getDirectoryId();
+                if (!directoryIds.contains(id)) {
+                    removePartition(i);
+                }
+            }
+        }
+
+        invalidate();
+        notifyDataSetChanged();
+    }
+
+    private DirectoryPartition createDirectoryPartition(Cursor cursor) {
+        PackageManager pm = getContext().getPackageManager();
+        DirectoryPartition partition = new DirectoryPartition(false, true);
+        partition.setDirectoryId(cursor.getLong(DirectoryQuery.ID));
+        String packageName = cursor.getString(DirectoryQuery.PACKAGE_NAME);
+        int typeResourceId = cursor.getInt(DirectoryQuery.TYPE_RESOURCE_ID);
+        if (!TextUtils.isEmpty(packageName) && typeResourceId != 0) {
+            // TODO: should this be done on a background thread?
+            try {
+                partition.setDirectoryType(pm.getResourcesForApplication(packageName)
+                        .getString(typeResourceId));
+            } catch (Exception e) {
+                Log.e(TAG, "Cannot obtain directory type from package: " + packageName);
+            }
+        }
+        partition.setDisplayName(cursor.getString(DirectoryQuery.DISPLAY_NAME));
+        return partition;
+    }
+
     @Override
     public void changeCursor(int partitionIndex, Cursor cursor) {
-        if (mPartitions != null) {
-            DirectoryPartition partition = mPartitions.get(partitionIndex);
-            partition.setLoading(false);
+        Partition partition = getPartition(partitionIndex);
+        if (partition instanceof DirectoryPartition) {
+            ((DirectoryPartition)partition).setLoading(false);
         }
 
         super.changeCursor(partitionIndex, cursor);
@@ -258,20 +359,24 @@
 
     @Override
     protected void bindHeaderView(View view, int partitionIndex, Cursor cursor) {
-        DirectoryPartition partition = mPartitions.get(partitionIndex);
+        Partition partition = getPartition(partitionIndex);
+        if (!(partition instanceof DirectoryPartition)) {
+            return;
+        }
 
+        DirectoryPartition directoryPartition = (DirectoryPartition)partition;
         TextView directoryTypeTextView = (TextView)view.findViewById(R.id.directory_type);
-        directoryTypeTextView.setText(partition.getDirectoryType());
+        directoryTypeTextView.setText(directoryPartition.getDirectoryType());
         TextView displayNameTextView = (TextView)view.findViewById(R.id.display_name);
-        if (!TextUtils.isEmpty(partition.getDisplayName())) {
-            displayNameTextView.setText(partition.getDisplayName());
+        if (!TextUtils.isEmpty(directoryPartition.getDisplayName())) {
+            displayNameTextView.setText(directoryPartition.getDisplayName());
             displayNameTextView.setVisibility(View.VISIBLE);
         } else {
             displayNameTextView.setVisibility(View.GONE);
         }
 
         TextView countText = (TextView)view.findViewById(R.id.count);
-        if (partition.isLoading()) {
+        if (directoryPartition.isLoading()) {
             countText.setText(R.string.search_results_searching);
         } else {
             int count = cursor == null ? 0 : cursor.getCount();
diff --git a/src/com/android/contacts/list/ContactEntryListFragment.java b/src/com/android/contacts/list/ContactEntryListFragment.java
index 8c2abb3..9bfdbb3 100644
--- a/src/com/android/contacts/list/ContactEntryListFragment.java
+++ b/src/com/android/contacts/list/ContactEntryListFragment.java
@@ -23,7 +23,7 @@
 import com.android.contacts.R;
 import com.android.contacts.ui.ContactsPreferences;
 import com.android.contacts.widget.ContextMenuAdapter;
-import com.google.android.collect.Lists;
+import com.android.contacts.widget.CompositeCursorAdapter.Partition;
 
 import android.accounts.Account;
 import android.accounts.AccountManager;
@@ -36,10 +36,8 @@
 import android.content.IContentService;
 import android.content.Intent;
 import android.content.Loader;
-import android.content.pm.PackageManager;
 import android.database.ContentObserver;
 import android.database.Cursor;
-import android.net.Uri;
 import android.os.Bundle;
 import android.os.Handler;
 import android.os.Parcelable;
@@ -68,8 +66,6 @@
 import android.widget.AbsListView.OnScrollListener;
 import android.widget.AdapterView.OnItemClickListener;
 
-import java.util.ArrayList;
-
 /**
  * Common base class for various contact-related list fragments.
  */
@@ -87,8 +83,6 @@
 
     private static final int DIRECTORY_LOADER_ID = -1;
 
-    private ArrayList<DirectoryPartition> mDirectoryPartitions = Lists.newArrayList();
-
     private boolean mSectionHeaderDisplayEnabled;
     private boolean mPhotoLoaderEnabled;
     private boolean mSearchMode;
@@ -118,31 +112,17 @@
 
     private int mProviderStatus = ProviderStatus.STATUS_NORMAL;
 
+    private boolean mForceLoad;
+    private boolean mLoadDirectoryList;
+
     /**
      * Indicates whether we are doing the initial complete load of data or
      * a refresh caused by a change notification.
      */
-    private boolean mInitialLoadComplete;
+    private boolean mLoadPriorityDirectoriesOnly;
 
     private ContactsRequest mRequest;
 
-    private static final class DirectoryQuery {
-        public static final Uri URI = Directory.CONTENT_URI;
-        public static final String ORDER_BY = Directory._ID;
-
-        public static final String[] PROJECTION = {
-            Directory._ID,
-            Directory.PACKAGE_NAME,
-            Directory.TYPE_RESOURCE_ID,
-            Directory.DISPLAY_NAME,
-        };
-
-        public static final int ID = 0;
-        public static final int PACKAGE_NAME = 1;
-        public static final int TYPE_RESOURCE_ID = 2;
-        public static final int DISPLAY_NAME = 3;
-    }
-
     protected abstract View inflateView(LayoutInflater inflater, ViewGroup container);
     protected abstract T createListAdapter();
 
@@ -200,42 +180,58 @@
 
         loadPreferences(mContactsPrefs);
 
-        configureAdapter();
-
-        if (isSearchMode()) {
-            if (mInitialLoadComplete) {
-                for (DirectoryPartition partition : mDirectoryPartitions) {
-                    if (partition.getPartitionIndex() != 0) {
-                        startLoading(partition, true);
-                    }
-                }
-            } else {
-                startLoading(0, null);
-            }
-        } else {
-            startLoading(0, null);
+        if (mListView instanceof ContactEntryListView) {
+            ContactEntryListView listView = (ContactEntryListView)mListView;
+            listView.setHighlightNamesWhenScrolling(isNameHighlighingEnabled());
         }
 
-        ContactEntryListView listView = (ContactEntryListView)mListView;
-        listView.setHighlightNamesWhenScrolling(isNameHighlighingEnabled());
-
+        mForceLoad = false;
+        mLoadDirectoryList = true;
+        mLoadPriorityDirectoriesOnly = true;
+        startLoading();
         super.onStart();
     }
 
+    private void startLoading() {
+        configureAdapter();
+        int partitionCount = mAdapter.getPartitionCount();
+        for (int i = 0; i < partitionCount; i++) {
+            Partition partition = mAdapter.getPartition(i);
+            if (partition instanceof DirectoryPartition) {
+                DirectoryPartition directoryPartition = (DirectoryPartition)partition;
+                if (mLoadPriorityDirectoriesOnly == directoryPartition.isPriorityDirectory()) {
+                    startLoadingDirectoryPartition(i);
+                }
+            } else {
+                startLoading(i, null);
+            }
+        }
+    }
+
     @Override
     protected Loader<Cursor> onCreateLoader(int id, Bundle args) {
+        CursorLoader loader = new CursorLoader(getActivity(), null, null, null, null, null);
         if (id == DIRECTORY_LOADER_ID) {
-            return new CursorLoader(getActivity(), DirectoryQuery.URI, DirectoryQuery.PROJECTION,
-                    null, null, DirectoryQuery.ORDER_BY);
+            mAdapter.configureDirectoryLoader(loader);
         } else {
-            CursorLoader loader = new CursorLoader(getActivity(), null, null, null, null, null);
-            if (mAdapter != null) {
-                long directoryId = args != null && args.containsKey(DIRECTORY_ID_ARG_KEY)
-                        ? args.getLong(DIRECTORY_ID_ARG_KEY)
-                        : Directory.DEFAULT;
-                mAdapter.configureLoader(loader, directoryId);
-            }
-            return loader;
+            long directoryId = args != null && args.containsKey(DIRECTORY_ID_ARG_KEY)
+                    ? args.getLong(DIRECTORY_ID_ARG_KEY)
+                    : Directory.DEFAULT;
+            mAdapter.configureLoader(loader, directoryId);
+        }
+        return loader;
+    }
+
+    private void startLoadingDirectoryPartition(int partitionIndex) {
+        DirectoryPartition partition = (DirectoryPartition)mAdapter.getPartition(partitionIndex);
+        CursorLoader loader = (CursorLoader)getLoader(partitionIndex);
+        if (loader == null) {
+            Bundle args = new Bundle();
+            args.putLong(DIRECTORY_ID_ARG_KEY, partition.getDirectoryId());
+            startLoading(partitionIndex, args);
+        } else if (mForceLoad) {
+            mAdapter.configureLoader(loader, partition.getDirectoryId());
+            loader.forceLoad();
         }
     }
 
@@ -248,15 +244,27 @@
             return;
         }
 
-        int partitionIndex = loader.getId();
-        if (!mInitialLoadComplete) {
-            onInitialLoadFinished(partitionIndex, data);
+        int loaderId = loader.getId();
+        if (loaderId == DIRECTORY_LOADER_ID) {
+            mAdapter.changeDirectories(data);
         } else {
-            onRequeryFinished(partitionIndex, data);
+            final int partitionIndex = loaderId;      // by convention
+            mAdapter.changeCursor(partitionIndex, data);
+            showCount(partitionIndex, data);
+            if (partitionIndex == mAdapter.getIndexedPartition()) {
+                mAizy.setIndexer(mAdapter.getIndexer());
+            }
+            completeRestoreInstanceState();
         }
 
-        if (partitionIndex == mAdapter.getIndexedPartition()) {
-            mAizy.setIndexer(mAdapter.getIndexer());
+        if (isSearchMode()) {
+            if (mLoadDirectoryList) {
+                mLoadDirectoryList = false;
+                startLoading(DIRECTORY_LOADER_ID, null);
+            } else if (mLoadPriorityDirectoriesOnly) {
+                mLoadPriorityDirectoriesOnly = false;
+                startLoading();
+            }
         }
 
 // TODO fix the empty view
@@ -265,149 +273,28 @@
 //            }
     }
 
-    private void onInitialLoadFinished(int partitionIndex, Cursor data) {
-        if (partitionIndex == 0) {
-            mDirectoryPartitions.clear();
-            mAdapter.resetPartitions();
-            DirectoryPartition partition = new DirectoryPartition();
-            partition.setDirectoryId(Directory.DEFAULT);
-            partition.setShowIfEmpty(isSearchMode());
-            partition.setDirectoryType(getActivity().getString(R.string.contactsList));
-            mDirectoryPartitions.add(partition);
-            if (isSearchMode()) {
-                mAdapter.addDirectoryPartition(partition);
-            } else {
-                mAdapter.addPartition(false, false);
-            }
-            mAdapter.changeCursor(partitionIndex, data);
-            showCount(partitionIndex, data);
-            if (data != null) {
-                completeRestoreInstanceState();
-            }
-            if (isSearchMode()) {
-                startLoading(DIRECTORY_LOADER_ID, null);
-            } else {
-                mInitialLoadComplete = true;
-            }
-        } else if (partitionIndex == DIRECTORY_LOADER_ID) {
-            try {
-                for (int index = 0; data.moveToNext(); index++) {
-                    DirectoryPartition partition = createDirectoryPartition(index, data);
-                    if (index != 0) {
-                        mDirectoryPartitions.add(partition);
-                        mAdapter.addDirectoryPartition(partition);
-                        startLoading(partition, false);
-                    }
-                }
-            } finally {
-                data.close();
-            }
-
-            mInitialLoadComplete = true;
-        }
-    }
-
-    private void onRequeryFinished(int partitionIndex, Cursor data) {
-        if (partitionIndex == 0) {
-            mAdapter.changeCursor(partitionIndex, data);
-            showCount(partitionIndex, data);
-            int size = mDirectoryPartitions.size();
-            for (int i = 1; i < size; i++) {
-                startLoading(mDirectoryPartitions.get(i), true);
-            }
-        } else if (partitionIndex == DIRECTORY_LOADER_ID) {
-            // The list of available directories has changed: reload everything
-            try {
-                mDirectoryPartitions.clear();
-                mAdapter.resetPartitions();
-                for (int index = 0; data.moveToNext(); index++) {
-                    DirectoryPartition partition = createDirectoryPartition(index, data);
-                    mDirectoryPartitions.add(partition);
-                    mAdapter.addDirectoryPartition(partition);
-                }
-            } finally {
-                data.close();
-            }
-            reloadData();
-        } else {
-            mAdapter.changeCursor(partitionIndex, data);
-            showCount(partitionIndex, data);
-        }
-    }
-
-    private DirectoryPartition createDirectoryPartition(int partitionIndex, Cursor cursor) {
-        PackageManager pm = getActivity().getPackageManager();
-        DirectoryPartition partition = new DirectoryPartition();
-        partition.setPartitionIndex(partitionIndex);
-        partition.setDirectoryId(cursor.getLong(DirectoryQuery.ID));
-        String packageName = cursor.getString(DirectoryQuery.PACKAGE_NAME);
-        int typeResourceId = cursor.getInt(DirectoryQuery.TYPE_RESOURCE_ID);
-        if (!TextUtils.isEmpty(packageName) && typeResourceId != 0) {
-            // TODO: should this be done on a background thread?
-            try {
-                partition.setDirectoryType(pm.getResourcesForApplication(packageName)
-                        .getString(typeResourceId));
-            } catch (Exception e) {
-                Log.e(TAG, "Cannot obtain directory type from package: " + packageName);
-            }
-        }
-        partition.setDisplayName(cursor.getString(DirectoryQuery.DISPLAY_NAME));
-        partition.setShowIfEmpty(partition.getDirectoryId() == Directory.DEFAULT);
-        return partition;
-    }
-
-    private void startLoading(DirectoryPartition partition, boolean forceLoad) {
-        CursorLoader loader = (CursorLoader)getLoader(partition.getPartitionIndex());
-        if (loader == null) {
-            Bundle args = new Bundle();
-            args.putLong(DIRECTORY_ID_ARG_KEY, partition.getDirectoryId());
-            startLoading(partition.getPartitionIndex(), args);
-        } else {
-            mAdapter.configureLoader(loader, partition.getDirectoryId());
-            if (forceLoad) {
-                loader.forceLoad();
-            }
-        }
-    }
-
     protected void reloadData() {
-        if (mInitialLoadComplete) {
-            mAdapter.onDataReload();
-            if (mDirectoryPartitions.size() > 0) {
-                // We need to cancel _all_ current queries and then launch
-                // a new query for the 0th partition.
-
-                CursorLoader directoryLoader = (CursorLoader)getLoader(DIRECTORY_LOADER_ID);
-                if (directoryLoader != null) {
-                    directoryLoader.cancelLoad();
-                }
-                int size = mDirectoryPartitions.size();
-                for (int i = 0; i < size; i++) {
-                    CursorLoader loader = (CursorLoader)getLoader(i);
-                    if (loader != null) {
-                        loader.cancelLoad();
-                    }
-                }
-
-                startLoading(mDirectoryPartitions.get(0), true);
-            }
-        } else {
-            startLoading(0, null);
-        }
+        cancelLoading();
+        mAdapter.onDataReload();
+        mLoadPriorityDirectoriesOnly = true;
+        mForceLoad = true;
+        startLoading();
     }
 
-    private void stopLoading() {
-        stopLoading(DIRECTORY_LOADER_ID);
-        for (DirectoryPartition partition : mDirectoryPartitions) {
-            stopLoading(partition.getPartitionIndex());
+    private void cancelLoading() {
+        int size = mAdapter.getPartitionCount();
+        for (int i = 0; i < size; i++) {
+            CursorLoader loader = (CursorLoader)getLoader(i);
+            if (loader != null) {
+                loader.cancelLoad();
+            }
         }
     }
 
     @Override
     public void onStop() {
         super.onStop();
-        mAdapter.resetPartitions();
-        mInitialLoadComplete = false;
+        mAdapter.clearPartitions();
     }
 
     /**
@@ -476,14 +363,15 @@
     public void setSearchMode(boolean flag) {
         if (mSearchMode != flag) {
             mSearchMode = flag;
-            // TODO not always
             setSectionHeaderDisplayEnabled(!mSearchMode);
+
             if (mAdapter != null) {
-                stopLoading();
+                mAdapter.clearPartitions();
                 mAdapter.setSearchMode(flag);
                 mAdapter.setPinnedPartitionHeadersEnabled(flag);
-                mInitialLoadComplete = false;
+                reloadData();
             }
+
             if (mListView != null) {
                 mListView.setFastScrollEnabled(!flag);
             }
@@ -665,6 +553,7 @@
         if (mAdapter == null) {
             return;
         }
+
         mAdapter.setQueryString(mQueryString);
         mAdapter.setPinnedPartitionHeadersEnabled(mSearchMode);
         mAdapter.setContactNameDisplayOrder(mDisplayOrder);
diff --git a/src/com/android/contacts/list/DirectoryPartition.java b/src/com/android/contacts/list/DirectoryPartition.java
index b76b97a..d7cb9bc 100644
--- a/src/com/android/contacts/list/DirectoryPartition.java
+++ b/src/com/android/contacts/list/DirectoryPartition.java
@@ -15,19 +15,23 @@
  */
 package com.android.contacts.list;
 
-import android.database.Cursor;
+import com.android.contacts.widget.CompositeCursorAdapter;
+
 import android.provider.ContactsContract.Directory;
 
 /**
  * Model object for a {@link Directory} row.
  */
-public final class DirectoryPartition {
+public final class DirectoryPartition extends CompositeCursorAdapter.Partition {
     private long mDirectoryId;
-    private int mPartitionIndex;
     private String mDirectoryType;
     private String mDisplayName;
-    private boolean mShowIfEmpty;
     private boolean mLoading;
+    private boolean mPriorityDirectory;
+
+    public DirectoryPartition(boolean showIfEmpty, boolean hasHeader) {
+        super(showIfEmpty, hasHeader);
+    }
 
     /**
      * Directory ID, see {@link Directory}.
@@ -41,17 +45,6 @@
     }
 
     /**
-     * Corresponding loader ID.
-     */
-    public int getPartitionIndex() {
-        return mPartitionIndex;
-    }
-
-    public void setPartitionIndex(int partitionIndex) {
-        this.mPartitionIndex = partitionIndex;
-    }
-
-    /**
      * Directory type resolved from {@link Directory#PACKAGE_NAME} and
      * {@link Directory#TYPE_RESOURCE_ID};
      */
@@ -74,17 +67,6 @@
         this.mDisplayName = displayName;
     }
 
-    /**
-     * True if the directory should be shown even if no contacts are found.
-     */
-    public boolean getShowIfEmpty() {
-        return mShowIfEmpty;
-    }
-
-    public void setShowIfEmpty(boolean showIfEmpty) {
-        this.mShowIfEmpty = showIfEmpty;
-    }
-
     public boolean isLoading() {
         return mLoading;
     }
@@ -92,4 +74,15 @@
     public void setLoading(boolean loading) {
         mLoading = loading;
     }
+
+    /**
+     * Returns true if this directory should be loaded before non-priority directories.
+     */
+    public boolean isPriorityDirectory() {
+        return mPriorityDirectory;
+    }
+
+    public void setPriorityDirectory(boolean priorityDirectory) {
+        mPriorityDirectory = priorityDirectory;
+    }
 }
diff --git a/src/com/android/contacts/widget/CompositeCursorAdapter.java b/src/com/android/contacts/widget/CompositeCursorAdapter.java
index c6d2ea3..147ed42 100644
--- a/src/com/android/contacts/widget/CompositeCursorAdapter.java
+++ b/src/com/android/contacts/widget/CompositeCursorAdapter.java
@@ -29,18 +29,29 @@
 
     private static final int INITIAL_CAPACITY = 2;
 
-    private static class Partition {
-        final boolean showIfEmpty;
-        final boolean hasHeader;
+    public static class Partition {
+        boolean showIfEmpty;
+        boolean hasHeader;
 
-        int count;
         Cursor cursor;
         int idColumnIndex;
+        int count;
 
         public Partition(boolean showIfEmpty, boolean hasHeader) {
             this.showIfEmpty = showIfEmpty;
             this.hasHeader = hasHeader;
         }
+
+        /**
+         * True if the directory should be shown even if no contacts are found.
+         */
+        public boolean getShowIfEmpty() {
+            return showIfEmpty;
+        }
+
+        public boolean getHasHeader() {
+            return hasHeader;
+        }
     }
 
     private final Context mContext;
@@ -68,29 +79,65 @@
      * list.
      */
     public void addPartition(boolean showIfEmpty, boolean hasHeader) {
+        addPartition(new Partition(showIfEmpty, hasHeader));
+    }
+
+    public void addPartition(Partition partition) {
         if (mSize >= mPartitions.length) {
             int newCapacity = mSize + 2;
             Partition[] newAdapters = new Partition[newCapacity];
             System.arraycopy(mPartitions, 0, newAdapters, 0, mSize);
             mPartitions = newAdapters;
         }
-        mPartitions[mSize++] = new Partition(showIfEmpty, hasHeader);
+        mPartitions[mSize++] = partition;
         invalidate();
         notifyDataSetChanged();
     }
 
-    public void resetPartitions() {
+    public void removePartition(int partitionIndex) {
+        Cursor cursor = mPartitions[partitionIndex].cursor;
+        if (cursor != null && !cursor.isClosed()) {
+            cursor.close();
+        }
+
+        System.arraycopy(mPartitions, partitionIndex + 1, mPartitions, partitionIndex, mSize - 1);
+        mSize--;
+        invalidate();
+        notifyDataSetChanged();
+    }
+
+    /**
+     * Removes cursors for all partitions, closing them as necessary.
+     */
+    public void clearPartitions() {
         for (int i = 0; i < mSize; i++) {
             Cursor cursor = mPartitions[i].cursor;
             if (cursor != null && !cursor.isClosed()) {
                 cursor.close();
             }
+            mPartitions[i].cursor = null;
         }
-        mSize = 0;
         invalidate();
         notifyDataSetChanged();
     }
 
+    public void setHasHeader(int partitionIndex, boolean flag) {
+        mPartitions[partitionIndex].hasHeader = flag;
+        invalidate();
+    }
+
+    public void setShowIfEmpty(int partitionIndex, boolean flag) {
+        mPartitions[partitionIndex].showIfEmpty = flag;
+        invalidate();
+    }
+
+    public Partition getPartition(int partitionIndex) {
+        if (partitionIndex >= mSize) {
+            throw new ArrayIndexOutOfBoundsException(partitionIndex);
+        }
+        return mPartitions[partitionIndex];
+    }
+
     protected void invalidate() {
         mCacheValid = false;
     }