Moving ContactListItemView and dependent classes.

Further clean-up of PhoneFavoriteFragment in Dialer app to move all necessary
dependencies into Contacts Common package.

Bug: 6993891
Change-Id: I3dfce84ad01932450dc09588c22903e7307d7da0
diff --git a/src/com/android/contacts/GroupMemberLoader.java b/src/com/android/contacts/GroupMemberLoader.java
index 12e82ce..e52ddda 100644
--- a/src/com/android/contacts/GroupMemberLoader.java
+++ b/src/com/android/contacts/GroupMemberLoader.java
@@ -24,7 +24,7 @@
 import android.provider.ContactsContract.Data;
 import android.provider.ContactsContract.Directory;
 
-import com.android.contacts.preference.ContactsPreferences;
+import com.android.contacts.common.preference.ContactsPreferences;
 
 import java.util.ArrayList;
 import java.util.List;
diff --git a/src/com/android/contacts/activities/ContactSelectionActivity.java b/src/com/android/contacts/activities/ContactSelectionActivity.java
index c60a228..6822494 100644
--- a/src/com/android/contacts/activities/ContactSelectionActivity.java
+++ b/src/com/android/contacts/activities/ContactSelectionActivity.java
@@ -46,7 +46,7 @@
 import com.android.contacts.list.ContactPickerFragment;
 import com.android.contacts.list.ContactsIntentResolver;
 import com.android.contacts.list.ContactsRequest;
-import com.android.contacts.list.DirectoryListLoader;
+import com.android.contacts.common.list.DirectoryListLoader;
 import com.android.contacts.list.EmailAddressPickerFragment;
 import com.android.contacts.list.OnContactPickerActionListener;
 import com.android.contacts.list.OnEmailAddressPickerActionListener;
diff --git a/src/com/android/contacts/activities/PeopleActivity.java b/src/com/android/contacts/activities/PeopleActivity.java
index b607408..792348f 100644
--- a/src/com/android/contacts/activities/PeopleActivity.java
+++ b/src/com/android/contacts/activities/PeopleActivity.java
@@ -65,7 +65,7 @@
 import com.android.contacts.interactions.ImportExportDialogFragment;
 import com.android.contacts.list.ContactBrowseListFragment;
 import com.android.contacts.list.ContactEntryListFragment;
-import com.android.contacts.list.ContactListFilter;
+import com.android.contacts.common.list.ContactListFilter;
 import com.android.contacts.list.ContactListFilterController;
 import com.android.contacts.list.ContactTileAdapter.DisplayType;
 import com.android.contacts.list.ContactTileFrequentFragment;
@@ -74,7 +74,7 @@
 import com.android.contacts.list.ContactsRequest;
 import com.android.contacts.list.ContactsUnavailableFragment;
 import com.android.contacts.list.DefaultContactBrowseListFragment;
-import com.android.contacts.list.DirectoryListLoader;
+import com.android.contacts.common.list.DirectoryListLoader;
 import com.android.contacts.list.OnContactBrowserActionListener;
 import com.android.contacts.list.OnContactsUnavailableActionListener;
 import com.android.contacts.list.ProviderStatusWatcher;
diff --git a/src/com/android/contacts/detail/ContactDetailDisplayUtils.java b/src/com/android/contacts/detail/ContactDetailDisplayUtils.java
index bda4918..12b63c7 100644
--- a/src/com/android/contacts/detail/ContactDetailDisplayUtils.java
+++ b/src/com/android/contacts/detail/ContactDetailDisplayUtils.java
@@ -45,7 +45,7 @@
 import com.android.contacts.model.RawContact;
 import com.android.contacts.model.dataitem.DataItem;
 import com.android.contacts.model.dataitem.OrganizationDataItem;
-import com.android.contacts.preference.ContactsPreferences;
+import com.android.contacts.common.preference.ContactsPreferences;
 import com.android.contacts.util.ContactBadgeUtil;
 import com.android.contacts.util.HtmlUtils;
 import com.android.contacts.util.MoreMath;
diff --git a/src/com/android/contacts/group/GroupBrowseListFragment.java b/src/com/android/contacts/group/GroupBrowseListFragment.java
index 625615f..d6ca3d7 100644
--- a/src/com/android/contacts/group/GroupBrowseListFragment.java
+++ b/src/com/android/contacts/group/GroupBrowseListFragment.java
@@ -46,7 +46,7 @@
 import com.android.contacts.GroupListLoader;
 import com.android.contacts.R;
 import com.android.contacts.group.GroupBrowseListAdapter.GroupListItemViewCache;
-import com.android.contacts.widget.AutoScrollListView;
+import com.android.contacts.common.list.AutoScrollListView;
 
 /**
  * Fragment to display the list of groups.
diff --git a/src/com/android/contacts/list/AccountFilterActivity.java b/src/com/android/contacts/list/AccountFilterActivity.java
index af727a5..18d703f 100644
--- a/src/com/android/contacts/list/AccountFilterActivity.java
+++ b/src/com/android/contacts/list/AccountFilterActivity.java
@@ -36,6 +36,7 @@
 
 import com.android.contacts.ContactsActivity;
 import com.android.contacts.R;
+import com.android.contacts.common.list.ContactListFilter;
 import com.android.contacts.model.AccountTypeManager;
 import com.android.contacts.model.account.AccountType;
 import com.android.contacts.model.account.AccountWithDataSet;
diff --git a/src/com/android/contacts/list/ContactBrowseListFragment.java b/src/com/android/contacts/list/ContactBrowseListFragment.java
index da4a644..8ea8c1c 100644
--- a/src/com/android/contacts/list/ContactBrowseListFragment.java
+++ b/src/com/android/contacts/list/ContactBrowseListFragment.java
@@ -36,8 +36,11 @@
 
 import com.android.common.widget.CompositeCursorAdapter.Partition;
 import com.android.contacts.R;
+import com.android.contacts.common.list.ContactListAdapter;
+import com.android.contacts.common.list.ContactListFilter;
+import com.android.contacts.common.list.DirectoryPartition;
 import com.android.contacts.util.ContactLoaderUtils;
-import com.android.contacts.widget.AutoScrollListView;
+import com.android.contacts.common.list.AutoScrollListView;
 
 import java.util.List;
 
diff --git a/src/com/android/contacts/list/ContactEntryListAdapter.java b/src/com/android/contacts/list/ContactEntryListAdapter.java
deleted file mode 100644
index 1f0373f..0000000
--- a/src/com/android/contacts/list/ContactEntryListAdapter.java
+++ /dev/null
@@ -1,678 +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.list;
-
-import android.content.Context;
-import android.content.CursorLoader;
-import android.database.Cursor;
-import android.net.Uri;
-import android.os.Bundle;
-import android.provider.ContactsContract;
-import android.provider.ContactsContract.ContactCounts;
-import android.provider.ContactsContract.Contacts;
-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.QuickContactBadge;
-import android.widget.SectionIndexer;
-import android.widget.TextView;
-
-import com.android.contacts.common.ContactPhotoManager;
-import com.android.contacts.R;
-import com.android.contacts.widget.IndexerListAdapter;
-
-import java.util.HashSet;
-
-/**
- * Common base class for various contact-related lists, e.g. contact list, phone number list
- * etc.
- */
-public abstract class ContactEntryListAdapter extends IndexerListAdapter {
-
-    private static final String TAG = "ContactEntryListAdapter";
-
-    /**
-     * Indicates whether the {@link Directory#LOCAL_INVISIBLE} directory should
-     * be included in the search.
-     */
-    private static final boolean LOCAL_INVISIBLE_DIRECTORY_ENABLED = false;
-
-    private int mDisplayOrder;
-    private int mSortOrder;
-
-    private boolean mDisplayPhotos;
-    private boolean mQuickContactEnabled;
-
-    /**
-     * indicates if contact queries include profile
-     */
-    private boolean mIncludeProfile;
-
-    /**
-     * indicates if query results includes a profile
-     */
-    private boolean mProfileExists;
-
-    private ContactPhotoManager mPhotoLoader;
-
-    private String mQueryString;
-    private char[] mUpperCaseQueryString;
-    private boolean mSearchMode;
-    private int mDirectorySearchMode;
-    private int mDirectoryResultLimit = Integer.MAX_VALUE;
-
-    private boolean mLoading = true;
-    private boolean mEmptyListEnabled = true;
-
-    private boolean mSelectionVisible;
-
-    private ContactListFilter mFilter;
-    private String mContactsCount = "";
-    private boolean mDarkTheme = false;
-
-    /** Resource used to provide header-text for default filter. */
-    private CharSequence mDefaultFilterHeaderText;
-
-    public ContactEntryListAdapter(Context context) {
-        super(context);
-        addPartitions();
-        setDefaultFilterHeaderText(R.string.local_search_label);
-    }
-
-    protected void setDefaultFilterHeaderText(int resourceId) {
-        mDefaultFilterHeaderText = getContext().getResources().getText(resourceId);
-    }
-
-    @Override
-    protected View createPinnedSectionHeaderView(Context context, ViewGroup parent) {
-        return new ContactListPinnedHeaderView(context, null);
-    }
-
-    @Override
-    protected void setPinnedSectionTitle(View pinnedHeaderView, String title) {
-        ((ContactListPinnedHeaderView)pinnedHeaderView).setSectionHeader(title);
-    }
-
-    @Override
-    protected void setPinnedHeaderContactsCount(View header) {
-        // Update the header with the contacts count only if a profile header exists
-        // otherwise, the contacts count are shown in the empty profile header view
-        if (mProfileExists) {
-            ((ContactListPinnedHeaderView)header).setCountView(mContactsCount);
-        } else {
-            clearPinnedHeaderContactsCount(header);
-        }
-    }
-
-    @Override
-    protected void clearPinnedHeaderContactsCount(View header) {
-        ((ContactListPinnedHeaderView)header).setCountView(null);
-    }
-
-    protected void addPartitions() {
-        addPartition(createDefaultDirectoryPartition());
-    }
-
-    protected DirectoryPartition createDefaultDirectoryPartition() {
-        DirectoryPartition partition = new DirectoryPartition(true, true);
-        partition.setDirectoryId(Directory.DEFAULT);
-        partition.setDirectoryType(getContext().getString(R.string.contactsList));
-        partition.setPriorityDirectory(true);
-        partition.setPhotoSupported(true);
-        return partition;
-    }
-
-    /**
-     * Remove all directories after the default directory. This is typically used when contacts
-     * list screens are asked to exit the search mode and thus need to remove all remote directory
-     * results for the search.
-     *
-     * This code assumes that the default directory and directories before that should not be
-     * deleted (e.g. Join screen has "suggested contacts" directory before the default director,
-     * and we should not remove the directory).
-     */
-    /* package */ void removeDirectoriesAfterDefault() {
-        final int partitionCount = getPartitionCount();
-        for (int i = partitionCount - 1; i >= 0; i--) {
-            final Partition partition = getPartition(i);
-            if ((partition instanceof DirectoryPartition)
-                    && ((DirectoryPartition) partition).getDirectoryId() == Directory.DEFAULT) {
-                break;
-            } else {
-                removePartition(i);
-            }
-        }
-    }
-
-    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;
-                }
-            }
-        }
-        return -1;
-    }
-
-    public abstract String getContactDisplayName(int position);
-    public abstract void configureLoader(CursorLoader loader, long directoryId);
-
-    /**
-     * Marks all partitions as "loading"
-     */
-    public void onDataReload() {
-        boolean notify = false;
-        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()) {
-                    notify = true;
-                }
-                directoryPartition.setStatus(DirectoryPartition.STATUS_NOT_LOADED);
-            }
-        }
-        if (notify) {
-            notifyDataSetChanged();
-        }
-    }
-
-    @Override
-    public void clearPartitions() {
-        int count = getPartitionCount();
-        for (int i = 0; i < count; i++) {
-            Partition partition = getPartition(i);
-            if (partition instanceof DirectoryPartition) {
-                DirectoryPartition directoryPartition = (DirectoryPartition)partition;
-                directoryPartition.setStatus(DirectoryPartition.STATUS_NOT_LOADED);
-            }
-        }
-        super.clearPartitions();
-    }
-
-    public boolean isSearchMode() {
-        return mSearchMode;
-    }
-
-    public void setSearchMode(boolean flag) {
-        mSearchMode = flag;
-    }
-
-    public String getQueryString() {
-        return mQueryString;
-    }
-
-    public void setQueryString(String queryString) {
-        mQueryString = queryString;
-        if (TextUtils.isEmpty(queryString)) {
-            mUpperCaseQueryString = null;
-        } else {
-            mUpperCaseQueryString = queryString.toUpperCase().toCharArray();
-        }
-    }
-
-    public char[] getUpperCaseQueryString() {
-        return mUpperCaseQueryString;
-    }
-
-    public int getDirectorySearchMode() {
-        return mDirectorySearchMode;
-    }
-
-    public void setDirectorySearchMode(int mode) {
-        mDirectorySearchMode = mode;
-    }
-
-    public int getDirectoryResultLimit() {
-        return mDirectoryResultLimit;
-    }
-
-    public void setDirectoryResultLimit(int limit) {
-        this.mDirectoryResultLimit = limit;
-    }
-
-    public int getContactNameDisplayOrder() {
-        return mDisplayOrder;
-    }
-
-    public void setContactNameDisplayOrder(int displayOrder) {
-        mDisplayOrder = displayOrder;
-    }
-
-    public int getSortOrder() {
-        return mSortOrder;
-    }
-
-    public void setSortOrder(int sortOrder) {
-        mSortOrder = sortOrder;
-    }
-
-    public void setPhotoLoader(ContactPhotoManager photoLoader) {
-        mPhotoLoader = photoLoader;
-    }
-
-    protected ContactPhotoManager getPhotoLoader() {
-        return mPhotoLoader;
-    }
-
-    public boolean getDisplayPhotos() {
-        return mDisplayPhotos;
-    }
-
-    public void setDisplayPhotos(boolean displayPhotos) {
-        mDisplayPhotos = displayPhotos;
-    }
-
-    public boolean isEmptyListEnabled() {
-        return mEmptyListEnabled;
-    }
-
-    public void setEmptyListEnabled(boolean flag) {
-        mEmptyListEnabled = flag;
-    }
-
-    public boolean isSelectionVisible() {
-        return mSelectionVisible;
-    }
-
-    public void setSelectionVisible(boolean flag) {
-        this.mSelectionVisible = flag;
-    }
-
-    public boolean isQuickContactEnabled() {
-        return mQuickContactEnabled;
-    }
-
-    public void setQuickContactEnabled(boolean quickContactEnabled) {
-        mQuickContactEnabled = quickContactEnabled;
-    }
-
-    public boolean shouldIncludeProfile() {
-        return mIncludeProfile;
-    }
-
-    public void setIncludeProfile(boolean includeProfile) {
-        mIncludeProfile = includeProfile;
-    }
-
-    public void setProfileExists(boolean exists) {
-        mProfileExists = exists;
-        // Stick the "ME" header for the profile
-        if (exists) {
-            SectionIndexer indexer = getIndexer();
-            if (indexer != null) {
-                ((ContactsSectionIndexer) indexer).setProfileHeader(
-                        getContext().getString(R.string.user_profile_contacts_list_header));
-            }
-        }
-    }
-
-    public boolean hasProfile() {
-        return mProfileExists;
-    }
-
-    public void setDarkTheme(boolean value) {
-        mDarkTheme = value;
-    }
-
-    public void configureDirectoryLoader(DirectoryListLoader loader) {
-        loader.setDirectorySearchMode(mDirectorySearchMode);
-        loader.setLocalInvisibleDirectoryEnabled(LOCAL_INVISIBLE_DIRECTORY_ENABLED);
-    }
-
-    /**
-     * Updates partitions according to the directory meta-data contained in the supplied
-     * cursor.
-     */
-    public void changeDirectories(Cursor cursor) {
-        if (cursor.getCount() == 0) {
-            // Directory table must have at least local directory, without which this adapter will
-            // enter very weird state.
-            Log.e(TAG, "Directory search loader returned an empty cursor, which implies we have " +
-                    "no directory entries.", new RuntimeException());
-            return;
-        }
-        HashSet<Long> directoryIds = new HashSet<Long>();
-
-        int idColumnIndex = cursor.getColumnIndex(Directory._ID);
-        int directoryTypeColumnIndex = cursor.getColumnIndex(DirectoryListLoader.DIRECTORY_TYPE);
-        int displayNameColumnIndex = cursor.getColumnIndex(Directory.DISPLAY_NAME);
-        int photoSupportColumnIndex = cursor.getColumnIndex(Directory.PHOTO_SUPPORT);
-
-        // TODO preserve the order of partition to match those of the cursor
-        // Phase I: add new directories
-        cursor.moveToPosition(-1);
-        while (cursor.moveToNext()) {
-            long id = cursor.getLong(idColumnIndex);
-            directoryIds.add(id);
-            if (getPartitionByDirectoryId(id) == -1) {
-                DirectoryPartition partition = new DirectoryPartition(false, true);
-                partition.setDirectoryId(id);
-                partition.setDirectoryType(cursor.getString(directoryTypeColumnIndex));
-                partition.setDisplayName(cursor.getString(displayNameColumnIndex));
-                int photoSupport = cursor.getInt(photoSupportColumnIndex);
-                partition.setPhotoSupported(photoSupport == Directory.PHOTO_SUPPORT_THUMBNAIL_ONLY
-                        || photoSupport == Directory.PHOTO_SUPPORT_FULL);
-                addPartition(partition);
-            }
-        }
-
-        // 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();
-    }
-
-    @Override
-    public void changeCursor(int partitionIndex, Cursor cursor) {
-        if (partitionIndex >= getPartitionCount()) {
-            // There is no partition for this data
-            return;
-        }
-
-        Partition partition = getPartition(partitionIndex);
-        if (partition instanceof DirectoryPartition) {
-            ((DirectoryPartition)partition).setStatus(DirectoryPartition.STATUS_LOADED);
-        }
-
-        if (mDisplayPhotos && mPhotoLoader != null && isPhotoSupported(partitionIndex)) {
-            mPhotoLoader.refreshCache();
-        }
-
-        super.changeCursor(partitionIndex, cursor);
-
-        if (isSectionHeaderDisplayEnabled() && partitionIndex == getIndexedPartition()) {
-            updateIndexer(cursor);
-        }
-    }
-
-    public void changeCursor(Cursor cursor) {
-        changeCursor(0, cursor);
-    }
-
-    /**
-     * Updates the indexer, which is used to produce section headers.
-     */
-    private void updateIndexer(Cursor cursor) {
-        if (cursor == null) {
-            setIndexer(null);
-            return;
-        }
-
-        Bundle bundle = cursor.getExtras();
-        if (bundle.containsKey(ContactCounts.EXTRA_ADDRESS_BOOK_INDEX_TITLES)) {
-            String sections[] =
-                    bundle.getStringArray(ContactCounts.EXTRA_ADDRESS_BOOK_INDEX_TITLES);
-            int counts[] = bundle.getIntArray(ContactCounts.EXTRA_ADDRESS_BOOK_INDEX_COUNTS);
-            setIndexer(new ContactsSectionIndexer(sections, counts));
-        } else {
-            setIndexer(null);
-        }
-    }
-
-    @Override
-    public int getViewTypeCount() {
-        // We need a separate view type for each item type, plus another one for
-        // each type with header, plus one for "other".
-        return getItemViewTypeCount() * 2 + 1;
-    }
-
-    @Override
-    public int getItemViewType(int partitionIndex, int position) {
-        int type = super.getItemViewType(partitionIndex, position);
-        if (!isUserProfile(position)
-                && isSectionHeaderDisplayEnabled()
-                && partitionIndex == getIndexedPartition()) {
-            Placement placement = getItemPlacementInSection(position);
-            return placement.firstInSection ? type : getItemViewTypeCount() + type;
-        } else {
-            return type;
-        }
-    }
-
-    @Override
-    public boolean isEmpty() {
-        // TODO
-//        if (contactsListActivity.mProviderStatus != ProviderStatus.STATUS_NORMAL) {
-//            return true;
-//        }
-
-        if (!mEmptyListEnabled) {
-            return false;
-        } else if (isSearchMode()) {
-            return TextUtils.isEmpty(getQueryString());
-        } else if (mLoading) {
-            // We don't want the empty state to show when loading.
-            return false;
-        } else {
-            return super.isEmpty();
-        }
-    }
-
-    public boolean isLoading() {
-        int count = getPartitionCount();
-        for (int i = 0; i < count; i++) {
-            Partition partition = getPartition(i);
-            if (partition instanceof DirectoryPartition
-                    && ((DirectoryPartition) partition).isLoading()) {
-                return true;
-            }
-        }
-        return false;
-    }
-
-    public boolean areAllPartitionsEmpty() {
-        int count = getPartitionCount();
-        for (int i = 0; i < count; i++) {
-            if (!isPartitionEmpty(i)) {
-                return false;
-            }
-        }
-        return true;
-    }
-
-    /**
-     * Changes visibility parameters for the default directory partition.
-     */
-    public void configureDefaultPartition(boolean showIfEmpty, boolean hasHeader) {
-        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, showIfEmpty);
-            setHasHeader(defaultPartitionIndex, hasHeader);
-        }
-    }
-
-    @Override
-    protected View newHeaderView(Context context, int partition, Cursor cursor,
-            ViewGroup parent) {
-        LayoutInflater inflater = LayoutInflater.from(context);
-        return inflater.inflate(R.layout.directory_header, parent, false);
-    }
-
-    @Override
-    protected void bindHeaderView(View view, int partitionIndex, Cursor cursor) {
-        Partition partition = getPartition(partitionIndex);
-        if (!(partition instanceof DirectoryPartition)) {
-            return;
-        }
-
-        DirectoryPartition directoryPartition = (DirectoryPartition)partition;
-        long directoryId = directoryPartition.getDirectoryId();
-        TextView labelTextView = (TextView)view.findViewById(R.id.label);
-        TextView displayNameTextView = (TextView)view.findViewById(R.id.display_name);
-        if (directoryId == Directory.DEFAULT || directoryId == Directory.LOCAL_INVISIBLE) {
-            labelTextView.setText(mDefaultFilterHeaderText);
-            displayNameTextView.setText(null);
-        } else {
-            labelTextView.setText(R.string.directory_search_label);
-            String directoryName = directoryPartition.getDisplayName();
-            String displayName = !TextUtils.isEmpty(directoryName)
-                    ? directoryName
-                    : directoryPartition.getDirectoryType();
-            displayNameTextView.setText(displayName);
-        }
-
-        TextView countText = (TextView)view.findViewById(R.id.count);
-        if (directoryPartition.isLoading()) {
-            countText.setText(R.string.search_results_searching);
-        } else {
-            int count = cursor == null ? 0 : cursor.getCount();
-            if (directoryId != Directory.DEFAULT && directoryId != Directory.LOCAL_INVISIBLE
-                    && count >= getDirectoryResultLimit()) {
-                countText.setText(mContext.getString(
-                        R.string.foundTooManyContacts, getDirectoryResultLimit()));
-            } else {
-                countText.setText(getQuantityText(
-                        count, R.string.listFoundAllContactsZero, R.plurals.searchFoundContacts));
-            }
-        }
-    }
-
-    /**
-     * Checks whether the contact entry at the given position represents the user's profile.
-     */
-    protected boolean isUserProfile(int position) {
-        // The profile only ever appears in the first position if it is present.  So if the position
-        // is anything beyond 0, it can't be the profile.
-        boolean isUserProfile = false;
-        if (position == 0) {
-            int partition = getPartitionForPosition(position);
-            if (partition >= 0) {
-                // Save the old cursor position - the call to getItem() may modify the cursor
-                // position.
-                int offset = getCursor(partition).getPosition();
-                Cursor cursor = (Cursor) getItem(position);
-                if (cursor != null) {
-                    int profileColumnIndex = cursor.getColumnIndex(Contacts.IS_USER_PROFILE);
-                    if (profileColumnIndex != -1) {
-                        isUserProfile = cursor.getInt(profileColumnIndex) == 1;
-                    }
-                    // Restore the old cursor position.
-                    cursor.moveToPosition(offset);
-                }
-            }
-        }
-        return isUserProfile;
-    }
-
-    // TODO: fix PluralRules to handle zero correctly and use Resources.getQuantityText directly
-    public String getQuantityText(int count, int zeroResourceId, int pluralResourceId) {
-        if (count == 0) {
-            return getContext().getString(zeroResourceId);
-        } else {
-            String format = getContext().getResources()
-                    .getQuantityText(pluralResourceId, count).toString();
-            return String.format(format, count);
-        }
-    }
-
-    public boolean isPhotoSupported(int partitionIndex) {
-        Partition partition = getPartition(partitionIndex);
-        if (partition instanceof DirectoryPartition) {
-            return ((DirectoryPartition) partition).isPhotoSupported();
-        }
-        return true;
-    }
-
-    /**
-     * Returns the currently selected filter.
-     */
-    public ContactListFilter getFilter() {
-        return mFilter;
-    }
-
-    public void setFilter(ContactListFilter filter) {
-        mFilter = filter;
-    }
-
-    // TODO: move sharable logic (bindXX() methods) to here with extra arguments
-
-    /**
-     * Loads the photo for the quick contact view and assigns the contact uri.
-     * @param photoIdColumn Index of the photo id column
-     * @param photoUriColumn Index of the photo uri column. Optional: Can be -1
-     * @param contactIdColumn Index of the contact id column
-     * @param lookUpKeyColumn Index of the lookup key column
-     */
-    protected void bindQuickContact(final ContactListItemView view, int partitionIndex,
-            Cursor cursor, int photoIdColumn, int photoUriColumn, int contactIdColumn,
-            int lookUpKeyColumn) {
-        long photoId = 0;
-        if (!cursor.isNull(photoIdColumn)) {
-            photoId = cursor.getLong(photoIdColumn);
-        }
-
-        QuickContactBadge quickContact = view.getQuickContact();
-        quickContact.assignContactUri(
-                getContactUri(partitionIndex, cursor, contactIdColumn, lookUpKeyColumn));
-
-        if (photoId != 0 || photoUriColumn == -1) {
-            getPhotoLoader().loadThumbnail(quickContact, photoId, mDarkTheme);
-        } else {
-            final String photoUriString = cursor.getString(photoUriColumn);
-            final Uri photoUri = photoUriString == null ? null : Uri.parse(photoUriString);
-            getPhotoLoader().loadPhoto(quickContact, photoUri, -1, mDarkTheme);
-        }
-
-    }
-
-    protected Uri getContactUri(int partitionIndex, Cursor cursor,
-            int contactIdColumn, int lookUpKeyColumn) {
-        long contactId = cursor.getLong(contactIdColumn);
-        String lookupKey = cursor.getString(lookUpKeyColumn);
-        Uri uri = Contacts.getLookupUri(contactId, lookupKey);
-        long directoryId = ((DirectoryPartition)getPartition(partitionIndex)).getDirectoryId();
-        if (directoryId != Directory.DEFAULT) {
-            uri = uri.buildUpon().appendQueryParameter(
-                    ContactsContract.DIRECTORY_PARAM_KEY, String.valueOf(directoryId)).build();
-        }
-        return uri;
-    }
-
-    public void setContactsCount(String count) {
-        mContactsCount = count;
-    }
-
-    public String getContactsCount() {
-        return mContactsCount;
-    }
-}
diff --git a/src/com/android/contacts/list/ContactEntryListFragment.java b/src/com/android/contacts/list/ContactEntryListFragment.java
index 7efc57a..d522b5e 100644
--- a/src/com/android/contacts/list/ContactEntryListFragment.java
+++ b/src/com/android/contacts/list/ContactEntryListFragment.java
@@ -55,9 +55,12 @@
 
 import com.android.common.widget.CompositeCursorAdapter.Partition;
 import com.android.contacts.ContactListEmptyView;
-import com.android.contacts.common.ContactPhotoManager;
 import com.android.contacts.R;
-import com.android.contacts.preference.ContactsPreferences;
+import com.android.contacts.common.ContactPhotoManager;
+import com.android.contacts.common.list.ContactEntryListAdapter;
+import com.android.contacts.common.list.DirectoryListLoader;
+import com.android.contacts.common.list.DirectoryPartition;
+import com.android.contacts.common.preference.ContactsPreferences;
 import com.android.contacts.widget.ContextMenuAdapter;
 
 /**
@@ -328,7 +331,9 @@
     public Loader<Cursor> onCreateLoader(int id, Bundle args) {
         if (id == DIRECTORY_LOADER_ID) {
             DirectoryListLoader loader = new DirectoryListLoader(mContext);
-            mAdapter.configureDirectoryLoader(loader);
+            loader.setDirectorySearchMode(mAdapter.getDirectorySearchMode());
+            loader.setLocalInvisibleDirectoryEnabled(
+                    ContactEntryListAdapter.LOCAL_INVISIBLE_DIRECTORY_ENABLED);
             return loader;
         } else {
             CursorLoader loader = createCursorLoader();
diff --git a/src/com/android/contacts/list/ContactListAdapter.java b/src/com/android/contacts/list/ContactListAdapter.java
deleted file mode 100644
index 1eb2dae..0000000
--- a/src/com/android/contacts/list/ContactListAdapter.java
+++ /dev/null
@@ -1,362 +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.list;
-
-import android.content.Context;
-import android.database.Cursor;
-import android.net.Uri;
-import android.provider.ContactsContract;
-import android.provider.ContactsContract.ContactCounts;
-import android.provider.ContactsContract.Contacts;
-import android.provider.ContactsContract.Directory;
-import android.provider.ContactsContract.SearchSnippetColumns;
-import android.text.TextUtils;
-import android.view.View;
-import android.view.ViewGroup;
-import android.widget.ListView;
-
-import com.android.contacts.R;
-
-/**
- * A cursor adapter for the {@link ContactsContract.Contacts#CONTENT_TYPE} content type.
- * Also includes support for including the {@link ContactsContract.Profile} record in the
- * list.
- */
-public abstract class ContactListAdapter extends ContactEntryListAdapter {
-
-    protected static class ContactQuery {
-        private static final String[] CONTACT_PROJECTION_PRIMARY = new String[] {
-            Contacts._ID,                           // 0
-            Contacts.DISPLAY_NAME_PRIMARY,          // 1
-            Contacts.CONTACT_PRESENCE,              // 2
-            Contacts.CONTACT_STATUS,                // 3
-            Contacts.PHOTO_ID,                      // 4
-            Contacts.PHOTO_THUMBNAIL_URI,           // 5
-            Contacts.LOOKUP_KEY,                    // 6
-            Contacts.IS_USER_PROFILE,               // 7
-        };
-
-        private static final String[] CONTACT_PROJECTION_ALTERNATIVE = new String[] {
-            Contacts._ID,                           // 0
-            Contacts.DISPLAY_NAME_ALTERNATIVE,      // 1
-            Contacts.CONTACT_PRESENCE,              // 2
-            Contacts.CONTACT_STATUS,                // 3
-            Contacts.PHOTO_ID,                      // 4
-            Contacts.PHOTO_THUMBNAIL_URI,           // 5
-            Contacts.LOOKUP_KEY,                    // 6
-            Contacts.IS_USER_PROFILE,               // 7
-        };
-
-        private static final String[] FILTER_PROJECTION_PRIMARY = new String[] {
-            Contacts._ID,                           // 0
-            Contacts.DISPLAY_NAME_PRIMARY,          // 1
-            Contacts.CONTACT_PRESENCE,              // 2
-            Contacts.CONTACT_STATUS,                // 3
-            Contacts.PHOTO_ID,                      // 4
-            Contacts.PHOTO_THUMBNAIL_URI,           // 5
-            Contacts.LOOKUP_KEY,                    // 6
-            Contacts.IS_USER_PROFILE,               // 7
-            SearchSnippetColumns.SNIPPET,           // 8
-        };
-
-        private static final String[] FILTER_PROJECTION_ALTERNATIVE = new String[] {
-            Contacts._ID,                           // 0
-            Contacts.DISPLAY_NAME_ALTERNATIVE,      // 1
-            Contacts.CONTACT_PRESENCE,              // 2
-            Contacts.CONTACT_STATUS,                // 3
-            Contacts.PHOTO_ID,                      // 4
-            Contacts.PHOTO_THUMBNAIL_URI,           // 5
-            Contacts.LOOKUP_KEY,                    // 6
-            Contacts.IS_USER_PROFILE,               // 7
-            SearchSnippetColumns.SNIPPET,           // 8
-        };
-
-        public static final int CONTACT_ID               = 0;
-        public static final int CONTACT_DISPLAY_NAME     = 1;
-        public static final int CONTACT_PRESENCE_STATUS  = 2;
-        public static final int CONTACT_CONTACT_STATUS   = 3;
-        public static final int CONTACT_PHOTO_ID         = 4;
-        public static final int CONTACT_PHOTO_URI        = 5;
-        public static final int CONTACT_LOOKUP_KEY       = 6;
-        public static final int CONTACT_IS_USER_PROFILE  = 7;
-        public static final int CONTACT_SNIPPET          = 8;
-    }
-
-    private CharSequence mUnknownNameText;
-
-    private long mSelectedContactDirectoryId;
-    private String mSelectedContactLookupKey;
-    private long mSelectedContactId;
-
-    public ContactListAdapter(Context context) {
-        super(context);
-
-        mUnknownNameText = context.getText(R.string.missing_name);
-    }
-
-    public CharSequence getUnknownNameText() {
-        return mUnknownNameText;
-    }
-
-    public long getSelectedContactDirectoryId() {
-        return mSelectedContactDirectoryId;
-    }
-
-    public String getSelectedContactLookupKey() {
-        return mSelectedContactLookupKey;
-    }
-
-    public long getSelectedContactId() {
-        return mSelectedContactId;
-    }
-
-    public void setSelectedContact(long selectedDirectoryId, String lookupKey, long contactId) {
-        mSelectedContactDirectoryId = selectedDirectoryId;
-        mSelectedContactLookupKey = lookupKey;
-        mSelectedContactId = contactId;
-    }
-
-    protected static Uri buildSectionIndexerUri(Uri uri) {
-        return uri.buildUpon()
-                .appendQueryParameter(ContactCounts.ADDRESS_BOOK_INDEX_EXTRAS, "true").build();
-    }
-
-    @Override
-    public String getContactDisplayName(int position) {
-        return ((Cursor) getItem(position)).getString(ContactQuery.CONTACT_DISPLAY_NAME);
-    }
-
-    /**
-     * Builds the {@link Contacts#CONTENT_LOOKUP_URI} for the given
-     * {@link ListView} position.
-     */
-    public Uri getContactUri(int position) {
-        int partitionIndex = getPartitionForPosition(position);
-        Cursor item = (Cursor)getItem(position);
-        return item != null ? getContactUri(partitionIndex, item) : null;
-    }
-
-    public Uri getContactUri(int partitionIndex, Cursor cursor) {
-        long contactId = cursor.getLong(ContactQuery.CONTACT_ID);
-        String lookupKey = cursor.getString(ContactQuery.CONTACT_LOOKUP_KEY);
-        Uri uri = Contacts.getLookupUri(contactId, lookupKey);
-        long directoryId = ((DirectoryPartition)getPartition(partitionIndex)).getDirectoryId();
-        if (directoryId != Directory.DEFAULT) {
-            uri = uri.buildUpon().appendQueryParameter(
-                    ContactsContract.DIRECTORY_PARAM_KEY, String.valueOf(directoryId)).build();
-        }
-        return uri;
-    }
-
-    /**
-     * Returns true if the specified contact is selected in the list. For a
-     * contact to be shown as selected, we need both the directory and and the
-     * lookup key to be the same. We are paying no attention to the contactId,
-     * because it is volatile, especially in the case of directories.
-     */
-    public boolean isSelectedContact(int partitionIndex, Cursor cursor) {
-        long directoryId = ((DirectoryPartition)getPartition(partitionIndex)).getDirectoryId();
-        if (getSelectedContactDirectoryId() != directoryId) {
-            return false;
-        }
-        String lookupKey = getSelectedContactLookupKey();
-        if (lookupKey != null && TextUtils.equals(lookupKey,
-                cursor.getString(ContactQuery.CONTACT_LOOKUP_KEY))) {
-            return true;
-        }
-
-        return directoryId != Directory.DEFAULT && directoryId != Directory.LOCAL_INVISIBLE
-                && getSelectedContactId() == cursor.getLong(ContactQuery.CONTACT_ID);
-    }
-
-    @Override
-    protected View newView(Context context, int partition, Cursor cursor, int position,
-            ViewGroup parent) {
-        ContactListItemView view = new ContactListItemView(context, null);
-        view.setUnknownNameText(mUnknownNameText);
-        view.setQuickContactEnabled(isQuickContactEnabled());
-        view.setActivatedStateSupported(isSelectionVisible());
-        return view;
-    }
-
-    protected void bindSectionHeaderAndDivider(ContactListItemView view, int position,
-            Cursor cursor) {
-        if (isSectionHeaderDisplayEnabled()) {
-            Placement placement = getItemPlacementInSection(position);
-
-            // First position, set the contacts number string
-            if (position == 0 && cursor.getInt(ContactQuery.CONTACT_IS_USER_PROFILE) == 1) {
-                view.setCountView(getContactsCount());
-            } else {
-                view.setCountView(null);
-            }
-            view.setSectionHeader(placement.sectionHeader);
-            view.setDividerVisible(!placement.lastInSection);
-        } else {
-            view.setSectionHeader(null);
-            view.setDividerVisible(true);
-            view.setCountView(null);
-        }
-    }
-
-    protected void bindPhoto(final ContactListItemView view, int partitionIndex, Cursor cursor) {
-        if (!isPhotoSupported(partitionIndex)) {
-            view.removePhotoView();
-            return;
-        }
-
-        // Set the photo, if available
-        long photoId = 0;
-        if (!cursor.isNull(ContactQuery.CONTACT_PHOTO_ID)) {
-            photoId = cursor.getLong(ContactQuery.CONTACT_PHOTO_ID);
-        }
-
-        if (photoId != 0) {
-            getPhotoLoader().loadThumbnail(view.getPhotoView(), photoId, false);
-        } else {
-            final String photoUriString = cursor.getString(ContactQuery.CONTACT_PHOTO_URI);
-            final Uri photoUri = photoUriString == null ? null : Uri.parse(photoUriString);
-            getPhotoLoader().loadDirectoryPhoto(view.getPhotoView(), photoUri, false);
-        }
-    }
-
-    protected void bindName(final ContactListItemView view, Cursor cursor) {
-        view.showDisplayName(
-                cursor, ContactQuery.CONTACT_DISPLAY_NAME, getContactNameDisplayOrder());
-        // Note: we don't show phonetic any more (See issue 5265330)
-    }
-
-    protected void bindPresenceAndStatusMessage(final ContactListItemView view, Cursor cursor) {
-        view.showPresenceAndStatusMessage(cursor, ContactQuery.CONTACT_PRESENCE_STATUS,
-                ContactQuery.CONTACT_CONTACT_STATUS);
-    }
-
-    protected void bindSearchSnippet(final ContactListItemView view, Cursor cursor) {
-        view.showSnippet(cursor, ContactQuery.CONTACT_SNIPPET);
-    }
-
-    public int getSelectedContactPosition() {
-        if (mSelectedContactLookupKey == null && mSelectedContactId == 0) {
-            return -1;
-        }
-
-        Cursor cursor = null;
-        int partitionIndex = -1;
-        int partitionCount = getPartitionCount();
-        for (int i = 0; i < partitionCount; i++) {
-            DirectoryPartition partition = (DirectoryPartition) getPartition(i);
-            if (partition.getDirectoryId() == mSelectedContactDirectoryId) {
-                partitionIndex = i;
-                break;
-            }
-        }
-        if (partitionIndex == -1) {
-            return -1;
-        }
-
-        cursor = getCursor(partitionIndex);
-        if (cursor == null) {
-            return -1;
-        }
-
-        cursor.moveToPosition(-1);      // Reset cursor
-        int offset = -1;
-        while (cursor.moveToNext()) {
-            if (mSelectedContactLookupKey != null) {
-                String lookupKey = cursor.getString(ContactQuery.CONTACT_LOOKUP_KEY);
-                if (mSelectedContactLookupKey.equals(lookupKey)) {
-                    offset = cursor.getPosition();
-                    break;
-                }
-            }
-            if (mSelectedContactId != 0 && (mSelectedContactDirectoryId == Directory.DEFAULT
-                    || mSelectedContactDirectoryId == Directory.LOCAL_INVISIBLE)) {
-                long contactId = cursor.getLong(ContactQuery.CONTACT_ID);
-                if (contactId == mSelectedContactId) {
-                    offset = cursor.getPosition();
-                    break;
-                }
-            }
-        }
-        if (offset == -1) {
-            return -1;
-        }
-
-        int position = getPositionForPartition(partitionIndex) + offset;
-        if (hasHeader(partitionIndex)) {
-            position++;
-        }
-        return position;
-    }
-
-    public boolean hasValidSelection() {
-        return getSelectedContactPosition() != -1;
-    }
-
-    public Uri getFirstContactUri() {
-        int partitionCount = getPartitionCount();
-        for (int i = 0; i < partitionCount; i++) {
-            DirectoryPartition partition = (DirectoryPartition) getPartition(i);
-            if (partition.isLoading()) {
-                continue;
-            }
-
-            Cursor cursor = getCursor(i);
-            if (cursor == null) {
-                continue;
-            }
-
-            if (!cursor.moveToFirst()) {
-                continue;
-            }
-
-            return getContactUri(i, cursor);
-        }
-
-        return null;
-    }
-
-    @Override
-    public void changeCursor(int partitionIndex, Cursor cursor) {
-        super.changeCursor(partitionIndex, cursor);
-
-        // Check if a profile exists
-        if (cursor != null && cursor.getCount() > 0) {
-            cursor.moveToFirst();
-            setProfileExists(cursor.getInt(ContactQuery.CONTACT_IS_USER_PROFILE) == 1);
-        }
-    }
-
-    /**
-     * @return Projection useful for children.
-     */
-    protected final String[] getProjection(boolean forSearch) {
-        final int sortOrder = getContactNameDisplayOrder();
-        if (forSearch) {
-            if (sortOrder == ContactsContract.Preferences.DISPLAY_ORDER_PRIMARY) {
-                return ContactQuery.FILTER_PROJECTION_PRIMARY;
-            } else {
-                return ContactQuery.FILTER_PROJECTION_ALTERNATIVE;
-            }
-        } else {
-            if (sortOrder == ContactsContract.Preferences.DISPLAY_ORDER_PRIMARY) {
-                return ContactQuery.CONTACT_PROJECTION_PRIMARY;
-            } else {
-                return ContactQuery.CONTACT_PROJECTION_ALTERNATIVE;
-            }
-        }
-    }
-}
diff --git a/src/com/android/contacts/list/ContactListFilter.java b/src/com/android/contacts/list/ContactListFilter.java
deleted file mode 100644
index 172cbe2..0000000
--- a/src/com/android/contacts/list/ContactListFilter.java
+++ /dev/null
@@ -1,306 +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.list;
-
-import android.content.SharedPreferences;
-import android.graphics.drawable.Drawable;
-import android.net.Uri;
-import android.os.Parcel;
-import android.os.Parcelable;
-import android.provider.ContactsContract.RawContacts;
-import android.text.TextUtils;
-
-/**
- * Contact list filter parameters.
- */
-public final class ContactListFilter implements Comparable<ContactListFilter>, Parcelable {
-
-    public static final int FILTER_TYPE_DEFAULT = -1;
-    public static final int FILTER_TYPE_ALL_ACCOUNTS = -2;
-    public static final int FILTER_TYPE_CUSTOM = -3;
-    public static final int FILTER_TYPE_STARRED = -4;
-    public static final int FILTER_TYPE_WITH_PHONE_NUMBERS_ONLY = -5;
-    public static final int FILTER_TYPE_SINGLE_CONTACT = -6;
-
-    public static final int FILTER_TYPE_ACCOUNT = 0;
-
-    /**
-     * Obsolete filter which had been used in Honeycomb. This may be stored in
-     * {@link SharedPreferences}, but should be replaced with ALL filter when it is found.
-     *
-     * TODO: "group" filter and relevant variables are all obsolete. Remove them.
-     */
-    private static final int FILTER_TYPE_GROUP = 1;
-
-    private static final String KEY_FILTER_TYPE = "filter.type";
-    private static final String KEY_ACCOUNT_NAME = "filter.accountName";
-    private static final String KEY_ACCOUNT_TYPE = "filter.accountType";
-    private static final String KEY_DATA_SET = "filter.dataSet";
-
-    public final int filterType;
-    public final String accountType;
-    public final String accountName;
-    public final String dataSet;
-    public final Drawable icon;
-    private String mId;
-
-    public ContactListFilter(int filterType, String accountType, String accountName, String dataSet,
-            Drawable icon) {
-        this.filterType = filterType;
-        this.accountType = accountType;
-        this.accountName = accountName;
-        this.dataSet = dataSet;
-        this.icon = icon;
-    }
-
-    public static ContactListFilter createFilterWithType(int filterType) {
-        return new ContactListFilter(filterType, null, null, null, null);
-    }
-
-    public static ContactListFilter createAccountFilter(String accountType, String accountName,
-            String dataSet, Drawable icon) {
-        return new ContactListFilter(ContactListFilter.FILTER_TYPE_ACCOUNT, accountType,
-                accountName, dataSet, icon);
-    }
-
-    /**
-     * Returns true if this filter is based on data and may become invalid over time.
-     */
-    public boolean isValidationRequired() {
-        return filterType == FILTER_TYPE_ACCOUNT;
-    }
-
-    @Override
-    public String toString() {
-        switch (filterType) {
-            case FILTER_TYPE_DEFAULT:
-                return "default";
-            case FILTER_TYPE_ALL_ACCOUNTS:
-                return "all_accounts";
-            case FILTER_TYPE_CUSTOM:
-                return "custom";
-            case FILTER_TYPE_STARRED:
-                return "starred";
-            case FILTER_TYPE_WITH_PHONE_NUMBERS_ONLY:
-                return "with_phones";
-            case FILTER_TYPE_SINGLE_CONTACT:
-                return "single";
-            case FILTER_TYPE_ACCOUNT:
-                return "account: " + accountType + (dataSet != null ? "/" + dataSet : "")
-                        + " " + accountName;
-        }
-        return super.toString();
-    }
-
-    @Override
-    public int compareTo(ContactListFilter another) {
-        int res = accountName.compareTo(another.accountName);
-        if (res != 0) {
-            return res;
-        }
-
-        res = accountType.compareTo(another.accountType);
-        if (res != 0) {
-            return res;
-        }
-
-        return filterType - another.filterType;
-    }
-
-    @Override
-    public int hashCode() {
-        int code = filterType;
-        if (accountType != null) {
-            code = code * 31 + accountType.hashCode();
-            code = code * 31 + accountName.hashCode();
-        }
-        if (dataSet != null) {
-            code = code * 31 + dataSet.hashCode();
-        }
-        return code;
-    }
-
-    @Override
-    public boolean equals(Object other) {
-        if (this == other) {
-            return true;
-        }
-
-        if (!(other instanceof ContactListFilter)) {
-            return false;
-        }
-
-        ContactListFilter otherFilter = (ContactListFilter) other;
-        if (filterType != otherFilter.filterType
-                || !TextUtils.equals(accountName, otherFilter.accountName)
-                || !TextUtils.equals(accountType, otherFilter.accountType)
-                || !TextUtils.equals(dataSet, otherFilter.dataSet)) {
-            return false;
-        }
-
-        return true;
-    }
-
-    /**
-     * Store the given {@link ContactListFilter} to preferences. If the requested filter is
-     * of type {@link #FILTER_TYPE_SINGLE_CONTACT} then do not save it to preferences because
-     * it is a temporary state.
-     */
-    public static void storeToPreferences(SharedPreferences prefs, ContactListFilter filter) {
-        if (filter != null && filter.filterType == FILTER_TYPE_SINGLE_CONTACT) {
-            return;
-        }
-        prefs.edit()
-            .putInt(KEY_FILTER_TYPE, filter == null ? FILTER_TYPE_DEFAULT : filter.filterType)
-            .putString(KEY_ACCOUNT_NAME, filter == null ? null : filter.accountName)
-            .putString(KEY_ACCOUNT_TYPE, filter == null ? null : filter.accountType)
-            .putString(KEY_DATA_SET, filter == null ? null : filter.dataSet)
-            .apply();
-    }
-
-    /**
-     * Try to obtain ContactListFilter object saved in SharedPreference.
-     * If there's no info there, return ALL filter instead.
-     */
-    public static ContactListFilter restoreDefaultPreferences(SharedPreferences prefs) {
-        ContactListFilter filter = restoreFromPreferences(prefs);
-        if (filter == null) {
-            filter = ContactListFilter.createFilterWithType(FILTER_TYPE_ALL_ACCOUNTS);
-        }
-        // "Group" filter is obsolete and thus is not exposed anymore. The "single contact mode"
-        // should also not be stored in preferences anymore since it is a temporary state.
-        if (filter.filterType == FILTER_TYPE_GROUP ||
-                filter.filterType == FILTER_TYPE_SINGLE_CONTACT) {
-            filter = ContactListFilter.createFilterWithType(FILTER_TYPE_ALL_ACCOUNTS);
-        }
-        return filter;
-    }
-
-    private static ContactListFilter restoreFromPreferences(SharedPreferences prefs) {
-        int filterType = prefs.getInt(KEY_FILTER_TYPE, FILTER_TYPE_DEFAULT);
-        if (filterType == FILTER_TYPE_DEFAULT) {
-            return null;
-        }
-
-        String accountName = prefs.getString(KEY_ACCOUNT_NAME, null);
-        String accountType = prefs.getString(KEY_ACCOUNT_TYPE, null);
-        String dataSet = prefs.getString(KEY_DATA_SET, null);
-        return new ContactListFilter(filterType, accountType, accountName, dataSet, null);
-    }
-
-
-    @Override
-    public void writeToParcel(Parcel dest, int flags) {
-        dest.writeInt(filterType);
-        dest.writeString(accountName);
-        dest.writeString(accountType);
-        dest.writeString(dataSet);
-    }
-
-    public static final Parcelable.Creator<ContactListFilter> CREATOR =
-            new Parcelable.Creator<ContactListFilter>() {
-        @Override
-        public ContactListFilter createFromParcel(Parcel source) {
-            int filterType = source.readInt();
-            String accountName = source.readString();
-            String accountType = source.readString();
-            String dataSet = source.readString();
-            return new ContactListFilter(filterType, accountType, accountName, dataSet, null);
-        }
-
-        @Override
-        public ContactListFilter[] newArray(int size) {
-            return new ContactListFilter[size];
-        }
-    };
-
-    @Override
-    public int describeContents() {
-        return 0;
-    }
-
-    /**
-     * Returns a string that can be used as a stable persistent identifier for this filter.
-     */
-    public String getId() {
-        if (mId == null) {
-            StringBuilder sb = new StringBuilder();
-            sb.append(filterType);
-            if (accountType != null) {
-                sb.append('-').append(accountType);
-            }
-            if (dataSet != null) {
-                sb.append('/').append(dataSet);
-            }
-            if (accountName != null) {
-                sb.append('-').append(accountName.replace('-', '_'));
-            }
-            mId = sb.toString();
-        }
-        return mId;
-    }
-
-    /**
-     * Adds the account query parameters to the given {@code uriBuilder}.
-     *
-     * @throws IllegalStateException if the filter type is not {@link #FILTER_TYPE_ACCOUNT}.
-     */
-    public Uri.Builder addAccountQueryParameterToUrl(Uri.Builder uriBuilder) {
-        if (filterType != FILTER_TYPE_ACCOUNT) {
-            throw new IllegalStateException("filterType must be FILTER_TYPE_ACCOUNT");
-        }
-        uriBuilder.appendQueryParameter(RawContacts.ACCOUNT_NAME, accountName);
-        uriBuilder.appendQueryParameter(RawContacts.ACCOUNT_TYPE, accountType);
-        if (!TextUtils.isEmpty(dataSet)) {
-            uriBuilder.appendQueryParameter(RawContacts.DATA_SET, dataSet);
-        }
-        return uriBuilder;
-    }
-
-    public String toDebugString() {
-        final StringBuilder builder = new StringBuilder();
-        builder.append("[filter type: " + filterType + " (" + filterTypeToString(filterType) + ")");
-        if (filterType == FILTER_TYPE_ACCOUNT) {
-            builder.append(", accountType: " + accountType)
-                    .append(", accountName: " + accountName)
-                    .append(", dataSet: " + dataSet);
-        }
-        builder.append(", icon: " + icon + "]");
-        return builder.toString();
-    }
-
-    public static final String filterTypeToString(int filterType) {
-        switch (filterType) {
-            case FILTER_TYPE_DEFAULT:
-                return "FILTER_TYPE_DEFAULT";
-            case FILTER_TYPE_ALL_ACCOUNTS:
-                return "FILTER_TYPE_ALL_ACCOUNTS";
-            case FILTER_TYPE_CUSTOM:
-                return "FILTER_TYPE_CUSTOM";
-            case FILTER_TYPE_STARRED:
-                return "FILTER_TYPE_STARRED";
-            case FILTER_TYPE_WITH_PHONE_NUMBERS_ONLY:
-                return "FILTER_TYPE_WITH_PHONE_NUMBERS_ONLY";
-            case FILTER_TYPE_SINGLE_CONTACT:
-                return "FILTER_TYPE_SINGLE_CONTACT";
-            case FILTER_TYPE_ACCOUNT:
-                return "FILTER_TYPE_ACCOUNT";
-            default:
-                return "(unknown)";
-        }
-    }
-}
diff --git a/src/com/android/contacts/list/ContactListFilterController.java b/src/com/android/contacts/list/ContactListFilterController.java
index 5377df2..b8086b0 100644
--- a/src/com/android/contacts/list/ContactListFilterController.java
+++ b/src/com/android/contacts/list/ContactListFilterController.java
@@ -19,6 +19,7 @@
 import android.content.SharedPreferences;
 import android.preference.PreferenceManager;
 
+import com.android.contacts.common.list.ContactListFilter;
 import com.android.contacts.model.AccountTypeManager;
 import com.android.contacts.model.account.AccountWithDataSet;
 
@@ -26,7 +27,7 @@
 import java.util.List;
 
 /**
- * Manages {@link ContactListFilter}. All methods must be called from UI thread.
+ * Manages {@link com.android.contacts.common.list.ContactListFilter}. All methods must be called from UI thread.
  */
 public abstract class ContactListFilterController {
 
diff --git a/src/com/android/contacts/list/ContactListFilterView.java b/src/com/android/contacts/list/ContactListFilterView.java
index d0ecfe4..a71809b 100644
--- a/src/com/android/contacts/list/ContactListFilterView.java
+++ b/src/com/android/contacts/list/ContactListFilterView.java
@@ -26,6 +26,7 @@
 import android.widget.TextView;
 
 import com.android.contacts.R;
+import com.android.contacts.common.list.ContactListFilter;
 import com.android.contacts.model.AccountTypeManager;
 import com.android.contacts.model.account.AccountType;
 
diff --git a/src/com/android/contacts/list/ContactListItemView.java b/src/com/android/contacts/list/ContactListItemView.java
deleted file mode 100644
index bef1c44..0000000
--- a/src/com/android/contacts/list/ContactListItemView.java
+++ /dev/null
@@ -1,1215 +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.list;
-
-import android.content.Context;
-import android.content.res.ColorStateList;
-import android.content.res.TypedArray;
-import android.database.CharArrayBuffer;
-import android.database.Cursor;
-import android.graphics.Canvas;
-import android.graphics.Color;
-import android.graphics.Rect;
-import android.graphics.Typeface;
-import android.graphics.drawable.Drawable;
-import android.os.Bundle;
-import android.provider.ContactsContract;
-import android.provider.ContactsContract.Contacts;
-import android.text.Spannable;
-import android.text.SpannableString;
-import android.text.TextUtils;
-import android.text.TextUtils.TruncateAt;
-import android.util.AttributeSet;
-import android.util.TypedValue;
-import android.view.Gravity;
-import android.view.View;
-import android.view.ViewGroup;
-import android.widget.AbsListView.SelectionBoundsAdjuster;
-import android.widget.ImageView;
-import android.widget.ImageView.ScaleType;
-import android.widget.QuickContactBadge;
-import android.widget.TextView;
-
-import com.android.contacts.common.ContactPresenceIconUtil;
-import com.android.contacts.common.ContactStatusUtil;
-import com.android.contacts.R;
-import com.android.contacts.common.format.PrefixHighlighter;
-
-/**
- * A custom view for an item in the contact list.
- * The view contains the contact's photo, a set of text views (for name, status, etc...) and
- * icons for presence and call.
- * The view uses no XML file for layout and all the measurements and layouts are done
- * in the onMeasure and onLayout methods.
- *
- * The layout puts the contact's photo on the right side of the view, the call icon (if present)
- * to the left of the photo, the text lines are aligned to the left and the presence icon (if
- * present) is set to the left of the status line.
- *
- * The layout also supports a header (used as a header of a group of contacts) that is above the
- * contact's data and a divider between contact view.
- */
-
-public class ContactListItemView extends ViewGroup
-        implements SelectionBoundsAdjuster {
-
-    // Style values for layout and appearance
-    private final int mPreferredHeight;
-    private final int mGapBetweenImageAndText;
-    private final int mGapBetweenLabelAndData;
-    private final int mPresenceIconMargin;
-    private final int mPresenceIconSize;
-    private final int mHeaderTextColor;
-    private final int mHeaderTextIndent;
-    private final int mHeaderTextSize;
-    private final int mHeaderUnderlineHeight;
-    private final int mHeaderUnderlineColor;
-    private final int mCountViewTextSize;
-    private final int mContactsCountTextColor;
-    private final int mTextIndent;
-    private Drawable mActivatedBackgroundDrawable;
-
-    /**
-     * Used with {@link #mLabelView}, specifying the width ratio between label and data.
-     */
-    private final int mLabelViewWidthWeight;
-    /**
-     * Used with {@link #mDataView}, specifying the width ratio between label and data.
-     */
-    private final int mDataViewWidthWeight;
-
-    // Will be used with adjustListItemSelectionBounds().
-    private int mSelectionBoundsMarginLeft;
-    private int mSelectionBoundsMarginRight;
-
-    // Horizontal divider between contact views.
-    private boolean mHorizontalDividerVisible = true;
-    private Drawable mHorizontalDividerDrawable;
-    private int mHorizontalDividerHeight;
-
-    /**
-     * Where to put contact photo. This affects the other Views' layout or look-and-feel.
-     */
-    public enum PhotoPosition {
-        LEFT,
-        RIGHT
-    }
-    public static final PhotoPosition DEFAULT_PHOTO_POSITION = PhotoPosition.RIGHT;
-    private PhotoPosition mPhotoPosition = DEFAULT_PHOTO_POSITION;
-
-    // Header layout data
-    private boolean mHeaderVisible;
-    private View mHeaderDivider;
-    private int mHeaderBackgroundHeight;
-    private TextView mHeaderTextView;
-
-    // The views inside the contact view
-    private boolean mQuickContactEnabled = true;
-    private QuickContactBadge mQuickContact;
-    private ImageView mPhotoView;
-    private TextView mNameTextView;
-    private TextView mPhoneticNameTextView;
-    private TextView mLabelView;
-    private TextView mDataView;
-    private TextView mSnippetView;
-    private TextView mStatusView;
-    private TextView mCountView;
-    private ImageView mPresenceIcon;
-
-    private ColorStateList mSecondaryTextColor;
-
-    private char[] mHighlightedPrefix;
-
-    private int mDefaultPhotoViewSize;
-    /**
-     * Can be effective even when {@link #mPhotoView} is null, as we want to have horizontal padding
-     * to align other data in this View.
-     */
-    private int mPhotoViewWidth;
-    /**
-     * Can be effective even when {@link #mPhotoView} is null, as we want to have vertical padding.
-     */
-    private int mPhotoViewHeight;
-
-    /**
-     * Only effective when {@link #mPhotoView} is null.
-     * When true all the Views on the right side of the photo should have horizontal padding on
-     * those left assuming there is a photo.
-     */
-    private boolean mKeepHorizontalPaddingForPhotoView;
-    /**
-     * Only effective when {@link #mPhotoView} is null.
-     */
-    private boolean mKeepVerticalPaddingForPhotoView;
-
-    /**
-     * True when {@link #mPhotoViewWidth} and {@link #mPhotoViewHeight} are ready for being used.
-     * False indicates those values should be updated before being used in position calculation.
-     */
-    private boolean mPhotoViewWidthAndHeightAreReady = false;
-
-    private int mNameTextViewHeight;
-    private int mPhoneticNameTextViewHeight;
-    private int mLabelViewHeight;
-    private int mDataViewHeight;
-    private int mSnippetTextViewHeight;
-    private int mStatusTextViewHeight;
-
-    // Holds Math.max(mLabelTextViewHeight, mDataViewHeight), assuming Label and Data share the
-    // same row.
-    private int mLabelAndDataViewMaxHeight;
-
-    // TODO: some TextView fields are using CharArrayBuffer while some are not. Determine which is
-    // more efficient for each case or in general, and simplify the whole implementation.
-    // Note: if we're sure MARQUEE will be used every time, there's no reason to use
-    // CharArrayBuffer, since MARQUEE requires Span and thus we need to copy characters inside the
-    // buffer to Spannable once, while CharArrayBuffer is for directly applying char array to
-    // TextView without any modification.
-    private final CharArrayBuffer mDataBuffer = new CharArrayBuffer(128);
-    private final CharArrayBuffer mPhoneticNameBuffer = new CharArrayBuffer(128);
-
-    private boolean mActivatedStateSupported;
-
-    private Rect mBoundsWithoutHeader = new Rect();
-
-    /** A helper used to highlight a prefix in a text field. */
-    private PrefixHighlighter mPrefixHighlighter;
-    private CharSequence mUnknownNameText;
-
-    public ContactListItemView(Context context, AttributeSet attrs) {
-        super(context, attrs);
-        mContext = context;
-
-        // Read all style values
-        TypedArray a = getContext().obtainStyledAttributes(attrs, R.styleable.ContactListItemView);
-        mPreferredHeight = a.getDimensionPixelSize(
-                R.styleable.ContactListItemView_list_item_height, 0);
-        mActivatedBackgroundDrawable = a.getDrawable(
-                R.styleable.ContactListItemView_activated_background);
-        mHorizontalDividerDrawable = a.getDrawable(
-                R.styleable.ContactListItemView_list_item_divider);
-
-        mGapBetweenImageAndText = a.getDimensionPixelOffset(
-                R.styleable.ContactListItemView_list_item_gap_between_image_and_text, 0);
-        mGapBetweenLabelAndData = a.getDimensionPixelOffset(
-                R.styleable.ContactListItemView_list_item_gap_between_label_and_data, 0);
-        mPresenceIconMargin = a.getDimensionPixelOffset(
-                R.styleable.ContactListItemView_list_item_presence_icon_margin, 4);
-        mPresenceIconSize = a.getDimensionPixelOffset(
-                R.styleable.ContactListItemView_list_item_presence_icon_size, 16);
-        mDefaultPhotoViewSize = a.getDimensionPixelOffset(
-                R.styleable.ContactListItemView_list_item_photo_size, 0);
-        mHeaderTextIndent = a.getDimensionPixelOffset(
-                R.styleable.ContactListItemView_list_item_header_text_indent, 0);
-        mHeaderTextColor = a.getColor(
-                R.styleable.ContactListItemView_list_item_header_text_color, Color.BLACK);
-        mHeaderTextSize = a.getDimensionPixelSize(
-                R.styleable.ContactListItemView_list_item_header_text_size, 12);
-        mHeaderBackgroundHeight = a.getDimensionPixelSize(
-                R.styleable.ContactListItemView_list_item_header_height, 30);
-        mHeaderUnderlineHeight = a.getDimensionPixelSize(
-                R.styleable.ContactListItemView_list_item_header_underline_height, 1);
-        mHeaderUnderlineColor = a.getColor(
-                R.styleable.ContactListItemView_list_item_header_underline_color, 0);
-        mTextIndent = a.getDimensionPixelOffset(
-                R.styleable.ContactListItemView_list_item_text_indent, 0);
-        mCountViewTextSize = a.getDimensionPixelSize(
-                R.styleable.ContactListItemView_list_item_contacts_count_text_size, 12);
-        mContactsCountTextColor = a.getColor(
-                R.styleable.ContactListItemView_list_item_contacts_count_text_color, Color.BLACK);
-        mDataViewWidthWeight = a.getInteger(
-                R.styleable.ContactListItemView_list_item_data_width_weight, 5);
-        mLabelViewWidthWeight = a.getInteger(
-                R.styleable.ContactListItemView_list_item_label_width_weight, 3);
-
-        setPadding(
-                a.getDimensionPixelOffset(
-                        R.styleable.ContactListItemView_list_item_padding_left, 0),
-                a.getDimensionPixelOffset(
-                        R.styleable.ContactListItemView_list_item_padding_top, 0),
-                a.getDimensionPixelOffset(
-                        R.styleable.ContactListItemView_list_item_padding_right, 0),
-                a.getDimensionPixelOffset(
-                        R.styleable.ContactListItemView_list_item_padding_bottom, 0));
-
-        final int prefixHighlightColor = a.getColor(
-                R.styleable.ContactListItemView_list_item_prefix_highlight_color, Color.GREEN);
-        mPrefixHighlighter = new PrefixHighlighter(prefixHighlightColor);
-        a.recycle();
-
-        a = getContext().obtainStyledAttributes(android.R.styleable.Theme);
-        mSecondaryTextColor = a.getColorStateList(android.R.styleable.Theme_textColorSecondary);
-        a.recycle();
-
-        mHorizontalDividerHeight = mHorizontalDividerDrawable.getIntrinsicHeight();
-
-        if (mActivatedBackgroundDrawable != null) {
-            mActivatedBackgroundDrawable.setCallback(this);
-        }
-    }
-
-    public void setUnknownNameText(CharSequence unknownNameText) {
-        mUnknownNameText = unknownNameText;
-    }
-
-    public void setQuickContactEnabled(boolean flag) {
-        mQuickContactEnabled = flag;
-    }
-
-    @Override
-    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
-        // We will match parent's width and wrap content vertically, but make sure
-        // height is no less than listPreferredItemHeight.
-        final int specWidth = resolveSize(0, widthMeasureSpec);
-        final int preferredHeight;
-        if (mHorizontalDividerVisible) {
-            preferredHeight = mPreferredHeight + mHorizontalDividerHeight;
-        } else {
-            preferredHeight = mPreferredHeight;
-        }
-
-        mNameTextViewHeight = 0;
-        mPhoneticNameTextViewHeight = 0;
-        mLabelViewHeight = 0;
-        mDataViewHeight = 0;
-        mLabelAndDataViewMaxHeight = 0;
-        mSnippetTextViewHeight = 0;
-        mStatusTextViewHeight = 0;
-
-        ensurePhotoViewSize();
-
-        // Width each TextView is able to use.
-        final int effectiveWidth;
-        // All the other Views will honor the photo, so available width for them may be shrunk.
-        if (mPhotoViewWidth > 0 || mKeepHorizontalPaddingForPhotoView) {
-            effectiveWidth = specWidth - getPaddingLeft() - getPaddingRight()
-                    - (mPhotoViewWidth + mGapBetweenImageAndText);
-        } else {
-            effectiveWidth = specWidth - getPaddingLeft() - getPaddingRight();
-        }
-
-        // Go over all visible text views and measure actual width of each of them.
-        // Also calculate their heights to get the total height for this entire view.
-
-        if (isVisible(mNameTextView)) {
-            // Caculate width for name text - this parallels similar measurement in onLayout.
-            int nameTextWidth = effectiveWidth;
-            if (mPhotoPosition != PhotoPosition.LEFT) {
-                nameTextWidth -= mTextIndent;
-            }
-            mNameTextView.measure(
-                    MeasureSpec.makeMeasureSpec(nameTextWidth, MeasureSpec.EXACTLY),
-                    MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED));
-            mNameTextViewHeight = mNameTextView.getMeasuredHeight();
-        }
-
-        if (isVisible(mPhoneticNameTextView)) {
-            mPhoneticNameTextView.measure(
-                    MeasureSpec.makeMeasureSpec(effectiveWidth, MeasureSpec.EXACTLY),
-                    MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED));
-            mPhoneticNameTextViewHeight = mPhoneticNameTextView.getMeasuredHeight();
-        }
-
-        // If both data (phone number/email address) and label (type like "MOBILE") are quite long,
-        // we should ellipsize both using appropriate ratio.
-        final int dataWidth;
-        final int labelWidth;
-        if (isVisible(mDataView)) {
-            if (isVisible(mLabelView)) {
-                final int totalWidth = effectiveWidth - mGapBetweenLabelAndData;
-                dataWidth = ((totalWidth * mDataViewWidthWeight)
-                        / (mDataViewWidthWeight + mLabelViewWidthWeight));
-                labelWidth = ((totalWidth * mLabelViewWidthWeight) /
-                        (mDataViewWidthWeight + mLabelViewWidthWeight));
-            } else {
-                dataWidth = effectiveWidth;
-                labelWidth = 0;
-            }
-        } else {
-            dataWidth = 0;
-            if (isVisible(mLabelView)) {
-                labelWidth = effectiveWidth;
-            } else {
-                labelWidth = 0;
-            }
-        }
-
-        if (isVisible(mDataView)) {
-            mDataView.measure(MeasureSpec.makeMeasureSpec(dataWidth, MeasureSpec.EXACTLY),
-                    MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED));
-            mDataViewHeight = mDataView.getMeasuredHeight();
-        }
-
-        if (isVisible(mLabelView)) {
-            // For performance reason we don't want AT_MOST usually, but when the picture is
-            // on right, we need to use it anyway because mDataView is next to mLabelView.
-            final int mode = (mPhotoPosition == PhotoPosition.LEFT
-                    ? MeasureSpec.EXACTLY : MeasureSpec.AT_MOST);
-            mLabelView.measure(MeasureSpec.makeMeasureSpec(labelWidth, mode),
-                    MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED));
-            mLabelViewHeight = mLabelView.getMeasuredHeight();
-        }
-        mLabelAndDataViewMaxHeight = Math.max(mLabelViewHeight, mDataViewHeight);
-
-        if (isVisible(mSnippetView)) {
-            mSnippetView.measure(
-                    MeasureSpec.makeMeasureSpec(effectiveWidth, MeasureSpec.EXACTLY),
-                    MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED));
-            mSnippetTextViewHeight = mSnippetView.getMeasuredHeight();
-        }
-
-        // Status view height is the biggest of the text view and the presence icon
-        if (isVisible(mPresenceIcon)) {
-            mPresenceIcon.measure(
-                    MeasureSpec.makeMeasureSpec(mPresenceIconSize, MeasureSpec.EXACTLY),
-                    MeasureSpec.makeMeasureSpec(mPresenceIconSize, MeasureSpec.EXACTLY));
-            mStatusTextViewHeight = mPresenceIcon.getMeasuredHeight();
-        }
-
-        if (isVisible(mStatusView)) {
-            // Presence and status are in a same row, so status will be affected by icon size.
-            final int statusWidth;
-            if (isVisible(mPresenceIcon)) {
-                statusWidth = (effectiveWidth - mPresenceIcon.getMeasuredWidth()
-                        - mPresenceIconMargin);
-            } else {
-                statusWidth = effectiveWidth;
-            }
-            mStatusView.measure(MeasureSpec.makeMeasureSpec(statusWidth, MeasureSpec.EXACTLY),
-                    MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED));
-            mStatusTextViewHeight =
-                    Math.max(mStatusTextViewHeight, mStatusView.getMeasuredHeight());
-        }
-
-        // Calculate height including padding.
-        int height = (mNameTextViewHeight + mPhoneticNameTextViewHeight +
-                mLabelAndDataViewMaxHeight +
-                mSnippetTextViewHeight + mStatusTextViewHeight);
-
-        // Make sure the height is at least as high as the photo
-        height = Math.max(height, mPhotoViewHeight + getPaddingBottom() + getPaddingTop());
-
-        // Add horizontal divider height
-        if (mHorizontalDividerVisible) {
-            height += mHorizontalDividerHeight;
-        }
-
-        // Make sure height is at least the preferred height
-        height = Math.max(height, preferredHeight);
-
-        // Add the height of the header if visible
-        if (mHeaderVisible) {
-            mHeaderTextView.measure(
-                    MeasureSpec.makeMeasureSpec(specWidth, MeasureSpec.EXACTLY),
-                    MeasureSpec.makeMeasureSpec(mHeaderBackgroundHeight, MeasureSpec.EXACTLY));
-            if (mCountView != null) {
-                mCountView.measure(
-                        MeasureSpec.makeMeasureSpec(specWidth, MeasureSpec.AT_MOST),
-                        MeasureSpec.makeMeasureSpec(mHeaderBackgroundHeight, MeasureSpec.EXACTLY));
-            }
-            mHeaderBackgroundHeight = Math.max(mHeaderBackgroundHeight,
-                    mHeaderTextView.getMeasuredHeight());
-            height += (mHeaderBackgroundHeight + mHeaderUnderlineHeight);
-        }
-
-        setMeasuredDimension(specWidth, height);
-    }
-
-    @Override
-    protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
-        final int height = bottom - top;
-        final int width = right - left;
-
-        // Determine the vertical bounds by laying out the header first.
-        int topBound = 0;
-        int bottomBound = height;
-        int leftBound = getPaddingLeft();
-        int rightBound = width - getPaddingRight();
-
-        // Put the header in the top of the contact view (Text + underline view)
-        if (mHeaderVisible) {
-            mHeaderTextView.layout(leftBound + mHeaderTextIndent,
-                    0,
-                    rightBound,
-                    mHeaderBackgroundHeight);
-            if (mCountView != null) {
-                mCountView.layout(rightBound - mCountView.getMeasuredWidth(),
-                        0,
-                        rightBound,
-                        mHeaderBackgroundHeight);
-            }
-            mHeaderDivider.layout(leftBound,
-                    mHeaderBackgroundHeight,
-                    rightBound,
-                    mHeaderBackgroundHeight + mHeaderUnderlineHeight);
-            topBound += (mHeaderBackgroundHeight + mHeaderUnderlineHeight);
-        }
-
-        // Put horizontal divider at the bottom
-        if (mHorizontalDividerVisible) {
-            mHorizontalDividerDrawable.setBounds(
-                    leftBound,
-                    height - mHorizontalDividerHeight,
-                    rightBound,
-                    height);
-            bottomBound -= mHorizontalDividerHeight;
-        }
-
-        mBoundsWithoutHeader.set(0, topBound, width, bottomBound);
-
-        if (mActivatedStateSupported && isActivated()) {
-            mActivatedBackgroundDrawable.setBounds(mBoundsWithoutHeader);
-        }
-
-        final View photoView = mQuickContact != null ? mQuickContact : mPhotoView;
-        if (mPhotoPosition == PhotoPosition.LEFT) {
-            // Photo is the left most view. All the other Views should on the right of the photo.
-            if (photoView != null) {
-                // Center the photo vertically
-                final int photoTop = topBound + (bottomBound - topBound - mPhotoViewHeight) / 2;
-                photoView.layout(
-                        leftBound,
-                        photoTop,
-                        leftBound + mPhotoViewWidth,
-                        photoTop + mPhotoViewHeight);
-                leftBound += mPhotoViewWidth + mGapBetweenImageAndText;
-            } else if (mKeepHorizontalPaddingForPhotoView) {
-                // Draw nothing but keep the padding.
-                leftBound += mPhotoViewWidth + mGapBetweenImageAndText;
-            }
-        } else {
-            // Photo is the right most view. Right bound should be adjusted that way.
-            if (photoView != null) {
-                // Center the photo vertically
-                final int photoTop = topBound + (bottomBound - topBound - mPhotoViewHeight) / 2;
-                photoView.layout(
-                        rightBound - mPhotoViewWidth,
-                        photoTop,
-                        rightBound,
-                        photoTop + mPhotoViewHeight);
-                rightBound -= (mPhotoViewWidth + mGapBetweenImageAndText);
-            }
-
-            // Add indent between left-most padding and texts.
-            leftBound += mTextIndent;
-        }
-
-        // Center text vertically
-        final int totalTextHeight = mNameTextViewHeight + mPhoneticNameTextViewHeight +
-                mLabelAndDataViewMaxHeight + mSnippetTextViewHeight + mStatusTextViewHeight;
-        int textTopBound = (bottomBound + topBound - totalTextHeight) / 2;
-
-        // Layout all text view and presence icon
-        // Put name TextView first
-        if (isVisible(mNameTextView)) {
-            mNameTextView.layout(leftBound,
-                    textTopBound,
-                    rightBound,
-                    textTopBound + mNameTextViewHeight);
-            textTopBound += mNameTextViewHeight;
-        }
-
-        // Presence and status
-        int statusLeftBound = leftBound;
-        if (isVisible(mPresenceIcon)) {
-            int iconWidth = mPresenceIcon.getMeasuredWidth();
-            mPresenceIcon.layout(
-                    leftBound,
-                    textTopBound,
-                    leftBound + iconWidth,
-                    textTopBound + mStatusTextViewHeight);
-            statusLeftBound += (iconWidth + mPresenceIconMargin);
-        }
-
-        if (isVisible(mStatusView)) {
-            mStatusView.layout(statusLeftBound,
-                    textTopBound,
-                    rightBound,
-                    textTopBound + mStatusTextViewHeight);
-        }
-
-        if (isVisible(mStatusView) || isVisible(mPresenceIcon)) {
-            textTopBound += mStatusTextViewHeight;
-        }
-
-        // Rest of text views
-        int dataLeftBound = leftBound;
-        if (isVisible(mPhoneticNameTextView)) {
-            mPhoneticNameTextView.layout(leftBound,
-                    textTopBound,
-                    rightBound,
-                    textTopBound + mPhoneticNameTextViewHeight);
-            textTopBound += mPhoneticNameTextViewHeight;
-        }
-
-        // Label and Data align bottom.
-        if (isVisible(mLabelView)) {
-            if (mPhotoPosition == PhotoPosition.LEFT) {
-                // When photo is on left, label is placed on the right edge of the list item.
-                mLabelView.layout(rightBound - mLabelView.getMeasuredWidth(),
-                        textTopBound + mLabelAndDataViewMaxHeight - mLabelViewHeight,
-                        rightBound,
-                        textTopBound + mLabelAndDataViewMaxHeight);
-                rightBound -= mLabelView.getMeasuredWidth();
-            } else {
-                // When photo is on right, label is placed on the left of data view.
-                dataLeftBound = leftBound + mLabelView.getMeasuredWidth();
-                mLabelView.layout(leftBound,
-                        textTopBound + mLabelAndDataViewMaxHeight - mLabelViewHeight,
-                        dataLeftBound,
-                        textTopBound + mLabelAndDataViewMaxHeight);
-                dataLeftBound += mGapBetweenLabelAndData;
-            }
-        }
-
-        if (isVisible(mDataView)) {
-            mDataView.layout(dataLeftBound,
-                    textTopBound + mLabelAndDataViewMaxHeight - mDataViewHeight,
-                    rightBound,
-                    textTopBound + mLabelAndDataViewMaxHeight);
-        }
-        if (isVisible(mLabelView) || isVisible(mDataView)) {
-            textTopBound += mLabelAndDataViewMaxHeight;
-        }
-
-        if (isVisible(mSnippetView)) {
-            mSnippetView.layout(leftBound,
-                    textTopBound,
-                    rightBound,
-                    textTopBound + mSnippetTextViewHeight);
-        }
-    }
-
-    @Override
-    public void adjustListItemSelectionBounds(Rect bounds) {
-        bounds.top += mBoundsWithoutHeader.top;
-        bounds.bottom = bounds.top + mBoundsWithoutHeader.height();
-        bounds.left += mSelectionBoundsMarginLeft;
-        bounds.right -= mSelectionBoundsMarginRight;
-    }
-
-    protected boolean isVisible(View view) {
-        return view != null && view.getVisibility() == View.VISIBLE;
-    }
-
-    /**
-     * Extracts width and height from the style
-     */
-    private void ensurePhotoViewSize() {
-        if (!mPhotoViewWidthAndHeightAreReady) {
-            mPhotoViewWidth = mPhotoViewHeight = getDefaultPhotoViewSize();
-            if (!mQuickContactEnabled && mPhotoView == null) {
-                if (!mKeepHorizontalPaddingForPhotoView) {
-                    mPhotoViewWidth = 0;
-                }
-                if (!mKeepVerticalPaddingForPhotoView) {
-                    mPhotoViewHeight = 0;
-                }
-            }
-
-            mPhotoViewWidthAndHeightAreReady = true;
-        }
-    }
-
-    protected void setDefaultPhotoViewSize(int pixels) {
-        mDefaultPhotoViewSize = pixels;
-    }
-
-    protected int getDefaultPhotoViewSize() {
-        return mDefaultPhotoViewSize;
-    }
-
-    /**
-     * Gets a LayoutParam that corresponds to the default photo size.
-     *
-     * @return A new LayoutParam.
-     */
-    private LayoutParams getDefaultPhotoLayoutParams() {
-        LayoutParams params = generateDefaultLayoutParams();
-        params.width = getDefaultPhotoViewSize();
-        params.height = params.width;
-        return params;
-    }
-
-    @Override
-    protected void drawableStateChanged() {
-        super.drawableStateChanged();
-        if (mActivatedStateSupported) {
-            mActivatedBackgroundDrawable.setState(getDrawableState());
-        }
-    }
-
-    @Override
-    protected boolean verifyDrawable(Drawable who) {
-        return who == mActivatedBackgroundDrawable || super.verifyDrawable(who);
-    }
-
-    @Override
-    public void jumpDrawablesToCurrentState() {
-        super.jumpDrawablesToCurrentState();
-        if (mActivatedStateSupported) {
-            mActivatedBackgroundDrawable.jumpToCurrentState();
-        }
-    }
-
-    @Override
-    public void dispatchDraw(Canvas canvas) {
-        if (mActivatedStateSupported && isActivated()) {
-            mActivatedBackgroundDrawable.draw(canvas);
-        }
-        if (mHorizontalDividerVisible) {
-            mHorizontalDividerDrawable.draw(canvas);
-        }
-
-        super.dispatchDraw(canvas);
-    }
-
-    /**
-     * Sets the flag that determines whether a divider should drawn at the bottom
-     * of the view.
-     */
-    public void setDividerVisible(boolean visible) {
-        mHorizontalDividerVisible = visible;
-    }
-
-    /**
-     * Sets section header or makes it invisible if the title is null.
-     */
-    public void setSectionHeader(String title) {
-        if (!TextUtils.isEmpty(title)) {
-            if (mHeaderTextView == null) {
-                mHeaderTextView = new TextView(mContext);
-                mHeaderTextView.setTextColor(mHeaderTextColor);
-                mHeaderTextView.setTextSize(TypedValue.COMPLEX_UNIT_PX, mHeaderTextSize);
-                mHeaderTextView.setTypeface(mHeaderTextView.getTypeface(), Typeface.BOLD);
-                mHeaderTextView.setGravity(Gravity.CENTER_VERTICAL);
-                addView(mHeaderTextView);
-            }
-            if (mHeaderDivider == null) {
-                mHeaderDivider = new View(mContext);
-                mHeaderDivider.setBackgroundColor(mHeaderUnderlineColor);
-                addView(mHeaderDivider);
-            }
-            setMarqueeText(mHeaderTextView, title);
-            mHeaderTextView.setVisibility(View.VISIBLE);
-            mHeaderDivider.setVisibility(View.VISIBLE);
-            mHeaderTextView.setAllCaps(true);
-            mHeaderVisible = true;
-        } else {
-            if (mHeaderTextView != null) {
-                mHeaderTextView.setVisibility(View.GONE);
-            }
-            if (mHeaderDivider != null) {
-                mHeaderDivider.setVisibility(View.GONE);
-            }
-            mHeaderVisible = false;
-        }
-    }
-
-    /**
-     * Returns the quick contact badge, creating it if necessary.
-     */
-    public QuickContactBadge getQuickContact() {
-        if (!mQuickContactEnabled) {
-            throw new IllegalStateException("QuickContact is disabled for this view");
-        }
-        if (mQuickContact == null) {
-            mQuickContact = new QuickContactBadge(mContext);
-            mQuickContact.setLayoutParams(getDefaultPhotoLayoutParams());
-            if (mNameTextView != null) {
-                mQuickContact.setContentDescription(mContext.getString(
-                        R.string.description_quick_contact_for, mNameTextView.getText()));
-            }
-
-            addView(mQuickContact);
-            mPhotoViewWidthAndHeightAreReady = false;
-        }
-        return mQuickContact;
-    }
-
-    /**
-     * Returns the photo view, creating it if necessary.
-     */
-    public ImageView getPhotoView() {
-        if (mPhotoView == null) {
-            mPhotoView = new ImageView(mContext);
-            mPhotoView.setLayoutParams(getDefaultPhotoLayoutParams());
-            // Quick contact style used above will set a background - remove it
-            mPhotoView.setBackground(null);
-            addView(mPhotoView);
-            mPhotoViewWidthAndHeightAreReady = false;
-        }
-        return mPhotoView;
-    }
-
-    /**
-     * Removes the photo view.
-     */
-    public void removePhotoView() {
-        removePhotoView(false, true);
-    }
-
-    /**
-     * Removes the photo view.
-     *
-     * @param keepHorizontalPadding True means data on the right side will have
-     *            padding on left, pretending there is still a photo view.
-     * @param keepVerticalPadding True means the View will have some height
-     *            enough for accommodating a photo view.
-     */
-    public void removePhotoView(boolean keepHorizontalPadding, boolean keepVerticalPadding) {
-        mPhotoViewWidthAndHeightAreReady = false;
-        mKeepHorizontalPaddingForPhotoView = keepHorizontalPadding;
-        mKeepVerticalPaddingForPhotoView = keepVerticalPadding;
-        if (mPhotoView != null) {
-            removeView(mPhotoView);
-            mPhotoView = null;
-        }
-        if (mQuickContact != null) {
-            removeView(mQuickContact);
-            mQuickContact = null;
-        }
-    }
-
-    /**
-     * Sets a word prefix that will be highlighted if encountered in fields like
-     * name and search snippet.
-     * <p>
-     * NOTE: must be all upper-case
-     */
-    public void setHighlightedPrefix(char[] upperCasePrefix) {
-        mHighlightedPrefix = upperCasePrefix;
-    }
-
-    /**
-     * Returns the text view for the contact name, creating it if necessary.
-     */
-    public TextView getNameTextView() {
-        if (mNameTextView == null) {
-            mNameTextView = new TextView(mContext);
-            mNameTextView.setSingleLine(true);
-            mNameTextView.setEllipsize(getTextEllipsis());
-            mNameTextView.setTextAppearance(mContext, android.R.style.TextAppearance_Medium);
-            // Manually call setActivated() since this view may be added after the first
-            // setActivated() call toward this whole item view.
-            mNameTextView.setActivated(isActivated());
-            mNameTextView.setGravity(Gravity.CENTER_VERTICAL);
-            addView(mNameTextView);
-        }
-        return mNameTextView;
-    }
-
-    /**
-     * Adds or updates a text view for the phonetic name.
-     */
-    public void setPhoneticName(char[] text, int size) {
-        if (text == null || size == 0) {
-            if (mPhoneticNameTextView != null) {
-                mPhoneticNameTextView.setVisibility(View.GONE);
-            }
-        } else {
-            getPhoneticNameTextView();
-            setMarqueeText(mPhoneticNameTextView, text, size);
-            mPhoneticNameTextView.setVisibility(VISIBLE);
-        }
-    }
-
-    /**
-     * Returns the text view for the phonetic name, creating it if necessary.
-     */
-    public TextView getPhoneticNameTextView() {
-        if (mPhoneticNameTextView == null) {
-            mPhoneticNameTextView = new TextView(mContext);
-            mPhoneticNameTextView.setSingleLine(true);
-            mPhoneticNameTextView.setEllipsize(getTextEllipsis());
-            mPhoneticNameTextView.setTextAppearance(mContext, android.R.style.TextAppearance_Small);
-            mPhoneticNameTextView.setTypeface(mPhoneticNameTextView.getTypeface(), Typeface.BOLD);
-            mPhoneticNameTextView.setActivated(isActivated());
-            addView(mPhoneticNameTextView);
-        }
-        return mPhoneticNameTextView;
-    }
-
-    /**
-     * Adds or updates a text view for the data label.
-     */
-    public void setLabel(CharSequence text) {
-        if (TextUtils.isEmpty(text)) {
-            if (mLabelView != null) {
-                mLabelView.setVisibility(View.GONE);
-            }
-        } else {
-            getLabelView();
-            setMarqueeText(mLabelView, text);
-            mLabelView.setVisibility(VISIBLE);
-        }
-    }
-
-    /**
-     * Returns the text view for the data label, creating it if necessary.
-     */
-    public TextView getLabelView() {
-        if (mLabelView == null) {
-            mLabelView = new TextView(mContext);
-            mLabelView.setSingleLine(true);
-            mLabelView.setEllipsize(getTextEllipsis());
-            mLabelView.setTextAppearance(mContext, android.R.style.TextAppearance_Small);
-            if (mPhotoPosition == PhotoPosition.LEFT) {
-                mLabelView.setTextSize(TypedValue.COMPLEX_UNIT_SP, mCountViewTextSize);
-                mLabelView.setAllCaps(true);
-                mLabelView.setGravity(Gravity.RIGHT);
-            } else {
-                mLabelView.setTypeface(mLabelView.getTypeface(), Typeface.BOLD);
-            }
-            mLabelView.setActivated(isActivated());
-            addView(mLabelView);
-        }
-        return mLabelView;
-    }
-
-    /**
-     * Adds or updates a text view for the data element.
-     */
-    public void setData(char[] text, int size) {
-        if (text == null || size == 0) {
-            if (mDataView != null) {
-                mDataView.setVisibility(View.GONE);
-            }
-        } else {
-            getDataView();
-            setMarqueeText(mDataView, text, size);
-            mDataView.setVisibility(VISIBLE);
-        }
-    }
-
-    private void setMarqueeText(TextView textView, char[] text, int size) {
-        if (getTextEllipsis() == TruncateAt.MARQUEE) {
-            setMarqueeText(textView, new String(text, 0, size));
-        } else {
-            textView.setText(text, 0, size);
-        }
-    }
-
-    private void setMarqueeText(TextView textView, CharSequence text) {
-        if (getTextEllipsis() == TruncateAt.MARQUEE) {
-            // To show MARQUEE correctly (with END effect during non-active state), we need
-            // to build Spanned with MARQUEE in addition to TextView's ellipsize setting.
-            final SpannableString spannable = new SpannableString(text);
-            spannable.setSpan(TruncateAt.MARQUEE, 0, spannable.length(),
-                    Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
-            textView.setText(spannable);
-        } else {
-            textView.setText(text);
-        }
-    }
-
-    /**
-     * Returns the text view for the data text, creating it if necessary.
-     */
-    public TextView getDataView() {
-        if (mDataView == null) {
-            mDataView = new TextView(mContext);
-            mDataView.setSingleLine(true);
-            mDataView.setEllipsize(getTextEllipsis());
-            mDataView.setTextAppearance(mContext, android.R.style.TextAppearance_Small);
-            mDataView.setActivated(isActivated());
-            addView(mDataView);
-        }
-        return mDataView;
-    }
-
-    /**
-     * Adds or updates a text view for the search snippet.
-     */
-    public void setSnippet(String text) {
-        if (TextUtils.isEmpty(text)) {
-            if (mSnippetView != null) {
-                mSnippetView.setVisibility(View.GONE);
-            }
-        } else {
-            mPrefixHighlighter.setText(getSnippetView(), text, mHighlightedPrefix);
-            mSnippetView.setVisibility(VISIBLE);
-        }
-    }
-
-    /**
-     * Returns the text view for the search snippet, creating it if necessary.
-     */
-    public TextView getSnippetView() {
-        if (mSnippetView == null) {
-            mSnippetView = new TextView(mContext);
-            mSnippetView.setSingleLine(true);
-            mSnippetView.setEllipsize(getTextEllipsis());
-            mSnippetView.setTextAppearance(mContext, android.R.style.TextAppearance_Small);
-            mSnippetView.setTypeface(mSnippetView.getTypeface(), Typeface.BOLD);
-            mSnippetView.setActivated(isActivated());
-            addView(mSnippetView);
-        }
-        return mSnippetView;
-    }
-
-    /**
-     * Returns the text view for the status, creating it if necessary.
-     */
-    public TextView getStatusView() {
-        if (mStatusView == null) {
-            mStatusView = new TextView(mContext);
-            mStatusView.setSingleLine(true);
-            mStatusView.setEllipsize(getTextEllipsis());
-            mStatusView.setTextAppearance(mContext, android.R.style.TextAppearance_Small);
-            mStatusView.setTextColor(mSecondaryTextColor);
-            mStatusView.setActivated(isActivated());
-            addView(mStatusView);
-        }
-        return mStatusView;
-    }
-
-    /**
-     * Returns the text view for the contacts count, creating it if necessary.
-     */
-    public TextView getCountView() {
-        if (mCountView == null) {
-            mCountView = new TextView(mContext);
-            mCountView.setSingleLine(true);
-            mCountView.setEllipsize(getTextEllipsis());
-            mCountView.setTextAppearance(mContext, android.R.style.TextAppearance_Medium);
-            mCountView.setTextColor(R.color.contact_count_text_color);
-            addView(mCountView);
-        }
-        return mCountView;
-    }
-
-    /**
-     * Adds or updates a text view for the contacts count.
-     */
-    public void setCountView(CharSequence text) {
-        if (TextUtils.isEmpty(text)) {
-            if (mCountView != null) {
-                mCountView.setVisibility(View.GONE);
-            }
-        } else {
-            getCountView();
-            setMarqueeText(mCountView, text);
-            mCountView.setTextSize(TypedValue.COMPLEX_UNIT_PX, mCountViewTextSize);
-            mCountView.setGravity(Gravity.CENTER_VERTICAL);
-            mCountView.setTextColor(mContactsCountTextColor);
-            mCountView.setVisibility(VISIBLE);
-        }
-    }
-
-    /**
-     * Adds or updates a text view for the status.
-     */
-    public void setStatus(CharSequence text) {
-        if (TextUtils.isEmpty(text)) {
-            if (mStatusView != null) {
-                mStatusView.setVisibility(View.GONE);
-            }
-        } else {
-            getStatusView();
-            setMarqueeText(mStatusView, text);
-            mStatusView.setVisibility(VISIBLE);
-        }
-    }
-
-    /**
-     * Adds or updates the presence icon view.
-     */
-    public void setPresence(Drawable icon) {
-        if (icon != null) {
-            if (mPresenceIcon == null) {
-                mPresenceIcon = new ImageView(mContext);
-                addView(mPresenceIcon);
-            }
-            mPresenceIcon.setImageDrawable(icon);
-            mPresenceIcon.setScaleType(ScaleType.CENTER);
-            mPresenceIcon.setVisibility(View.VISIBLE);
-        } else {
-            if (mPresenceIcon != null) {
-                mPresenceIcon.setVisibility(View.GONE);
-            }
-        }
-    }
-
-    private TruncateAt getTextEllipsis() {
-        return TruncateAt.MARQUEE;
-    }
-
-    public void showDisplayName(Cursor cursor, int nameColumnIndex, int displayOrder) {
-        CharSequence name = cursor.getString(nameColumnIndex);
-        if (!TextUtils.isEmpty(name)) {
-            name = mPrefixHighlighter.apply(name, mHighlightedPrefix);
-        } else {
-            name = mUnknownNameText;
-        }
-        setMarqueeText(getNameTextView(), name);
-
-        // Since the quick contact content description is derived from the display name and there is
-        // no guarantee that when the quick contact is initialized the display name is already set,
-        // do it here too.
-        if (mQuickContact != null) {
-            mQuickContact.setContentDescription(mContext.getString(
-                    R.string.description_quick_contact_for, mNameTextView.getText()));
-        }
-    }
-
-    public void hideDisplayName() {
-        if (mNameTextView != null) {
-            removeView(mNameTextView);
-            mNameTextView = null;
-        }
-    }
-
-    public void showPhoneticName(Cursor cursor, int phoneticNameColumnIndex) {
-        cursor.copyStringToBuffer(phoneticNameColumnIndex, mPhoneticNameBuffer);
-        int phoneticNameSize = mPhoneticNameBuffer.sizeCopied;
-        if (phoneticNameSize != 0) {
-            setPhoneticName(mPhoneticNameBuffer.data, phoneticNameSize);
-        } else {
-            setPhoneticName(null, 0);
-        }
-    }
-
-    public void hidePhoneticName() {
-        if (mPhoneticNameTextView != null) {
-            removeView(mPhoneticNameTextView);
-            mPhoneticNameTextView = null;
-        }
-    }
-
-    /**
-     * Sets the proper icon (star or presence or nothing) and/or status message.
-     */
-    public void showPresenceAndStatusMessage(Cursor cursor, int presenceColumnIndex,
-            int contactStatusColumnIndex) {
-        Drawable icon = null;
-        int presence = 0;
-        if (!cursor.isNull(presenceColumnIndex)) {
-            presence = cursor.getInt(presenceColumnIndex);
-            icon = ContactPresenceIconUtil.getPresenceIcon(getContext(), presence);
-        }
-        setPresence(icon);
-
-        String statusMessage = null;
-        if (contactStatusColumnIndex != 0 && !cursor.isNull(contactStatusColumnIndex)) {
-            statusMessage = cursor.getString(contactStatusColumnIndex);
-        }
-        // If there is no status message from the contact, but there was a presence value, then use
-        // the default status message string
-        if (statusMessage == null && presence != 0) {
-            statusMessage = ContactStatusUtil.getStatusString(getContext(), presence);
-        }
-        setStatus(statusMessage);
-    }
-
-    /**
-     * Shows search snippet.
-     */
-    public void showSnippet(Cursor cursor, int summarySnippetColumnIndex) {
-        if (cursor.getColumnCount() <= summarySnippetColumnIndex) {
-            setSnippet(null);
-            return;
-        }
-        String snippet;
-        String columnContent = cursor.getString(summarySnippetColumnIndex);
-
-        // Do client side snippeting if provider didn't do it
-        Bundle extras = cursor.getExtras();
-        if (extras.getBoolean(ContactsContract.DEFERRED_SNIPPETING)) {
-            int displayNameIndex = cursor.getColumnIndex(Contacts.DISPLAY_NAME);
-
-            snippet = ContactsContract.snippetize(columnContent,
-                    displayNameIndex < 0 ? null : cursor.getString(displayNameIndex),
-                            extras.getString(ContactsContract.DEFERRED_SNIPPETING_QUERY),
-                            DefaultContactListAdapter.SNIPPET_START_MATCH,
-                            DefaultContactListAdapter.SNIPPET_END_MATCH,
-                            DefaultContactListAdapter.SNIPPET_ELLIPSIS,
-                            DefaultContactListAdapter.SNIPPET_MAX_TOKENS);
-        } else {
-            snippet = columnContent;
-        }
-
-        if (snippet != null) {
-            int from = 0;
-            int to = snippet.length();
-            int start = snippet.indexOf(DefaultContactListAdapter.SNIPPET_START_MATCH);
-            if (start == -1) {
-                snippet = null;
-            } else {
-                int firstNl = snippet.lastIndexOf('\n', start);
-                if (firstNl != -1) {
-                    from = firstNl + 1;
-                }
-                int end = snippet.lastIndexOf(DefaultContactListAdapter.SNIPPET_END_MATCH);
-                if (end != -1) {
-                    int lastNl = snippet.indexOf('\n', end);
-                    if (lastNl != -1) {
-                        to = lastNl;
-                    }
-                }
-
-                StringBuilder sb = new StringBuilder();
-                for (int i = from; i < to; i++) {
-                    char c = snippet.charAt(i);
-                    if (c != DefaultContactListAdapter.SNIPPET_START_MATCH &&
-                            c != DefaultContactListAdapter.SNIPPET_END_MATCH) {
-                        sb.append(c);
-                    }
-                }
-                snippet = sb.toString();
-            }
-        }
-        setSnippet(snippet);
-    }
-
-    /**
-     * Shows data element (e.g. phone number).
-     */
-    public void showData(Cursor cursor, int dataColumnIndex) {
-        cursor.copyStringToBuffer(dataColumnIndex, mDataBuffer);
-        setData(mDataBuffer.data, mDataBuffer.sizeCopied);
-    }
-
-    public void setActivatedStateSupported(boolean flag) {
-        this.mActivatedStateSupported = flag;
-    }
-
-    @Override
-    public void requestLayout() {
-        // We will assume that once measured this will not need to resize
-        // itself, so there is no need to pass the layout request to the parent
-        // view (ListView).
-        forceLayout();
-    }
-
-    public void setPhotoPosition(PhotoPosition photoPosition) {
-        mPhotoPosition = photoPosition;
-    }
-
-    public PhotoPosition getPhotoPosition() {
-        return mPhotoPosition;
-    }
-
-    /**
-     * Specifies left and right margin for selection bounds. See also
-     * {@link #adjustListItemSelectionBounds(Rect)}.
-     */
-    public void setSelectionBoundsHorizontalMargin(int left, int right) {
-        mSelectionBoundsMarginLeft = left;
-        mSelectionBoundsMarginRight = right;
-    }
-}
diff --git a/src/com/android/contacts/list/ContactListPinnedHeaderView.java b/src/com/android/contacts/list/ContactListPinnedHeaderView.java
deleted file mode 100644
index 34a56d6..0000000
--- a/src/com/android/contacts/list/ContactListPinnedHeaderView.java
+++ /dev/null
@@ -1,178 +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.list;
-
-import android.content.Context;
-import android.content.res.TypedArray;
-import android.graphics.Color;
-import android.graphics.Typeface;
-import android.text.TextUtils;
-import android.util.AttributeSet;
-import android.util.TypedValue;
-import android.view.Gravity;
-import android.view.View;
-import android.view.ViewGroup;
-import android.widget.TextView;
-
-import com.android.contacts.R;
-
-/**
- * A custom view for the pinned section header shown at the top of the contact list.
- */
-public class ContactListPinnedHeaderView extends ViewGroup {
-
-    protected final Context mContext;
-
-    private final int mHeaderTextColor;
-    private final int mHeaderTextIndent;
-    private final int mHeaderTextSize;
-    private final int mHeaderUnderlineHeight;
-    private final int mHeaderUnderlineColor;
-    private final int mPaddingRight;
-    private final int mPaddingLeft;
-    private final int mContactsCountTextColor;
-    private final int mCountViewTextSize;
-
-    private int mHeaderBackgroundHeight;
-    private TextView mHeaderTextView;
-    private TextView mCountTextView = null;
-    private View mHeaderDivider;
-
-    public ContactListPinnedHeaderView(Context context, AttributeSet attrs) {
-        super(context, attrs);
-        mContext = context;
-
-        TypedArray a = getContext().obtainStyledAttributes(attrs, R.styleable.ContactListItemView);
-
-        mHeaderTextIndent = a.getDimensionPixelOffset(
-                R.styleable.ContactListItemView_list_item_header_text_indent, 0);
-        mHeaderTextColor = a.getColor(
-                R.styleable.ContactListItemView_list_item_header_text_color, Color.BLACK);
-        mHeaderTextSize = a.getDimensionPixelSize(
-                R.styleable.ContactListItemView_list_item_header_text_size, 12);
-        mHeaderUnderlineHeight = a.getDimensionPixelSize(
-                R.styleable.ContactListItemView_list_item_header_underline_height, 1);
-        mHeaderUnderlineColor = a.getColor(
-                R.styleable.ContactListItemView_list_item_header_underline_color, 0);
-        mHeaderBackgroundHeight = a.getDimensionPixelSize(
-                R.styleable.ContactListItemView_list_item_header_height, 30);
-        mPaddingLeft = a.getDimensionPixelOffset(
-                R.styleable.ContactListItemView_list_item_padding_left, 0);
-        mPaddingRight = a.getDimensionPixelOffset(
-                R.styleable.ContactListItemView_list_item_padding_right, 0);
-        mContactsCountTextColor = a.getColor(
-                R.styleable.ContactListItemView_list_item_contacts_count_text_color, Color.BLACK);
-        mCountViewTextSize = (int)a.getDimensionPixelSize(
-                R.styleable.ContactListItemView_list_item_contacts_count_text_size, 12);
-
-        a.recycle();
-
-        mHeaderTextView = new TextView(mContext);
-        mHeaderTextView.setTextColor(mHeaderTextColor);
-        mHeaderTextView.setTextSize(TypedValue.COMPLEX_UNIT_PX, mHeaderTextSize);
-        mHeaderTextView.setTypeface(mHeaderTextView.getTypeface(), Typeface.BOLD);
-        mHeaderTextView.setGravity(Gravity.CENTER_VERTICAL);
-        mHeaderTextView.setAllCaps(true);
-        addView(mHeaderTextView);
-        mHeaderDivider = new View(mContext);
-        mHeaderDivider.setBackgroundColor(mHeaderUnderlineColor);
-        addView(mHeaderDivider);
-    }
-
-    @Override
-    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
-
-        // We will match parent's width and wrap content vertically.
-        int width = resolveSize(0, widthMeasureSpec);
-
-        mHeaderTextView.measure(
-                MeasureSpec.makeMeasureSpec(width, MeasureSpec.AT_MOST),
-                MeasureSpec.makeMeasureSpec(mHeaderBackgroundHeight, MeasureSpec.EXACTLY));
-        if (isViewMeasurable(mCountTextView)) {
-            mCountTextView.measure(
-                    MeasureSpec.makeMeasureSpec(width, MeasureSpec.AT_MOST),
-                    MeasureSpec.makeMeasureSpec(mHeaderBackgroundHeight, MeasureSpec.EXACTLY));
-        }
-
-        setMeasuredDimension(width, mHeaderBackgroundHeight + mHeaderUnderlineHeight);
-    }
-
-    @Override
-    protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
-        int width = right - left;
-
-        // Take into account left and right padding when laying out the below views.
-        mHeaderTextView.layout(mHeaderTextIndent + mPaddingLeft,
-                0,
-                mHeaderTextView.getMeasuredWidth() + mHeaderTextIndent + mPaddingLeft,
-                mHeaderBackgroundHeight);
-
-        if (isViewMeasurable(mCountTextView)) {
-            mCountTextView.layout(width - mPaddingRight - mCountTextView.getMeasuredWidth(),
-                    0,
-                    width - mPaddingRight,
-                    mHeaderBackgroundHeight);
-        }
-
-        mHeaderDivider.layout(mPaddingLeft,
-                mHeaderBackgroundHeight,
-                width - mPaddingRight,
-                mHeaderBackgroundHeight + mHeaderUnderlineHeight);
-    }
-
-    /**
-     * Sets section header or makes it invisible if the title is null.
-     */
-    public void setSectionHeader(String title) {
-        if (!TextUtils.isEmpty(title)) {
-            mHeaderTextView.setText(title);
-            mHeaderTextView.setVisibility(View.VISIBLE);
-            mHeaderDivider.setVisibility(View.VISIBLE);
-        } else {
-            mHeaderTextView.setVisibility(View.GONE);
-            mHeaderDivider.setVisibility(View.GONE);
-        }
-    }
-
-    @Override
-    public void requestLayout() {
-        // We will assume that once measured this will not need to resize
-        // itself, so there is no need to pass the layout request to the parent
-        // view (ListView).
-        forceLayout();
-    }
-
-    public void setCountView(String count) {
-        if (mCountTextView == null) {
-            mCountTextView = new TextView(mContext);
-            mCountTextView.setTextColor(mContactsCountTextColor);
-            mCountTextView.setTextSize(TypedValue.COMPLEX_UNIT_PX, mCountViewTextSize);
-            mCountTextView.setGravity(Gravity.CENTER_VERTICAL);
-            addView(mCountTextView);
-        }
-        mCountTextView.setText(count);
-        if (count == null || count.isEmpty()) {
-            mCountTextView.setVisibility(View.GONE);
-        } else {
-            mCountTextView.setVisibility(View.VISIBLE);
-        }
-    }
-
-    private boolean isViewMeasurable(View view) {
-        return (view != null && view.getVisibility() == View.VISIBLE);
-    }
-}
diff --git a/src/com/android/contacts/list/ContactNameHighlightingAnimation.java b/src/com/android/contacts/list/ContactNameHighlightingAnimation.java
index ddea603..46fe88b 100644
--- a/src/com/android/contacts/list/ContactNameHighlightingAnimation.java
+++ b/src/com/android/contacts/list/ContactNameHighlightingAnimation.java
@@ -18,6 +18,7 @@
 import android.view.View;
 import android.widget.ListView;
 
+import com.android.contacts.common.list.ContactListItemView;
 import com.android.contacts.widget.TextHighlightingAnimation;
 
 /**
diff --git a/src/com/android/contacts/list/ContactPickerFragment.java b/src/com/android/contacts/list/ContactPickerFragment.java
index bda62f3..2de71be 100644
--- a/src/com/android/contacts/list/ContactPickerFragment.java
+++ b/src/com/android/contacts/list/ContactPickerFragment.java
@@ -24,6 +24,11 @@
 import android.widget.AdapterView;
 
 import com.android.contacts.R;
+import com.android.contacts.common.list.ContactEntryListAdapter;
+import com.android.contacts.common.list.ContactListAdapter;
+import com.android.contacts.common.list.ContactListFilter;
+import com.android.contacts.common.list.DefaultContactListAdapter;
+import com.android.contacts.common.list.DirectoryListLoader;
 import com.android.contacts.list.ShortcutIntentBuilder.OnShortcutIntentCreatedListener;
 
 /**
diff --git a/src/com/android/contacts/list/ContactsSectionIndexer.java b/src/com/android/contacts/list/ContactsSectionIndexer.java
deleted file mode 100644
index c260667..0000000
--- a/src/com/android/contacts/list/ContactsSectionIndexer.java
+++ /dev/null
@@ -1,121 +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.list;
-
-import android.text.TextUtils;
-import android.widget.SectionIndexer;
-
-import java.util.Arrays;
-
-/**
- * A section indexer that is configured with precomputed section titles and
- * their respective counts.
- */
-public class ContactsSectionIndexer implements SectionIndexer {
-
-    private String[] mSections;
-    private int[] mPositions;
-    private int mCount;
-    private static final String BLANK_HEADER_STRING = " ";
-
-    /**
-     * Constructor.
-     *
-     * @param sections a non-null array
-     * @param counts a non-null array of the same size as <code>sections</code>
-     */
-    public ContactsSectionIndexer(String[] sections, int[] counts) {
-        if (sections == null || counts == null) {
-            throw new NullPointerException();
-        }
-
-        if (sections.length != counts.length) {
-            throw new IllegalArgumentException(
-                    "The sections and counts arrays must have the same length");
-        }
-
-        // TODO process sections/counts based on current locale and/or specific section titles
-
-        this.mSections = sections;
-        mPositions = new int[counts.length];
-        int position = 0;
-        for (int i = 0; i < counts.length; i++) {
-            if (TextUtils.isEmpty(mSections[i])) {
-                mSections[i] = BLANK_HEADER_STRING;
-            } else if (!mSections[i].equals(BLANK_HEADER_STRING)) {
-                mSections[i] = mSections[i].trim();
-            }
-
-            mPositions[i] = position;
-            position += counts[i];
-        }
-        mCount = position;
-    }
-
-    public Object[] getSections() {
-        return mSections;
-    }
-
-    public int getPositionForSection(int section) {
-        if (section < 0 || section >= mSections.length) {
-            return -1;
-        }
-
-        return mPositions[section];
-    }
-
-    public int getSectionForPosition(int position) {
-        if (position < 0 || position >= mCount) {
-            return -1;
-        }
-
-        int index = Arrays.binarySearch(mPositions, position);
-
-        /*
-         * Consider this example: section positions are 0, 3, 5; the supplied
-         * position is 4. The section corresponding to position 4 starts at
-         * position 3, so the expected return value is 1. Binary search will not
-         * find 4 in the array and thus will return -insertPosition-1, i.e. -3.
-         * To get from that number to the expected value of 1 we need to negate
-         * and subtract 2.
-         */
-        return index >= 0 ? index : -index - 2;
-    }
-
-    public void setProfileHeader(String header) {
-        if (mSections != null) {
-            // Don't do anything if the header is already set properly.
-            if (mSections.length > 0 && header.equals(mSections[0])) {
-                return;
-            }
-
-            // Since the section indexer isn't aware of the profile at the top, we need to add a
-            // special section at the top for it and shift everything else down.
-            String[] tempSections = new String[mSections.length + 1];
-            int[] tempPositions = new int[mPositions.length + 1];
-            tempSections[0] = header;
-            tempPositions[0] = 0;
-            for (int i = 1; i <= mPositions.length; i++) {
-                tempSections[i] = mSections[i - 1];
-                tempPositions[i] = mPositions[i - 1] + 1;
-            }
-            mSections = tempSections;
-            mPositions = tempPositions;
-            mCount++;
-        }
-    }
-}
diff --git a/src/com/android/contacts/list/DefaultContactBrowseListFragment.java b/src/com/android/contacts/list/DefaultContactBrowseListFragment.java
index 8f38045..c9c895b 100644
--- a/src/com/android/contacts/list/DefaultContactBrowseListFragment.java
+++ b/src/com/android/contacts/list/DefaultContactBrowseListFragment.java
@@ -32,6 +32,10 @@
 import android.widget.TextView;
 
 import com.android.contacts.R;
+import com.android.contacts.common.list.ContactListAdapter;
+import com.android.contacts.common.list.ContactListFilter;
+import com.android.contacts.common.list.DefaultContactListAdapter;
+import com.android.contacts.common.list.ProfileAndContactsLoader;
 import com.android.contacts.editor.ContactEditorFragment;
 import com.android.contacts.util.AccountFilterUtil;
 
diff --git a/src/com/android/contacts/list/DefaultContactListAdapter.java b/src/com/android/contacts/list/DefaultContactListAdapter.java
deleted file mode 100644
index 88c1db0..0000000
--- a/src/com/android/contacts/list/DefaultContactListAdapter.java
+++ /dev/null
@@ -1,222 +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.list;
-
-import android.content.ContentUris;
-import android.content.Context;
-import android.content.CursorLoader;
-import android.content.SharedPreferences;
-import android.database.Cursor;
-import android.net.Uri;
-import android.net.Uri.Builder;
-import android.preference.PreferenceManager;
-import android.provider.ContactsContract;
-import android.provider.ContactsContract.Contacts;
-import android.provider.ContactsContract.Directory;
-import android.provider.ContactsContract.SearchSnippetColumns;
-import android.text.TextUtils;
-import android.view.View;
-
-import com.android.contacts.preference.ContactsPreferences;
-
-import java.util.ArrayList;
-import java.util.List;
-
-/**
- * A cursor adapter for the {@link ContactsContract.Contacts#CONTENT_TYPE} content type.
- */
-public class DefaultContactListAdapter extends ContactListAdapter {
-
-    public static final char SNIPPET_START_MATCH = '\u0001';
-    public static final char SNIPPET_END_MATCH = '\u0001';
-    public static final String SNIPPET_ELLIPSIS = "\u2026";
-    public static final int SNIPPET_MAX_TOKENS = 5;
-
-    public static final String SNIPPET_ARGS = SNIPPET_START_MATCH + "," + SNIPPET_END_MATCH + ","
-            + SNIPPET_ELLIPSIS + "," + SNIPPET_MAX_TOKENS;
-
-    public DefaultContactListAdapter(Context context) {
-        super(context);
-    }
-
-    @Override
-    public void configureLoader(CursorLoader loader, long directoryId) {
-        if (loader instanceof ProfileAndContactsLoader) {
-            ((ProfileAndContactsLoader) loader).setLoadProfile(shouldIncludeProfile());
-        }
-
-        ContactListFilter filter = getFilter();
-        if (isSearchMode()) {
-            String query = getQueryString();
-            if (query == null) {
-                query = "";
-            }
-            query = query.trim();
-            if (TextUtils.isEmpty(query)) {
-                // Regardless of the directory, we don't want anything returned,
-                // so let's just send a "nothing" query to the local directory.
-                loader.setUri(Contacts.CONTENT_URI);
-                loader.setProjection(getProjection(false));
-                loader.setSelection("0");
-            } else {
-                Builder builder = Contacts.CONTENT_FILTER_URI.buildUpon();
-                builder.appendPath(query);      // Builder will encode the query
-                builder.appendQueryParameter(ContactsContract.DIRECTORY_PARAM_KEY,
-                        String.valueOf(directoryId));
-                if (directoryId != Directory.DEFAULT && directoryId != Directory.LOCAL_INVISIBLE) {
-                    builder.appendQueryParameter(ContactsContract.LIMIT_PARAM_KEY,
-                            String.valueOf(getDirectoryResultLimit()));
-                }
-                builder.appendQueryParameter(SearchSnippetColumns.SNIPPET_ARGS_PARAM_KEY,
-                        SNIPPET_ARGS);
-                builder.appendQueryParameter(SearchSnippetColumns.DEFERRED_SNIPPETING_KEY,"1");
-                loader.setUri(builder.build());
-                loader.setProjection(getProjection(true));
-            }
-        } else {
-            configureUri(loader, directoryId, filter);
-            loader.setProjection(getProjection(false));
-            configureSelection(loader, directoryId, filter);
-        }
-
-        String sortOrder;
-        if (getSortOrder() == ContactsContract.Preferences.SORT_ORDER_PRIMARY) {
-            sortOrder = Contacts.SORT_KEY_PRIMARY;
-        } else {
-            sortOrder = Contacts.SORT_KEY_ALTERNATIVE;
-        }
-
-        loader.setSortOrder(sortOrder);
-    }
-
-    protected void configureUri(CursorLoader loader, long directoryId, ContactListFilter filter) {
-        Uri uri = Contacts.CONTENT_URI;
-        if (filter != null && filter.filterType == ContactListFilter.FILTER_TYPE_SINGLE_CONTACT) {
-            String lookupKey = getSelectedContactLookupKey();
-            if (lookupKey != null) {
-                uri = Uri.withAppendedPath(Contacts.CONTENT_LOOKUP_URI, lookupKey);
-            } else {
-                uri = ContentUris.withAppendedId(Contacts.CONTENT_URI, getSelectedContactId());
-            }
-        }
-
-        if (directoryId == Directory.DEFAULT && isSectionHeaderDisplayEnabled()) {
-            uri = buildSectionIndexerUri(uri);
-        }
-
-        // The "All accounts" filter is the same as the entire contents of Directory.DEFAULT
-        if (filter != null
-                && filter.filterType != ContactListFilter.FILTER_TYPE_CUSTOM
-                && filter.filterType != ContactListFilter.FILTER_TYPE_SINGLE_CONTACT) {
-            final Uri.Builder builder = uri.buildUpon();
-            builder.appendQueryParameter(
-                    ContactsContract.DIRECTORY_PARAM_KEY, String.valueOf(Directory.DEFAULT));
-            if (filter.filterType == ContactListFilter.FILTER_TYPE_ACCOUNT) {
-                filter.addAccountQueryParameterToUrl(builder);
-            }
-            uri = builder.build();
-        }
-
-        loader.setUri(uri);
-    }
-
-    private void configureSelection(
-            CursorLoader loader, long directoryId, ContactListFilter filter) {
-        if (filter == null) {
-            return;
-        }
-
-        if (directoryId != Directory.DEFAULT) {
-            return;
-        }
-
-        StringBuilder selection = new StringBuilder();
-        List<String> selectionArgs = new ArrayList<String>();
-
-        switch (filter.filterType) {
-            case ContactListFilter.FILTER_TYPE_ALL_ACCOUNTS: {
-                // We have already added directory=0 to the URI, which takes care of this
-                // filter
-                break;
-            }
-            case ContactListFilter.FILTER_TYPE_SINGLE_CONTACT: {
-                // We have already added the lookup key to the URI, which takes care of this
-                // filter
-                break;
-            }
-            case ContactListFilter.FILTER_TYPE_STARRED: {
-                selection.append(Contacts.STARRED + "!=0");
-                break;
-            }
-            case ContactListFilter.FILTER_TYPE_WITH_PHONE_NUMBERS_ONLY: {
-                selection.append(Contacts.HAS_PHONE_NUMBER + "=1");
-                break;
-            }
-            case ContactListFilter.FILTER_TYPE_CUSTOM: {
-                selection.append(Contacts.IN_VISIBLE_GROUP + "=1");
-                if (isCustomFilterForPhoneNumbersOnly()) {
-                    selection.append(" AND " + Contacts.HAS_PHONE_NUMBER + "=1");
-                }
-                break;
-            }
-            case ContactListFilter.FILTER_TYPE_ACCOUNT: {
-                // We use query parameters for account filter, so no selection to add here.
-                break;
-            }
-        }
-        loader.setSelection(selection.toString());
-        loader.setSelectionArgs(selectionArgs.toArray(new String[0]));
-    }
-
-    @Override
-    protected void bindView(View itemView, int partition, Cursor cursor, int position) {
-        final ContactListItemView view = (ContactListItemView)itemView;
-
-        view.setHighlightedPrefix(isSearchMode() ? getUpperCaseQueryString() : null);
-
-        if (isSelectionVisible()) {
-            view.setActivated(isSelectedContact(partition, cursor));
-        }
-
-        bindSectionHeaderAndDivider(view, position, cursor);
-
-        if (isQuickContactEnabled()) {
-            bindQuickContact(view, partition, cursor, ContactQuery.CONTACT_PHOTO_ID,
-                    ContactQuery.CONTACT_PHOTO_URI, ContactQuery.CONTACT_ID,
-                    ContactQuery.CONTACT_LOOKUP_KEY);
-        } else {
-            if (getDisplayPhotos()) {
-                bindPhoto(view, partition, cursor);
-            }
-        }
-
-        bindName(view, cursor);
-        bindPresenceAndStatusMessage(view, cursor);
-
-        if (isSearchMode()) {
-            bindSearchSnippet(view, cursor);
-        } else {
-            view.setSnippet(null);
-        }
-    }
-
-    private boolean isCustomFilterForPhoneNumbersOnly() {
-        // TODO: this flag should not be stored in shared prefs.  It needs to be in the db.
-        SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(getContext());
-        return prefs.getBoolean(ContactsPreferences.PREF_DISPLAY_ONLY_PHONES,
-                ContactsPreferences.PREF_DISPLAY_ONLY_PHONES_DEFAULT);
-    }
-}
diff --git a/src/com/android/contacts/list/DirectoryListLoader.java b/src/com/android/contacts/list/DirectoryListLoader.java
deleted file mode 100644
index f85cc5e..0000000
--- a/src/com/android/contacts/list/DirectoryListLoader.java
+++ /dev/null
@@ -1,196 +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.list;
-
-import android.content.AsyncTaskLoader;
-import android.content.Context;
-import android.content.pm.PackageManager;
-import android.database.ContentObserver;
-import android.database.Cursor;
-import android.database.MatrixCursor;
-import android.net.Uri;
-import android.os.Handler;
-import android.provider.ContactsContract.Directory;
-import android.text.TextUtils;
-import android.util.Log;
-
-import com.android.contacts.R;
-
-/**
- * A specialized loader for the list of directories, see {@link Directory}.
- */
-public class DirectoryListLoader extends AsyncTaskLoader<Cursor> {
-
-    private static final String TAG = "ContactEntryListAdapter";
-
-    public static final int SEARCH_MODE_NONE = 0;
-    public static final int SEARCH_MODE_DEFAULT = 1;
-    public static final int SEARCH_MODE_CONTACT_SHORTCUT = 2;
-    public static final int SEARCH_MODE_DATA_SHORTCUT = 3;
-
-    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,
-            Directory.PHOTO_SUPPORT,
-        };
-
-        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;
-        public static final int PHOTO_SUPPORT = 4;
-    }
-
-    public static final String DIRECTORY_TYPE = "directoryType";
-
-    private static final String[] RESULT_PROJECTION = {
-        Directory._ID,
-        DIRECTORY_TYPE,
-        Directory.DISPLAY_NAME,
-        Directory.PHOTO_SUPPORT,
-    };
-
-    private final ContentObserver mObserver = new ContentObserver(new Handler()) {
-        @Override
-        public void onChange(boolean selfChange) {
-            forceLoad();
-        }
-    };
-
-    private int mDirectorySearchMode;
-    private boolean mLocalInvisibleDirectoryEnabled;
-
-    private MatrixCursor mDefaultDirectoryList;
-
-    public DirectoryListLoader(Context context) {
-        super(context);
-    }
-
-    public void setDirectorySearchMode(int mode) {
-        mDirectorySearchMode = mode;
-    }
-
-    /**
-     * A flag that indicates whether the {@link Directory#LOCAL_INVISIBLE} directory should
-     * be included in the results.
-     */
-    public void setLocalInvisibleDirectoryEnabled(boolean flag) {
-        this.mLocalInvisibleDirectoryEnabled = flag;
-    }
-
-    @Override
-    protected void onStartLoading() {
-        getContext().getContentResolver().
-                registerContentObserver(Directory.CONTENT_URI, false, mObserver);
-        forceLoad();
-    }
-
-    @Override
-    protected void onStopLoading() {
-        getContext().getContentResolver().unregisterContentObserver(mObserver);
-    }
-
-    @Override
-    public Cursor loadInBackground() {
-        if (mDirectorySearchMode == SEARCH_MODE_NONE) {
-            return getDefaultDirectories();
-        }
-
-        MatrixCursor result = new MatrixCursor(RESULT_PROJECTION);
-        Context context = getContext();
-        PackageManager pm = context.getPackageManager();
-        String selection;
-        switch (mDirectorySearchMode) {
-            case SEARCH_MODE_DEFAULT:
-                selection = mLocalInvisibleDirectoryEnabled ? null
-                        : (Directory._ID + "!=" + Directory.LOCAL_INVISIBLE);
-                break;
-
-            case SEARCH_MODE_CONTACT_SHORTCUT:
-                selection = Directory.SHORTCUT_SUPPORT + "=" + Directory.SHORTCUT_SUPPORT_FULL
-                        + (mLocalInvisibleDirectoryEnabled ? ""
-                                : (" AND " + Directory._ID + "!=" + Directory.LOCAL_INVISIBLE));
-                break;
-
-            case SEARCH_MODE_DATA_SHORTCUT:
-                selection = Directory.SHORTCUT_SUPPORT + " IN ("
-                        + Directory.SHORTCUT_SUPPORT_FULL + ", "
-                        + Directory.SHORTCUT_SUPPORT_DATA_ITEMS_ONLY + ")"
-                        + (mLocalInvisibleDirectoryEnabled ? ""
-                                : (" AND " + Directory._ID + "!=" + Directory.LOCAL_INVISIBLE));
-                break;
-
-            default:
-                throw new RuntimeException(
-                        "Unsupported directory search mode: " + mDirectorySearchMode);
-        }
-
-        Cursor cursor = context.getContentResolver().query(DirectoryQuery.URI,
-                DirectoryQuery.PROJECTION, selection, null, DirectoryQuery.ORDER_BY);
-        try {
-            while(cursor.moveToNext()) {
-                long directoryId = cursor.getLong(DirectoryQuery.ID);
-                String directoryType = null;
-
-                String packageName = cursor.getString(DirectoryQuery.PACKAGE_NAME);
-                int typeResourceId = cursor.getInt(DirectoryQuery.TYPE_RESOURCE_ID);
-                if (!TextUtils.isEmpty(packageName) && typeResourceId != 0) {
-                    try {
-                        directoryType = pm.getResourcesForApplication(packageName)
-                                .getString(typeResourceId);
-                    } catch (Exception e) {
-                        Log.e(TAG, "Cannot obtain directory type from package: " + packageName);
-                    }
-                }
-                String displayName = cursor.getString(DirectoryQuery.DISPLAY_NAME);
-                int photoSupport = cursor.getInt(DirectoryQuery.PHOTO_SUPPORT);
-                result.addRow(new Object[]{directoryId, directoryType, displayName, photoSupport});
-            }
-        } finally {
-            cursor.close();
-        }
-
-        return result;
-    }
-
-    private Cursor getDefaultDirectories() {
-        if (mDefaultDirectoryList == null) {
-            mDefaultDirectoryList = new MatrixCursor(RESULT_PROJECTION);
-            mDefaultDirectoryList.addRow(new Object[] {
-                    Directory.DEFAULT,
-                    getContext().getString(R.string.contactsList),
-                    null
-            });
-            mDefaultDirectoryList.addRow(new Object[] {
-                    Directory.LOCAL_INVISIBLE,
-                    getContext().getString(R.string.local_invisible_directory),
-                    null
-            });
-        }
-        return mDefaultDirectoryList;
-    }
-
-    @Override
-    protected void onReset() {
-        stopLoading();
-    }
-}
diff --git a/src/com/android/contacts/list/DirectoryPartition.java b/src/com/android/contacts/list/DirectoryPartition.java
deleted file mode 100644
index c1fd533..0000000
--- a/src/com/android/contacts/list/DirectoryPartition.java
+++ /dev/null
@@ -1,109 +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.list;
-
-import android.provider.ContactsContract.Directory;
-
-import com.android.common.widget.CompositeCursorAdapter;
-
-/**
- * Model object for a {@link Directory} row.
- */
-public final class DirectoryPartition extends CompositeCursorAdapter.Partition {
-
-    public static final int STATUS_NOT_LOADED = 0;
-    public static final int STATUS_LOADING = 1;
-    public static final int STATUS_LOADED = 2;
-
-    private long mDirectoryId;
-    private String mDirectoryType;
-    private String mDisplayName;
-    private int mStatus;
-    private boolean mPriorityDirectory;
-    private boolean mPhotoSupported;
-
-    public DirectoryPartition(boolean showIfEmpty, boolean hasHeader) {
-        super(showIfEmpty, hasHeader);
-    }
-
-    /**
-     * Directory ID, see {@link Directory}.
-     */
-    public long getDirectoryId() {
-        return mDirectoryId;
-    }
-
-    public void setDirectoryId(long directoryId) {
-        this.mDirectoryId = directoryId;
-    }
-
-    /**
-     * Directory type resolved from {@link Directory#PACKAGE_NAME} and
-     * {@link Directory#TYPE_RESOURCE_ID};
-     */
-    public String getDirectoryType() {
-        return mDirectoryType;
-    }
-
-    public void setDirectoryType(String directoryType) {
-        this.mDirectoryType = directoryType;
-    }
-
-    /**
-     * See {@link Directory#DISPLAY_NAME}.
-     */
-    public String getDisplayName() {
-        return mDisplayName;
-    }
-
-    public void setDisplayName(String displayName) {
-        this.mDisplayName = displayName;
-    }
-
-    public int getStatus() {
-        return mStatus;
-    }
-
-    public void setStatus(int status) {
-        mStatus = status;
-    }
-
-    public boolean isLoading() {
-        return mStatus == STATUS_NOT_LOADED || mStatus == STATUS_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;
-    }
-
-    /**
-     * Returns true if this directory supports photos.
-     */
-    public boolean isPhotoSupported() {
-        return mPhotoSupported;
-    }
-
-    public void setPhotoSupported(boolean flag) {
-        this.mPhotoSupported = flag;
-    }
-}
diff --git a/src/com/android/contacts/list/EmailAddressListAdapter.java b/src/com/android/contacts/list/EmailAddressListAdapter.java
index c85abdd..4a32ae3 100644
--- a/src/com/android/contacts/list/EmailAddressListAdapter.java
+++ b/src/com/android/contacts/list/EmailAddressListAdapter.java
@@ -29,6 +29,9 @@
 import android.view.View;
 import android.view.ViewGroup;
 
+import com.android.contacts.common.list.ContactEntryListAdapter;
+import com.android.contacts.common.list.ContactListItemView;
+
 /**
  * A cursor adapter for the {@link Email#CONTENT_TYPE} content type.
  */
diff --git a/src/com/android/contacts/list/EmailAddressPickerFragment.java b/src/com/android/contacts/list/EmailAddressPickerFragment.java
index 41f470f..f4dd108 100644
--- a/src/com/android/contacts/list/EmailAddressPickerFragment.java
+++ b/src/com/android/contacts/list/EmailAddressPickerFragment.java
@@ -21,6 +21,8 @@
 import android.view.ViewGroup;
 
 import com.android.contacts.R;
+import com.android.contacts.common.list.ContactEntryListAdapter;
+import com.android.contacts.common.list.DirectoryListLoader;
 
 /**
  * Fragment containing an email list for picking.
diff --git a/src/com/android/contacts/list/JoinContactListAdapter.java b/src/com/android/contacts/list/JoinContactListAdapter.java
index 6e1a6b2..88259c1 100644
--- a/src/com/android/contacts/list/JoinContactListAdapter.java
+++ b/src/com/android/contacts/list/JoinContactListAdapter.java
@@ -31,6 +31,9 @@
 import android.widget.TextView;
 
 import com.android.contacts.R;
+import com.android.contacts.common.list.ContactListAdapter;
+import com.android.contacts.common.list.ContactListItemView;
+import com.android.contacts.common.list.DirectoryListLoader;
 
 public class JoinContactListAdapter extends ContactListAdapter {
 
diff --git a/src/com/android/contacts/list/LegacyContactListAdapter.java b/src/com/android/contacts/list/LegacyContactListAdapter.java
index dbdce5e..defc4e7 100644
--- a/src/com/android/contacts/list/LegacyContactListAdapter.java
+++ b/src/com/android/contacts/list/LegacyContactListAdapter.java
@@ -24,6 +24,9 @@
 import android.view.View;
 import android.view.ViewGroup;
 
+import com.android.contacts.common.list.ContactEntryListAdapter;
+import com.android.contacts.common.list.ContactListItemView;
+
 /**
  * A cursor adapter for the People.CONTENT_TYPE content type.
  */
diff --git a/src/com/android/contacts/list/LegacyPhoneNumberListAdapter.java b/src/com/android/contacts/list/LegacyPhoneNumberListAdapter.java
index 1ef106a..bc16a0b 100644
--- a/src/com/android/contacts/list/LegacyPhoneNumberListAdapter.java
+++ b/src/com/android/contacts/list/LegacyPhoneNumberListAdapter.java
@@ -26,6 +26,9 @@
 import android.view.View;
 import android.view.ViewGroup;
 
+import com.android.contacts.common.list.ContactEntryListAdapter;
+import com.android.contacts.common.list.ContactListItemView;
+
 /**
  * A cursor adapter for the Phones.CONTENT_TYPE content type.
  */
diff --git a/src/com/android/contacts/list/LegacyPostalAddressListAdapter.java b/src/com/android/contacts/list/LegacyPostalAddressListAdapter.java
index 91a0e3b..2819ed6 100644
--- a/src/com/android/contacts/list/LegacyPostalAddressListAdapter.java
+++ b/src/com/android/contacts/list/LegacyPostalAddressListAdapter.java
@@ -26,6 +26,9 @@
 import android.view.View;
 import android.view.ViewGroup;
 
+import com.android.contacts.common.list.ContactEntryListAdapter;
+import com.android.contacts.common.list.ContactListItemView;
+
 /**
  * A cursor adapter for the ContactMethods.CONTENT_TYPE content type.
  */
diff --git a/src/com/android/contacts/list/PhoneNumberListAdapter.java b/src/com/android/contacts/list/PhoneNumberListAdapter.java
index 6323cc1..169ceff 100644
--- a/src/com/android/contacts/list/PhoneNumberListAdapter.java
+++ b/src/com/android/contacts/list/PhoneNumberListAdapter.java
@@ -35,6 +35,9 @@
 import android.view.ViewGroup;
 
 import com.android.contacts.R;
+import com.android.contacts.common.list.ContactEntryListAdapter;
+import com.android.contacts.common.list.ContactListFilter;
+import com.android.contacts.common.list.ContactListItemView;
 
 import java.util.ArrayList;
 import java.util.List;
diff --git a/src/com/android/contacts/list/PhoneNumberPickerFragment.java b/src/com/android/contacts/list/PhoneNumberPickerFragment.java
index 17b5500..d029348 100644
--- a/src/com/android/contacts/list/PhoneNumberPickerFragment.java
+++ b/src/com/android/contacts/list/PhoneNumberPickerFragment.java
@@ -28,6 +28,10 @@
 import android.view.ViewGroup;
 
 import com.android.contacts.R;
+import com.android.contacts.common.list.ContactEntryListAdapter;
+import com.android.contacts.common.list.ContactListFilter;
+import com.android.contacts.common.list.ContactListItemView;
+import com.android.contacts.common.list.DirectoryListLoader;
 import com.android.contacts.list.ShortcutIntentBuilder.OnShortcutIntentCreatedListener;
 import com.android.contacts.util.AccountFilterUtil;
 
diff --git a/src/com/android/contacts/list/PostalAddressListAdapter.java b/src/com/android/contacts/list/PostalAddressListAdapter.java
index 5e3be30..a800c00 100644
--- a/src/com/android/contacts/list/PostalAddressListAdapter.java
+++ b/src/com/android/contacts/list/PostalAddressListAdapter.java
@@ -28,6 +28,9 @@
 import android.view.View;
 import android.view.ViewGroup;
 
+import com.android.contacts.common.list.ContactEntryListAdapter;
+import com.android.contacts.common.list.ContactListItemView;
+
 /**
  * A cursor adapter for the {@link StructuredPostal#CONTENT_TYPE} content type.
  */
diff --git a/src/com/android/contacts/list/PostalAddressPickerFragment.java b/src/com/android/contacts/list/PostalAddressPickerFragment.java
index 9bd4fc3..a874708 100644
--- a/src/com/android/contacts/list/PostalAddressPickerFragment.java
+++ b/src/com/android/contacts/list/PostalAddressPickerFragment.java
@@ -21,6 +21,8 @@
 import android.view.ViewGroup;
 
 import com.android.contacts.R;
+import com.android.contacts.common.list.ContactEntryListAdapter;
+import com.android.contacts.common.list.DirectoryListLoader;
 
 /**
  * Fragment containing a postal address list for picking.
diff --git a/src/com/android/contacts/list/ProfileAndContactsLoader.java b/src/com/android/contacts/list/ProfileAndContactsLoader.java
deleted file mode 100644
index 4291a40..0000000
--- a/src/com/android/contacts/list/ProfileAndContactsLoader.java
+++ /dev/null
@@ -1,90 +0,0 @@
-/*
- * Copyright (C) 2011 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 android.content.Context;
-import android.content.CursorLoader;
-import android.database.Cursor;
-import android.database.MatrixCursor;
-import android.database.MergeCursor;
-import android.os.Bundle;
-import android.provider.ContactsContract.Profile;
-
-import com.google.common.collect.Lists;
-
-import java.util.List;
-
-/**
- * A loader for use in the default contact list, which will also query for the user's profile
- * if configured to do so.
- */
-public class ProfileAndContactsLoader extends CursorLoader {
-
-    private boolean mLoadProfile;
-    private String[] mProjection;
-
-    public ProfileAndContactsLoader(Context context) {
-        super(context);
-    }
-
-    public void setLoadProfile(boolean flag) {
-        mLoadProfile = flag;
-    }
-
-    public void setProjection(String[] projection) {
-        super.setProjection(projection);
-        mProjection = projection;
-    }
-
-    @Override
-    public Cursor loadInBackground() {
-        // First load the profile, if enabled.
-        List<Cursor> cursors = Lists.newArrayList();
-        if (mLoadProfile) {
-            cursors.add(loadProfile());
-        }
-        final Cursor contactsCursor = super.loadInBackground();
-        cursors.add(contactsCursor);
-        return new MergeCursor(cursors.toArray(new Cursor[cursors.size()])) {
-            @Override
-            public Bundle getExtras() {
-                // Need to get the extras from the contacts cursor.
-                return contactsCursor.getExtras();
-            }
-        };
-    }
-
-    /**
-     * Loads the profile into a MatrixCursor.
-     */
-    private MatrixCursor loadProfile() {
-        Cursor cursor = getContext().getContentResolver().query(Profile.CONTENT_URI, mProjection,
-                null, null, null);
-        try {
-            MatrixCursor matrix = new MatrixCursor(mProjection);
-            Object[] row = new Object[mProjection.length];
-            while (cursor.moveToNext()) {
-                for (int i = 0; i < row.length; i++) {
-                    row[i] = cursor.getString(i);
-                }
-                matrix.addRow(row);
-            }
-            return matrix;
-        } finally {
-            cursor.close();
-        }
-    }
-}
diff --git a/src/com/android/contacts/preference/ContactsPreferences.java b/src/com/android/contacts/preference/ContactsPreferences.java
deleted file mode 100644
index e88417f..0000000
--- a/src/com/android/contacts/preference/ContactsPreferences.java
+++ /dev/null
@@ -1,160 +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.preference;
-
-import android.content.ContentResolver;
-import android.content.Context;
-import android.database.ContentObserver;
-import android.os.Handler;
-import android.provider.ContactsContract;
-import android.provider.Settings;
-import android.provider.Settings.SettingNotFoundException;
-
-import com.android.contacts.R;
-
-/**
- * Manages user preferences for contacts.
- */
-public final class ContactsPreferences extends ContentObserver {
-
-    public static final String PREF_DISPLAY_ONLY_PHONES = "only_phones";
-    public static final boolean PREF_DISPLAY_ONLY_PHONES_DEFAULT = false;
-
-    private Context mContext;
-    private int mSortOrder = -1;
-    private int mDisplayOrder = -1;
-    private ChangeListener mListener = null;
-    private Handler mHandler;
-
-    public ContactsPreferences(Context context) {
-        super(null);
-        mContext = context;
-        mHandler = new Handler();
-    }
-
-    public boolean isSortOrderUserChangeable() {
-        return mContext.getResources().getBoolean(R.bool.config_sort_order_user_changeable);
-    }
-
-    public int getDefaultSortOrder() {
-        if (mContext.getResources().getBoolean(R.bool.config_default_sort_order_primary)) {
-            return ContactsContract.Preferences.SORT_ORDER_PRIMARY;
-        } else {
-            return ContactsContract.Preferences.SORT_ORDER_ALTERNATIVE;
-        }
-    }
-
-    public int getSortOrder() {
-        if (!isSortOrderUserChangeable()) {
-            return getDefaultSortOrder();
-        }
-
-        if (mSortOrder == -1) {
-            try {
-                mSortOrder = Settings.System.getInt(mContext.getContentResolver(),
-                        ContactsContract.Preferences.SORT_ORDER);
-            } catch (SettingNotFoundException e) {
-                mSortOrder = getDefaultSortOrder();
-            }
-        }
-        return mSortOrder;
-    }
-
-    public void setSortOrder(int sortOrder) {
-        mSortOrder = sortOrder;
-        Settings.System.putInt(mContext.getContentResolver(),
-                ContactsContract.Preferences.SORT_ORDER, sortOrder);
-    }
-
-    public boolean isDisplayOrderUserChangeable() {
-        return mContext.getResources().getBoolean(R.bool.config_display_order_user_changeable);
-    }
-
-    public int getDefaultDisplayOrder() {
-        if (mContext.getResources().getBoolean(R.bool.config_default_display_order_primary)) {
-            return ContactsContract.Preferences.DISPLAY_ORDER_PRIMARY;
-        } else {
-            return ContactsContract.Preferences.DISPLAY_ORDER_ALTERNATIVE;
-        }
-    }
-
-    public int getDisplayOrder() {
-        if (!isDisplayOrderUserChangeable()) {
-            return getDefaultDisplayOrder();
-        }
-
-        if (mDisplayOrder == -1) {
-            try {
-                mDisplayOrder = Settings.System.getInt(mContext.getContentResolver(),
-                        ContactsContract.Preferences.DISPLAY_ORDER);
-            } catch (SettingNotFoundException e) {
-                mDisplayOrder = getDefaultDisplayOrder();
-            }
-        }
-        return mDisplayOrder;
-    }
-
-    public void setDisplayOrder(int displayOrder) {
-        mDisplayOrder = displayOrder;
-        Settings.System.putInt(mContext.getContentResolver(),
-                ContactsContract.Preferences.DISPLAY_ORDER, displayOrder);
-    }
-
-    public void registerChangeListener(ChangeListener listener) {
-        if (mListener != null) unregisterChangeListener();
-
-        mListener = listener;
-
-        // Reset preferences to "unknown" because they may have changed while the
-        // observer was unregistered.
-        mDisplayOrder = -1;
-        mSortOrder = -1;
-
-        final ContentResolver contentResolver = mContext.getContentResolver();
-        contentResolver.registerContentObserver(
-                Settings.System.getUriFor(
-                        ContactsContract.Preferences.SORT_ORDER), false, this);
-        contentResolver.registerContentObserver(
-                Settings.System.getUriFor(
-                        ContactsContract.Preferences.DISPLAY_ORDER), false, this);
-    }
-
-    public void unregisterChangeListener() {
-        if (mListener != null) {
-            mContext.getContentResolver().unregisterContentObserver(this);
-            mListener = null;
-        }
-    }
-
-    @Override
-    public void onChange(boolean selfChange) {
-        // This notification is not sent on the Ui thread. Use the previously created Handler
-        // to switch to the Ui thread
-        mHandler.post(new Runnable() {
-            @Override
-            public void run() {
-                mSortOrder = -1;
-                mDisplayOrder = -1;
-                if (mListener != null) mListener.onChange();
-            }
-        });
-    }
-
-    public interface ChangeListener {
-        void onChange();
-    }
-}
diff --git a/src/com/android/contacts/preference/DisplayOrderPreference.java b/src/com/android/contacts/preference/DisplayOrderPreference.java
index 8629384..81489a0 100644
--- a/src/com/android/contacts/preference/DisplayOrderPreference.java
+++ b/src/com/android/contacts/preference/DisplayOrderPreference.java
@@ -23,6 +23,7 @@
 import android.util.AttributeSet;
 
 import com.android.contacts.R;
+import com.android.contacts.common.preference.ContactsPreferences;
 
 /**
  * Custom preference: view-name-as (first name first or last name first).
diff --git a/src/com/android/contacts/preference/SortOrderPreference.java b/src/com/android/contacts/preference/SortOrderPreference.java
index 6ad4aad..da51eed 100644
--- a/src/com/android/contacts/preference/SortOrderPreference.java
+++ b/src/com/android/contacts/preference/SortOrderPreference.java
@@ -23,6 +23,7 @@
 import android.util.AttributeSet;
 
 import com.android.contacts.R;
+import com.android.contacts.common.preference.ContactsPreferences;
 
 /**
  * Custom preference: sort-by.
diff --git a/src/com/android/contacts/util/AccountFilterUtil.java b/src/com/android/contacts/util/AccountFilterUtil.java
index b95972c..a6df994 100644
--- a/src/com/android/contacts/util/AccountFilterUtil.java
+++ b/src/com/android/contacts/util/AccountFilterUtil.java
@@ -26,7 +26,7 @@
 
 import com.android.contacts.R;
 import com.android.contacts.list.AccountFilterActivity;
-import com.android.contacts.list.ContactListFilter;
+import com.android.contacts.common.list.ContactListFilter;
 import com.android.contacts.list.ContactListFilterController;
 
 /**
diff --git a/src/com/android/contacts/widget/AutoScrollListView.java b/src/com/android/contacts/widget/AutoScrollListView.java
deleted file mode 100644
index e9c1c42..0000000
--- a/src/com/android/contacts/widget/AutoScrollListView.java
+++ /dev/null
@@ -1,117 +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.widget;
-
-import android.content.Context;
-import android.util.AttributeSet;
-import android.widget.ListView;
-
-/**
- * A ListView that can be asked to scroll (smoothly or otherwise) to a specific
- * position.  This class takes advantage of similar functionality that exists
- * in {@link ListView} and enhances it.
- */
-public class AutoScrollListView extends ListView {
-
-    /**
-     * Position the element at about 1/3 of the list height
-     */
-    private static final float PREFERRED_SELECTION_OFFSET_FROM_TOP = 0.33f;
-
-    private int mRequestedScrollPosition = -1;
-    private boolean mSmoothScrollRequested;
-
-    public AutoScrollListView(Context context) {
-        super(context);
-    }
-
-    public AutoScrollListView(Context context, AttributeSet attrs) {
-        super(context, attrs);
-    }
-
-    public AutoScrollListView(Context context, AttributeSet attrs, int defStyle) {
-        super(context, attrs, defStyle);
-    }
-
-    /**
-     * Brings the specified position to view by optionally performing a jump-scroll maneuver:
-     * first it jumps to some position near the one requested and then does a smooth
-     * scroll to the requested position.  This creates an impression of full smooth
-     * scrolling without actually traversing the entire list.  If smooth scrolling is
-     * not requested, instantly positions the requested item at a preferred offset.
-     */
-    public void requestPositionToScreen(int position, boolean smoothScroll) {
-        mRequestedScrollPosition = position;
-        mSmoothScrollRequested = smoothScroll;
-        requestLayout();
-    }
-
-    @Override
-    protected void layoutChildren() {
-        super.layoutChildren();
-        if (mRequestedScrollPosition == -1) {
-            return;
-        }
-
-        final int position = mRequestedScrollPosition;
-        mRequestedScrollPosition = -1;
-
-        int firstPosition = getFirstVisiblePosition() + 1;
-        int lastPosition = getLastVisiblePosition();
-        if (position >= firstPosition && position <= lastPosition) {
-            return; // Already on screen
-        }
-
-        final int offset = (int) (getHeight() * PREFERRED_SELECTION_OFFSET_FROM_TOP);
-        if (!mSmoothScrollRequested) {
-            setSelectionFromTop(position, offset);
-
-            // Since we have changed the scrolling position, we need to redo child layout
-            // Calling "requestLayout" in the middle of a layout pass has no effect,
-            // so we call layoutChildren explicitly
-            super.layoutChildren();
-
-        } else {
-            // We will first position the list a couple of screens before or after
-            // the new selection and then scroll smoothly to it.
-            int twoScreens = (lastPosition - firstPosition) * 2;
-            int preliminaryPosition;
-            if (position < firstPosition) {
-                preliminaryPosition = position + twoScreens;
-                if (preliminaryPosition >= getCount()) {
-                    preliminaryPosition = getCount() - 1;
-                }
-                if (preliminaryPosition < firstPosition) {
-                    setSelection(preliminaryPosition);
-                    super.layoutChildren();
-                }
-            } else {
-                preliminaryPosition = position - twoScreens;
-                if (preliminaryPosition < 0) {
-                    preliminaryPosition = 0;
-                }
-                if (preliminaryPosition > lastPosition) {
-                    setSelection(preliminaryPosition);
-                    super.layoutChildren();
-                }
-            }
-
-
-            smoothScrollToPositionFromTop(position, offset);
-        }
-    }
-}
diff --git a/src/com/android/contacts/widget/IndexerListAdapter.java b/src/com/android/contacts/widget/IndexerListAdapter.java
deleted file mode 100644
index a5aeb0f..0000000
--- a/src/com/android/contacts/widget/IndexerListAdapter.java
+++ /dev/null
@@ -1,235 +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.widget;
-
-import android.content.Context;
-import android.view.View;
-import android.view.ViewGroup;
-import android.widget.ListView;
-import android.widget.SectionIndexer;
-
-/**
- * A list adapter that supports section indexer and a pinned header.
- */
-public abstract class IndexerListAdapter extends PinnedHeaderListAdapter implements SectionIndexer {
-
-    protected Context mContext;
-    private SectionIndexer mIndexer;
-    private int mIndexedPartition = 0;
-    private boolean mSectionHeaderDisplayEnabled;
-    private View mHeader;
-
-    /**
-     * An item view is displayed differently depending on whether it is placed
-     * at the beginning, middle or end of a section. It also needs to know the
-     * section header when it is at the beginning of a section. This object
-     * captures all this configuration.
-     */
-    public static final class Placement {
-        private int position = ListView.INVALID_POSITION;
-        public boolean firstInSection;
-        public boolean lastInSection;
-        public String sectionHeader;
-
-        public void invalidate() {
-            position = ListView.INVALID_POSITION;
-        }
-    }
-
-    private Placement mPlacementCache = new Placement();
-
-    /**
-     * Constructor.
-     */
-    public IndexerListAdapter(Context context) {
-        super(context);
-        mContext = context;
-    }
-
-    /**
-     * Creates a section header view that will be pinned at the top of the list
-     * as the user scrolls.
-     */
-    protected abstract View createPinnedSectionHeaderView(Context context, ViewGroup parent);
-
-    /**
-     * Sets the title in the pinned header as the user scrolls.
-     */
-    protected abstract void setPinnedSectionTitle(View pinnedHeaderView, String title);
-
-    /**
-     * Sets the contacts count in the pinned header.
-     */
-    protected abstract void setPinnedHeaderContactsCount(View header);
-
-    /**
-     * clears the contacts count in the pinned header and makes the view invisible.
-     */
-    protected abstract void clearPinnedHeaderContactsCount(View header);
-
-    public boolean isSectionHeaderDisplayEnabled() {
-        return mSectionHeaderDisplayEnabled;
-    }
-
-    public void setSectionHeaderDisplayEnabled(boolean flag) {
-        this.mSectionHeaderDisplayEnabled = flag;
-    }
-
-    public int getIndexedPartition() {
-        return mIndexedPartition;
-    }
-
-    public void setIndexedPartition(int partition) {
-        this.mIndexedPartition = partition;
-    }
-
-    public SectionIndexer getIndexer() {
-        return mIndexer;
-    }
-
-    public void setIndexer(SectionIndexer indexer) {
-        mIndexer = indexer;
-        mPlacementCache.invalidate();
-    }
-
-    public Object[] getSections() {
-        if (mIndexer == null) {
-            return new String[] { " " };
-        } else {
-            return mIndexer.getSections();
-        }
-    }
-
-    /**
-     * @return relative position of the section in the indexed partition
-     */
-    public int getPositionForSection(int sectionIndex) {
-        if (mIndexer == null) {
-            return -1;
-        }
-
-        return mIndexer.getPositionForSection(sectionIndex);
-    }
-
-    /**
-     * @param position relative position in the indexed partition
-     */
-    public int getSectionForPosition(int position) {
-        if (mIndexer == null) {
-            return -1;
-        }
-
-        return mIndexer.getSectionForPosition(position);
-    }
-
-    @Override
-    public int getPinnedHeaderCount() {
-        if (isSectionHeaderDisplayEnabled()) {
-            return super.getPinnedHeaderCount() + 1;
-        } else {
-            return super.getPinnedHeaderCount();
-        }
-    }
-
-    @Override
-    public View getPinnedHeaderView(int viewIndex, View convertView, ViewGroup parent) {
-        if (isSectionHeaderDisplayEnabled() && viewIndex == getPinnedHeaderCount() - 1) {
-            if (mHeader == null) {
-                mHeader = createPinnedSectionHeaderView(mContext, parent);
-            }
-            return mHeader;
-        } else {
-            return super.getPinnedHeaderView(viewIndex, convertView, parent);
-        }
-    }
-
-    @Override
-    public void configurePinnedHeaders(PinnedHeaderListView listView) {
-        super.configurePinnedHeaders(listView);
-
-        if (!isSectionHeaderDisplayEnabled()) {
-            return;
-        }
-
-        int index = getPinnedHeaderCount() - 1;
-        if (mIndexer == null || getCount() == 0) {
-            listView.setHeaderInvisible(index, false);
-        } else {
-            int listPosition = listView.getPositionAt(listView.getTotalTopPinnedHeaderHeight());
-            int position = listPosition - listView.getHeaderViewsCount();
-
-            int section = -1;
-            int partition = getPartitionForPosition(position);
-            if (partition == mIndexedPartition) {
-                int offset = getOffsetInPartition(position);
-                if (offset != -1) {
-                    section = getSectionForPosition(offset);
-                }
-            }
-
-            if (section == -1) {
-                listView.setHeaderInvisible(index, false);
-            } else {
-                setPinnedSectionTitle(mHeader, (String)mIndexer.getSections()[section]);
-                if (section == 0) {
-                    setPinnedHeaderContactsCount(mHeader);
-                } else {
-                    clearPinnedHeaderContactsCount(mHeader);
-                }
-                // Compute the item position where the current partition begins
-                int partitionStart = getPositionForPartition(mIndexedPartition);
-                if (hasHeader(mIndexedPartition)) {
-                    partitionStart++;
-                }
-
-                // Compute the item position where the next section begins
-                int nextSectionPosition = partitionStart + getPositionForSection(section + 1);
-                boolean isLastInSection = position == nextSectionPosition - 1;
-                listView.setFadingHeader(index, listPosition, isLastInSection);
-            }
-        }
-    }
-
-    /**
-     * Computes the item's placement within its section and populates the {@code placement}
-     * object accordingly.  Please note that the returned object is volatile and should be
-     * copied if the result needs to be used later.
-     */
-    public Placement getItemPlacementInSection(int position) {
-        if (mPlacementCache.position == position) {
-            return mPlacementCache;
-        }
-
-        mPlacementCache.position = position;
-        if (isSectionHeaderDisplayEnabled()) {
-            int section = getSectionForPosition(position);
-            if (section != -1 && getPositionForSection(section) == position) {
-                mPlacementCache.firstInSection = true;
-                mPlacementCache.sectionHeader = (String)getSections()[section];
-            } else {
-                mPlacementCache.firstInSection = false;
-                mPlacementCache.sectionHeader = null;
-            }
-
-            mPlacementCache.lastInSection = (getPositionForSection(section + 1) - 1 == position);
-        } else {
-            mPlacementCache.firstInSection = false;
-            mPlacementCache.lastInSection = false;
-            mPlacementCache.sectionHeader = null;
-        }
-        return mPlacementCache;
-    }
-}
diff --git a/src/com/android/contacts/widget/PinnedHeaderListAdapter.java b/src/com/android/contacts/widget/PinnedHeaderListAdapter.java
deleted file mode 100644
index c01e331..0000000
--- a/src/com/android/contacts/widget/PinnedHeaderListAdapter.java
+++ /dev/null
@@ -1,171 +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.widget;
-
-import android.content.Context;
-import android.view.View;
-import android.view.ViewGroup;
-
-import com.android.common.widget.CompositeCursorAdapter;
-
-/**
- * A subclass of {@link CompositeCursorAdapter} that manages pinned partition headers.
- */
-public abstract class PinnedHeaderListAdapter extends CompositeCursorAdapter
-        implements PinnedHeaderListView.PinnedHeaderAdapter {
-
-    public static final int PARTITION_HEADER_TYPE = 0;
-
-    private boolean mPinnedPartitionHeadersEnabled;
-    private boolean mHeaderVisibility[];
-
-    public PinnedHeaderListAdapter(Context context) {
-        super(context);
-    }
-
-    public PinnedHeaderListAdapter(Context context, int initialCapacity) {
-        super(context, initialCapacity);
-    }
-
-    public boolean getPinnedPartitionHeadersEnabled() {
-        return mPinnedPartitionHeadersEnabled;
-    }
-
-    public void setPinnedPartitionHeadersEnabled(boolean flag) {
-        this.mPinnedPartitionHeadersEnabled = flag;
-    }
-
-    @Override
-    public int getPinnedHeaderCount() {
-        if (mPinnedPartitionHeadersEnabled) {
-            return getPartitionCount();
-        } else {
-            return 0;
-        }
-    }
-
-    protected boolean isPinnedPartitionHeaderVisible(int partition) {
-        return mPinnedPartitionHeadersEnabled && hasHeader(partition)
-                && !isPartitionEmpty(partition);
-    }
-
-    /**
-     * The default implementation creates the same type of view as a normal
-     * partition header.
-     */
-    @Override
-    public View getPinnedHeaderView(int partition, View convertView, ViewGroup parent) {
-        if (hasHeader(partition)) {
-            View view = null;
-            if (convertView != null) {
-                Integer headerType = (Integer)convertView.getTag();
-                if (headerType != null && headerType == PARTITION_HEADER_TYPE) {
-                    view = convertView;
-                }
-            }
-            if (view == null) {
-                view = newHeaderView(getContext(), partition, null, parent);
-                view.setTag(PARTITION_HEADER_TYPE);
-                view.setFocusable(false);
-                view.setEnabled(false);
-            }
-            bindHeaderView(view, partition, getCursor(partition));
-            return view;
-        } else {
-            return null;
-        }
-    }
-
-    @Override
-    public void configurePinnedHeaders(PinnedHeaderListView listView) {
-        if (!mPinnedPartitionHeadersEnabled) {
-            return;
-        }
-
-        int size = getPartitionCount();
-
-        // Cache visibility bits, because we will need them several times later on
-        if (mHeaderVisibility == null || mHeaderVisibility.length != size) {
-            mHeaderVisibility = new boolean[size];
-        }
-        for (int i = 0; i < size; i++) {
-            boolean visible = isPinnedPartitionHeaderVisible(i);
-            mHeaderVisibility[i] = visible;
-            if (!visible) {
-                listView.setHeaderInvisible(i, true);
-            }
-        }
-
-        int headerViewsCount = listView.getHeaderViewsCount();
-
-        // Starting at the top, find and pin headers for partitions preceding the visible one(s)
-        int maxTopHeader = -1;
-        int topHeaderHeight = 0;
-        for (int i = 0; i < size; i++) {
-            if (mHeaderVisibility[i]) {
-                int position = listView.getPositionAt(topHeaderHeight) - headerViewsCount;
-                int partition = getPartitionForPosition(position);
-                if (i > partition) {
-                    break;
-                }
-
-                listView.setHeaderPinnedAtTop(i, topHeaderHeight, false);
-                topHeaderHeight += listView.getPinnedHeaderHeight(i);
-                maxTopHeader = i;
-            }
-        }
-
-        // Starting at the bottom, find and pin headers for partitions following the visible one(s)
-        int maxBottomHeader = size;
-        int bottomHeaderHeight = 0;
-        int listHeight = listView.getHeight();
-        for (int i = size; --i > maxTopHeader;) {
-            if (mHeaderVisibility[i]) {
-                int position = listView.getPositionAt(listHeight - bottomHeaderHeight)
-                        - headerViewsCount;
-                if (position < 0) {
-                    break;
-                }
-
-                int partition = getPartitionForPosition(position - 1);
-                if (partition == -1 || i <= partition) {
-                    break;
-                }
-
-                int height = listView.getPinnedHeaderHeight(i);
-                bottomHeaderHeight += height;
-                // Animate the header only if the partition is completely invisible below
-                // the bottom of the view
-                int firstPositionForPartition = getPositionForPartition(i);
-                boolean animate = position < firstPositionForPartition;
-                listView.setHeaderPinnedAtBottom(i, listHeight - bottomHeaderHeight, animate);
-                maxBottomHeader = i;
-            }
-        }
-
-        // Headers in between the top-pinned and bottom-pinned should be hidden
-        for (int i = maxTopHeader + 1; i < maxBottomHeader; i++) {
-            if (mHeaderVisibility[i]) {
-                listView.setHeaderInvisible(i, isPartitionEmpty(i));
-            }
-        }
-    }
-
-    @Override
-    public int getScrollPositionForHeader(int viewIndex) {
-        return getPositionForPartition(viewIndex);
-    }
-}
diff --git a/src/com/android/contacts/widget/PinnedHeaderListDemoActivity.java b/src/com/android/contacts/widget/PinnedHeaderListDemoActivity.java
index 4276791..027cc9d 100644
--- a/src/com/android/contacts/widget/PinnedHeaderListDemoActivity.java
+++ b/src/com/android/contacts/widget/PinnedHeaderListDemoActivity.java
@@ -28,9 +28,10 @@
 import android.widget.TextView;
 
 import com.android.contacts.R;
+import com.android.contacts.common.list.PinnedHeaderListAdapter;
 
 /**
- * An activity that demonstrates various use cases for the {@link PinnedHeaderListView}.
+ * An activity that demonstrates various use cases for the {@link com.android.contacts.common.list.PinnedHeaderListView}.
  * If we decide to move PinnedHeaderListView to the framework, this class could go
  * to API demos.
  */
diff --git a/src/com/android/contacts/widget/PinnedHeaderListView.java b/src/com/android/contacts/widget/PinnedHeaderListView.java
deleted file mode 100644
index 1503879..0000000
--- a/src/com/android/contacts/widget/PinnedHeaderListView.java
+++ /dev/null
@@ -1,522 +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.widget;
-
-import android.content.Context;
-import android.graphics.Canvas;
-import android.graphics.Rect;
-import android.graphics.RectF;
-import android.util.AttributeSet;
-import android.view.MotionEvent;
-import android.view.View;
-import android.view.ViewGroup;
-import android.widget.AbsListView;
-import android.widget.AbsListView.OnScrollListener;
-import android.widget.AdapterView;
-import android.widget.AdapterView.OnItemSelectedListener;
-import android.widget.ListAdapter;
-
-/**
- * A ListView that maintains a header pinned at the top of the list. The
- * pinned header can be pushed up and dissolved as needed.
- */
-public class PinnedHeaderListView extends AutoScrollListView
-        implements OnScrollListener, OnItemSelectedListener {
-
-    /**
-     * Adapter interface.  The list adapter must implement this interface.
-     */
-    public interface PinnedHeaderAdapter {
-
-        /**
-         * Returns the overall number of pinned headers, visible or not.
-         */
-        int getPinnedHeaderCount();
-
-        /**
-         * Creates or updates the pinned header view.
-         */
-        View getPinnedHeaderView(int viewIndex, View convertView, ViewGroup parent);
-
-        /**
-         * Configures the pinned headers to match the visible list items. The
-         * adapter should call {@link PinnedHeaderListView#setHeaderPinnedAtTop},
-         * {@link PinnedHeaderListView#setHeaderPinnedAtBottom},
-         * {@link PinnedHeaderListView#setFadingHeader} or
-         * {@link PinnedHeaderListView#setHeaderInvisible}, for each header that
-         * needs to change its position or visibility.
-         */
-        void configurePinnedHeaders(PinnedHeaderListView listView);
-
-        /**
-         * Returns the list position to scroll to if the pinned header is touched.
-         * Return -1 if the list does not need to be scrolled.
-         */
-        int getScrollPositionForHeader(int viewIndex);
-    }
-
-    private static final int MAX_ALPHA = 255;
-    private static final int TOP = 0;
-    private static final int BOTTOM = 1;
-    private static final int FADING = 2;
-
-    private static final int DEFAULT_ANIMATION_DURATION = 20;
-
-    private static final class PinnedHeader {
-        View view;
-        boolean visible;
-        int y;
-        int height;
-        int alpha;
-        int state;
-
-        boolean animating;
-        boolean targetVisible;
-        int sourceY;
-        int targetY;
-        long targetTime;
-    }
-
-    private PinnedHeaderAdapter mAdapter;
-    private int mSize;
-    private PinnedHeader[] mHeaders;
-    private RectF mBounds = new RectF();
-    private Rect mClipRect = new Rect();
-    private OnScrollListener mOnScrollListener;
-    private OnItemSelectedListener mOnItemSelectedListener;
-    private int mScrollState;
-
-    private int mAnimationDuration = DEFAULT_ANIMATION_DURATION;
-    private boolean mAnimating;
-    private long mAnimationTargetTime;
-    private int mHeaderPaddingLeft;
-    private int mHeaderWidth;
-
-    public PinnedHeaderListView(Context context) {
-        this(context, null, com.android.internal.R.attr.listViewStyle);
-    }
-
-    public PinnedHeaderListView(Context context, AttributeSet attrs) {
-        this(context, attrs, com.android.internal.R.attr.listViewStyle);
-    }
-
-    public PinnedHeaderListView(Context context, AttributeSet attrs, int defStyle) {
-        super(context, attrs, defStyle);
-        super.setOnScrollListener(this);
-        super.setOnItemSelectedListener(this);
-    }
-
-    @Override
-    protected void onLayout(boolean changed, int l, int t, int r, int b) {
-        super.onLayout(changed, l, t, r, b);
-        mHeaderPaddingLeft = getPaddingLeft();
-        mHeaderWidth = r - l - mHeaderPaddingLeft - getPaddingRight();
-    }
-
-    public void setPinnedHeaderAnimationDuration(int duration) {
-        mAnimationDuration = duration;
-    }
-
-    @Override
-    public void setAdapter(ListAdapter adapter) {
-        mAdapter = (PinnedHeaderAdapter)adapter;
-        super.setAdapter(adapter);
-    }
-
-    @Override
-    public void setOnScrollListener(OnScrollListener onScrollListener) {
-        mOnScrollListener = onScrollListener;
-        super.setOnScrollListener(this);
-    }
-
-    @Override
-    public void setOnItemSelectedListener(OnItemSelectedListener listener) {
-        mOnItemSelectedListener = listener;
-        super.setOnItemSelectedListener(this);
-    }
-
-    @Override
-    public void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount,
-            int totalItemCount) {
-        if (mAdapter != null) {
-            int count = mAdapter.getPinnedHeaderCount();
-            if (count != mSize) {
-                mSize = count;
-                if (mHeaders == null) {
-                    mHeaders = new PinnedHeader[mSize];
-                } else if (mHeaders.length < mSize) {
-                    PinnedHeader[] headers = mHeaders;
-                    mHeaders = new PinnedHeader[mSize];
-                    System.arraycopy(headers, 0, mHeaders, 0, headers.length);
-                }
-            }
-
-            for (int i = 0; i < mSize; i++) {
-                if (mHeaders[i] == null) {
-                    mHeaders[i] = new PinnedHeader();
-                }
-                mHeaders[i].view = mAdapter.getPinnedHeaderView(i, mHeaders[i].view, this);
-            }
-
-            mAnimationTargetTime = System.currentTimeMillis() + mAnimationDuration;
-            mAdapter.configurePinnedHeaders(this);
-            invalidateIfAnimating();
-
-        }
-        if (mOnScrollListener != null) {
-            mOnScrollListener.onScroll(this, firstVisibleItem, visibleItemCount, totalItemCount);
-        }
-    }
-
-    @Override
-    protected float getTopFadingEdgeStrength() {
-        // Disable vertical fading at the top when the pinned header is present
-        return mSize > 0 ? 0 : super.getTopFadingEdgeStrength();
-    }
-
-    @Override
-    public void onScrollStateChanged(AbsListView view, int scrollState) {
-        mScrollState = scrollState;
-        if (mOnScrollListener != null) {
-            mOnScrollListener.onScrollStateChanged(this, scrollState);
-        }
-    }
-
-    /**
-     * Ensures that the selected item is positioned below the top-pinned headers
-     * and above the bottom-pinned ones.
-     */
-    @Override
-    public void onItemSelected(AdapterView<?> parent, View view, int position, long id) {
-        int height = getHeight();
-
-        int windowTop = 0;
-        int windowBottom = height;
-
-        for (int i = 0; i < mSize; i++) {
-            PinnedHeader header = mHeaders[i];
-            if (header.visible) {
-                if (header.state == TOP) {
-                    windowTop = header.y + header.height;
-                } else if (header.state == BOTTOM) {
-                    windowBottom = header.y;
-                    break;
-                }
-            }
-        }
-
-        View selectedView = getSelectedView();
-        if (selectedView != null) {
-            if (selectedView.getTop() < windowTop) {
-                setSelectionFromTop(position, windowTop);
-            } else if (selectedView.getBottom() > windowBottom) {
-                setSelectionFromTop(position, windowBottom - selectedView.getHeight());
-            }
-        }
-
-        if (mOnItemSelectedListener != null) {
-            mOnItemSelectedListener.onItemSelected(parent, view, position, id);
-        }
-    }
-
-    @Override
-    public void onNothingSelected(AdapterView<?> parent) {
-        if (mOnItemSelectedListener != null) {
-            mOnItemSelectedListener.onNothingSelected(parent);
-        }
-    }
-
-    public int getPinnedHeaderHeight(int viewIndex) {
-        ensurePinnedHeaderLayout(viewIndex);
-        return mHeaders[viewIndex].view.getHeight();
-    }
-
-    /**
-     * Set header to be pinned at the top.
-     *
-     * @param viewIndex index of the header view
-     * @param y is position of the header in pixels.
-     * @param animate true if the transition to the new coordinate should be animated
-     */
-    public void setHeaderPinnedAtTop(int viewIndex, int y, boolean animate) {
-        ensurePinnedHeaderLayout(viewIndex);
-        PinnedHeader header = mHeaders[viewIndex];
-        header.visible = true;
-        header.y = y;
-        header.state = TOP;
-
-        // TODO perhaps we should animate at the top as well
-        header.animating = false;
-    }
-
-    /**
-     * Set header to be pinned at the bottom.
-     *
-     * @param viewIndex index of the header view
-     * @param y is position of the header in pixels.
-     * @param animate true if the transition to the new coordinate should be animated
-     */
-    public void setHeaderPinnedAtBottom(int viewIndex, int y, boolean animate) {
-        ensurePinnedHeaderLayout(viewIndex);
-        PinnedHeader header = mHeaders[viewIndex];
-        header.state = BOTTOM;
-        if (header.animating) {
-            header.targetTime = mAnimationTargetTime;
-            header.sourceY = header.y;
-            header.targetY = y;
-        } else if (animate && (header.y != y || !header.visible)) {
-            if (header.visible) {
-                header.sourceY = header.y;
-            } else {
-                header.visible = true;
-                header.sourceY = y + header.height;
-            }
-            header.animating = true;
-            header.targetVisible = true;
-            header.targetTime = mAnimationTargetTime;
-            header.targetY = y;
-        } else {
-            header.visible = true;
-            header.y = y;
-        }
-    }
-
-    /**
-     * Set header to be pinned at the top of the first visible item.
-     *
-     * @param viewIndex index of the header view
-     * @param position is position of the header in pixels.
-     */
-    public void setFadingHeader(int viewIndex, int position, boolean fade) {
-        ensurePinnedHeaderLayout(viewIndex);
-
-        View child = getChildAt(position - getFirstVisiblePosition());
-        if (child == null) return;
-
-        PinnedHeader header = mHeaders[viewIndex];
-        header.visible = true;
-        header.state = FADING;
-        header.alpha = MAX_ALPHA;
-        header.animating = false;
-
-        int top = getTotalTopPinnedHeaderHeight();
-        header.y = top;
-        if (fade) {
-            int bottom = child.getBottom() - top;
-            int headerHeight = header.height;
-            if (bottom < headerHeight) {
-                int portion = bottom - headerHeight;
-                header.alpha = MAX_ALPHA * (headerHeight + portion) / headerHeight;
-                header.y = top + portion;
-            }
-        }
-    }
-
-    /**
-     * Makes header invisible.
-     *
-     * @param viewIndex index of the header view
-     * @param animate true if the transition to the new coordinate should be animated
-     */
-    public void setHeaderInvisible(int viewIndex, boolean animate) {
-        PinnedHeader header = mHeaders[viewIndex];
-        if (header.visible && (animate || header.animating) && header.state == BOTTOM) {
-            header.sourceY = header.y;
-            if (!header.animating) {
-                header.visible = true;
-                header.targetY = getBottom() + header.height;
-            }
-            header.animating = true;
-            header.targetTime = mAnimationTargetTime;
-            header.targetVisible = false;
-        } else {
-            header.visible = false;
-        }
-    }
-
-    private void ensurePinnedHeaderLayout(int viewIndex) {
-        View view = mHeaders[viewIndex].view;
-        if (view.isLayoutRequested()) {
-            int widthSpec = MeasureSpec.makeMeasureSpec(mHeaderWidth, MeasureSpec.EXACTLY);
-            int heightSpec;
-            ViewGroup.LayoutParams layoutParams = view.getLayoutParams();
-            if (layoutParams != null && layoutParams.height > 0) {
-                heightSpec = MeasureSpec.makeMeasureSpec(layoutParams.height, MeasureSpec.EXACTLY);
-            } else {
-                heightSpec = MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED);
-            }
-            view.measure(widthSpec, heightSpec);
-            int height = view.getMeasuredHeight();
-            mHeaders[viewIndex].height = height;
-            view.layout(0, 0, mHeaderWidth, height);
-        }
-    }
-
-    /**
-     * Returns the sum of heights of headers pinned to the top.
-     */
-    public int getTotalTopPinnedHeaderHeight() {
-        for (int i = mSize; --i >= 0;) {
-            PinnedHeader header = mHeaders[i];
-            if (header.visible && header.state == TOP) {
-                return header.y + header.height;
-            }
-        }
-        return 0;
-    }
-
-    /**
-     * Returns the list item position at the specified y coordinate.
-     */
-    public int getPositionAt(int y) {
-        do {
-            int position = pointToPosition(getPaddingLeft() + 1, y);
-            if (position != -1) {
-                return position;
-            }
-            // If position == -1, we must have hit a separator. Let's examine
-            // a nearby pixel
-            y--;
-        } while (y > 0);
-        return 0;
-    }
-
-    @Override
-    public boolean onInterceptTouchEvent(MotionEvent ev) {
-        if (mScrollState == SCROLL_STATE_IDLE) {
-            final int y = (int)ev.getY();
-            for (int i = mSize; --i >= 0;) {
-                PinnedHeader header = mHeaders[i];
-                if (header.visible && header.y <= y && header.y + header.height > y) {
-                    if (ev.getAction() == MotionEvent.ACTION_DOWN) {
-                        return smoothScrollToPartition(i);
-                    } else {
-                        return true;
-                    }
-                }
-            }
-        }
-
-        return super.onInterceptTouchEvent(ev);
-    }
-
-    private boolean smoothScrollToPartition(int partition) {
-        final int position = mAdapter.getScrollPositionForHeader(partition);
-        if (position == -1) {
-            return false;
-        }
-
-        int offset = 0;
-        for (int i = 0; i < partition; i++) {
-            PinnedHeader header = mHeaders[i];
-            if (header.visible) {
-                offset += header.height;
-            }
-        }
-
-        smoothScrollToPositionFromTop(position + getHeaderViewsCount(), offset);
-        return true;
-    }
-
-    private void invalidateIfAnimating() {
-        mAnimating = false;
-        for (int i = 0; i < mSize; i++) {
-            if (mHeaders[i].animating) {
-                mAnimating = true;
-                invalidate();
-                return;
-            }
-        }
-    }
-
-    @Override
-    protected void dispatchDraw(Canvas canvas) {
-        long currentTime = mAnimating ? System.currentTimeMillis() : 0;
-
-        int top = 0;
-        int bottom = getBottom();
-        boolean hasVisibleHeaders = false;
-        for (int i = 0; i < mSize; i++) {
-            PinnedHeader header = mHeaders[i];
-            if (header.visible) {
-                hasVisibleHeaders = true;
-                if (header.state == BOTTOM && header.y < bottom) {
-                    bottom = header.y;
-                } else if (header.state == TOP || header.state == FADING) {
-                    int newTop = header.y + header.height;
-                    if (newTop > top) {
-                        top = newTop;
-                    }
-                }
-            }
-        }
-
-        if (hasVisibleHeaders) {
-            canvas.save();
-            mClipRect.set(0, top, getWidth(), bottom);
-            canvas.clipRect(mClipRect);
-        }
-
-        super.dispatchDraw(canvas);
-
-        if (hasVisibleHeaders) {
-            canvas.restore();
-
-            // First draw top headers, then the bottom ones to handle the Z axis correctly
-            for (int i = mSize; --i >= 0;) {
-                PinnedHeader header = mHeaders[i];
-                if (header.visible && (header.state == TOP || header.state == FADING)) {
-                    drawHeader(canvas, header, currentTime);
-                }
-            }
-
-            for (int i = 0; i < mSize; i++) {
-                PinnedHeader header = mHeaders[i];
-                if (header.visible && header.state == BOTTOM) {
-                    drawHeader(canvas, header, currentTime);
-                }
-            }
-        }
-
-        invalidateIfAnimating();
-    }
-
-    private void drawHeader(Canvas canvas, PinnedHeader header, long currentTime) {
-        if (header.animating) {
-            int timeLeft = (int)(header.targetTime - currentTime);
-            if (timeLeft <= 0) {
-                header.y = header.targetY;
-                header.visible = header.targetVisible;
-                header.animating = false;
-            } else {
-                header.y = header.targetY + (header.sourceY - header.targetY) * timeLeft
-                        / mAnimationDuration;
-            }
-        }
-        if (header.visible) {
-            View view = header.view;
-            int saveCount = canvas.save();
-            canvas.translate(mHeaderPaddingLeft, header.y);
-            if (header.state == FADING) {
-                mBounds.set(0, 0, mHeaderWidth, view.getHeight());
-                canvas.saveLayerAlpha(mBounds, header.alpha, Canvas.ALL_SAVE_FLAG);
-            }
-            view.draw(canvas);
-            canvas.restoreToCount(saveCount);
-        }
-    }
-}