Merge "Allow overwriting "All Contacts" by a custom text (e.g. "All Friends") Bug:2116002"
diff --git a/src/com/android/contacts/list/ContactBrowseListContextMenuAdapter.java b/src/com/android/contacts/list/ContactBrowseListContextMenuAdapter.java
index 6ff3110..c9d4635 100644
--- a/src/com/android/contacts/list/ContactBrowseListContextMenuAdapter.java
+++ b/src/com/android/contacts/list/ContactBrowseListContextMenuAdapter.java
@@ -55,7 +55,7 @@
             return;
         }
 
-        ContactEntryListAdapter adapter = mContactListFragment.getAdapter();
+        ContactListAdapter adapter = mContactListFragment.getAdapter();
         adapter.moveToPosition(info.position);
 
         // Setup the menu header
@@ -92,10 +92,10 @@
             return false;
         }
 
-        ContactEntryListAdapter adapter = mContactListFragment.getAdapter();
+        ContactListAdapter adapter = mContactListFragment.getAdapter();
         adapter.moveToPosition(info.position);
-        final Uri contactUri = adapter.getContactUri();
 
+        final Uri contactUri = adapter.getContactUri();
         switch (item.getItemId()) {
             case MENU_ITEM_VIEW_CONTACT: {
                 mContactListFragment.viewContact(contactUri);
diff --git a/src/com/android/contacts/list/ContactBrowseListFragment.java b/src/com/android/contacts/list/ContactBrowseListFragment.java
index 97c7a1c..5ae425f 100644
--- a/src/com/android/contacts/list/ContactBrowseListFragment.java
+++ b/src/com/android/contacts/list/ContactBrowseListFragment.java
@@ -15,7 +15,6 @@
  */
 package com.android.contacts.list;
 
-import com.android.contacts.ContactsListActivity;
 import com.android.contacts.R;
 
 import android.app.patterns.CursorLoader;
@@ -31,7 +30,7 @@
  * Fragment containing a contact list used for browsing (as compared to
  * picking a contact with one of the PICK intents).
  */
-public class ContactBrowseListFragment extends ContactEntryListFragment {
+public class ContactBrowseListFragment extends ContactEntryListFragment<ContactListAdapter> {
 
     private OnContactBrowserActionListener mListener;
     private boolean mEditMode;
@@ -55,10 +54,6 @@
         mListener = listener;
     }
 
-    public boolean isSearchAllContactsItemPosition(int position) {
-        return isSearchMode() && position == getAdapter().getCount() - 1;
-    }
-
     @Override
     protected void onInitializeLoaders() {
         startLoading(0, null);
@@ -66,40 +61,43 @@
 
     @Override
     protected void onLoadFinished(Loader<Cursor> loader, Cursor data) {
+        super.onLoadFinished(loader, data);
         getAdapter().changeCursor(data);
     }
 
     @Override
     protected void onItemClick(int position, long id) {
-        if (isSearchAllContactsItemPosition(position)) {
+        ContactListAdapter adapter = getAdapter();
+        if (adapter.isSearchAllContactsItemPosition(position)) {
             mListener.onSearchAllContactsAction((String)null);
         } else if (isEditMode()) {
             if (position == 0 && !isSearchMode() && isCreateContactEnabled()) {
                 mListener.onCreateNewContactAction();
             } else {
-                ContactEntryListAdapter adapter = getAdapter();
                 adapter.moveToPosition(position);
                 mListener.onEditContactAction(adapter.getContactUri());
             }
         } else {
-            ContactEntryListAdapter adapter = getAdapter();
             adapter.moveToPosition(position);
             mListener.onViewContactAction(adapter.getContactUri());
         }
     }
 
     @Override
-    protected ContactEntryListAdapter createListAdapter() {
-        ContactItemListAdapter adapter =
-                new ContactItemListAdapter((ContactsListActivity)getActivity());
+    protected ContactListAdapter createListAdapter() {
+        ContactListAdapter adapter = new ContactListAdapter(getActivity());
         adapter.setSectionHeaderDisplayEnabled(isSectionHeaderDisplayEnabled());
-        adapter.setDisplayPhotos(isPhotoLoaderEnabled());
+
         adapter.setSearchMode(isSearchMode());
         adapter.setSearchResultsMode(isSearchResultsMode());
         adapter.setQueryString(getQueryString());
+
         adapter.setContactNameDisplayOrder(getContactNameDisplayOrder());
         adapter.setSortOrder(getSortOrder());
 
+        adapter.setDisplayPhotos(true);
+        adapter.setQuickContactEnabled(true);
+
         adapter.configureLoader(mLoader);
         return adapter;
     }
diff --git a/src/com/android/contacts/list/ContactEntryListAdapter.java b/src/com/android/contacts/list/ContactEntryListAdapter.java
index dbd6561..9da3704 100644
--- a/src/com/android/contacts/list/ContactEntryListAdapter.java
+++ b/src/com/android/contacts/list/ContactEntryListAdapter.java
@@ -16,20 +16,18 @@
 package com.android.contacts.list;
 
 import com.android.contacts.ContactPhotoLoader;
-import com.android.contacts.widget.TextWithHighlighting;
+import com.android.contacts.ContactsSectionIndexer;
+import com.android.contacts.R;
 import com.android.contacts.widget.TextWithHighlightingFactory;
 
 import android.app.patterns.CursorLoader;
 import android.content.Context;
 import android.database.Cursor;
-import android.database.DatabaseUtils;
-import android.net.Uri;
-import android.provider.ContactsContract;
+import android.os.Bundle;
 import android.provider.ContactsContract.ContactCounts;
-import android.provider.ContactsContract.Contacts;
-import android.provider.ContactsContract.SearchSnippetColumns;
-import android.text.TextUtils;
-import android.widget.ListView;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
 
 /**
  * Common base class for various contact-related lists, e.g. contact list, phone number list
@@ -37,47 +35,6 @@
  */
 public abstract class ContactEntryListAdapter extends PinnedHeaderListAdapter {
 
-    // TODO move to type-specific adapter
-    static final String[] CONTACTS_SUMMARY_PROJECTION = new String[] {
-        Contacts._ID,                       // 0
-        Contacts.DISPLAY_NAME_PRIMARY,      // 1
-        Contacts.DISPLAY_NAME_ALTERNATIVE,  // 2
-        Contacts.SORT_KEY_PRIMARY,          // 3
-        Contacts.STARRED,                   // 4
-        Contacts.TIMES_CONTACTED,           // 5
-        Contacts.CONTACT_PRESENCE,          // 6
-        Contacts.PHOTO_ID,                  // 7
-        Contacts.LOOKUP_KEY,                // 8
-        Contacts.PHONETIC_NAME,             // 9
-        Contacts.HAS_PHONE_NUMBER,          // 10
-    };
-
-    // TODO move to type-specific adapter
-    static final String[] CONTACTS_SUMMARY_FILTER_PROJECTION = new String[] {
-        Contacts._ID,                       // 0
-        Contacts.DISPLAY_NAME_PRIMARY,      // 1
-        Contacts.DISPLAY_NAME_ALTERNATIVE,  // 2
-        Contacts.SORT_KEY_PRIMARY,          // 3
-        Contacts.STARRED,                   // 4
-        Contacts.TIMES_CONTACTED,           // 5
-        Contacts.CONTACT_PRESENCE,          // 6
-        Contacts.PHOTO_ID,                  // 7
-        Contacts.LOOKUP_KEY,                // 8
-        Contacts.PHONETIC_NAME,             // 9
-        Contacts.HAS_PHONE_NUMBER,          // 10
-        SearchSnippetColumns.SNIPPET_MIMETYPE, // 11
-        SearchSnippetColumns.SNIPPET_DATA1,     // 12
-        SearchSnippetColumns.SNIPPET_DATA4,     // 13
-    };
-
-    // TODO move to a type-specific adapter
-    public static final int SUMMARY_ID_COLUMN_INDEX = 0;
-    public static final int SUMMARY_DISPLAY_NAME_PRIMARY_COLUMN_INDEX = 1;
-    public static final int SUMMARY_DISPLAY_NAME_ALTERNATIVE_COLUMN_INDEX = 2;
-    public static final int SUMMARY_STARRED_COLUMN_INDEX = 4;
-    public static final int SUMMARY_LOOKUP_KEY_COLUMN_INDEX = 8;
-    public static final int SUMMARY_HAS_PHONE_COLUMN_INDEX = 10;
-
     /**
      * The animation is used here to allocate animated name text views.
      */
@@ -86,9 +43,11 @@
     private int mDisplayOrder;
     private int mSortOrder;
     private boolean mNameHighlightingEnabled;
-    private ContactPhotoLoader mPhotoLoader;
     private boolean mSectionHeaderDisplayEnabled;
 
+    private boolean mDisplayPhotos;
+    private ContactPhotoLoader mPhotoLoader;
+
     private String mQueryString;
     private boolean mSearchMode;
     private boolean mSearchResultsMode;
@@ -97,6 +56,13 @@
         super(context);
     }
 
+    public Context getContext() {
+        return mContext;
+    }
+
+    public abstract String getContactDisplayName();
+    public abstract void configureLoader(CursorLoader loader);
+
     public boolean isSearchMode() {
         return mSearchMode;
     }
@@ -121,10 +87,6 @@
         mQueryString = queryString;
     }
 
-    public Context getContext() {
-        return mContext;
-    }
-
     public boolean isSectionHeaderDisplayEnabled() {
         return mSectionHeaderDisplayEnabled;
     }
@@ -149,6 +111,7 @@
         mSortOrder = sortOrder;
     }
 
+    // TODO no highlighting in STREQUENT mode
     public void setNameHighlightingEnabled(boolean flag) {
         mNameHighlightingEnabled = flag;
     }
@@ -161,8 +124,8 @@
         mTextWithHighlightingFactory = factory;
     }
 
-    protected TextWithHighlighting createTextWithHighlighting() {
-        return mTextWithHighlightingFactory.createTextWithHighlighting();
+    protected TextWithHighlightingFactory getTextWithHighlightingFactory() {
+        return mTextWithHighlightingFactory;
     }
 
     public void setPhotoLoader(ContactPhotoLoader photoLoader) {
@@ -173,37 +136,12 @@
         return mPhotoLoader;
     }
 
-    public void configureLoader(CursorLoader loader) {
-        Uri uri;
-        if (isSearchMode() || isSearchResultsMode()) {
-            String query = getQueryString();
-            uri = Uri.withAppendedPath(Contacts.CONTENT_FILTER_URI,
-                    TextUtils.isEmpty(query) ? "" : Uri.encode(query));
-            loader.setProjection(CONTACTS_SUMMARY_FILTER_PROJECTION);
-            if (!isSearchResultsMode()) {
-                loader.setSelection(Contacts.IN_VISIBLE_GROUP + "=1");
-            }
-        } else {
-            uri = Contacts.CONTENT_URI;
-            loader.setProjection(CONTACTS_SUMMARY_PROJECTION);
-            loader.setSelection(Contacts.IN_VISIBLE_GROUP + "=1");
-        }
-
-        if (isSectionHeaderDisplayEnabled()) {
-            uri = buildSectionIndexerUri(uri);
-        }
-
-        loader.setUri(uri);
-        if (mSortOrder == ContactsContract.Preferences.SORT_ORDER_PRIMARY) {
-            loader.setSortOrder(Contacts.SORT_KEY_PRIMARY);
-        } else {
-            loader.setSortOrder(Contacts.SORT_KEY_ALTERNATIVE);
-        }
+    public boolean getDisplayPhotos() {
+        return mDisplayPhotos;
     }
 
-    private static Uri buildSectionIndexerUri(Uri uri) {
-        return uri.buildUpon()
-                .appendQueryParameter(ContactCounts.ADDRESS_BOOK_INDEX_EXTRAS, "true").build();
+    public void setDisplayPhotos(boolean displayPhotos) {
+        mDisplayPhotos = displayPhotos;
     }
 
     /*
@@ -215,39 +153,75 @@
         super.onContentChanged();
     }
 
+    @Override
+    public void changeCursor(Cursor cursor) {
+        super.changeCursor(cursor);
+
+        if (isSectionHeaderDisplayEnabled()) {
+            updateIndexer(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);
+        }
+    }
+
     public void moveToPosition(int position) {
         // For side-effect
         getItem(position);
     }
 
-    public boolean getHasPhoneNumber() {
-        return getCursor().getInt(SUMMARY_HAS_PHONE_COLUMN_INDEX) != 0;
-    }
-
-    public boolean isContactStarred() {
-        return getCursor().getInt(SUMMARY_STARRED_COLUMN_INDEX) != 0;
-    }
-
-    public String getContactDisplayName() {
-        return getCursor().getString(getSummaryDisplayNameColumnIndex());
-    }
-
-    public int getSummaryDisplayNameColumnIndex() {
-        if (mDisplayOrder == ContactsContract.Preferences.DISPLAY_ORDER_PRIMARY) {
-            return SUMMARY_DISPLAY_NAME_PRIMARY_COLUMN_INDEX;
-        } else {
-            return SUMMARY_DISPLAY_NAME_ALTERNATIVE_COLUMN_INDEX;
+    @Override
+    public int getCount() {
+        if (!mDataValid) {
+            return 0;
         }
+
+        int count = super.getCount();
+
+        if (mSearchMode) {
+            // Last element in the list is "Search all contacts"
+            count++;
+        }
+
+        return count;
     }
 
-    /**
-     * Builds the {@link Contacts#CONTENT_LOOKUP_URI} for the given
-     * {@link ListView} position.
-     */
-    public Uri getContactUri() {
-        Cursor cursor = getCursor();
-        long contactId = cursor.getLong(SUMMARY_ID_COLUMN_INDEX);
-        String lookupKey = cursor.getString(SUMMARY_LOOKUP_KEY_COLUMN_INDEX);
-        return Contacts.getLookupUri(contactId, lookupKey);
+    public boolean isSearchAllContactsItemPosition(int position) {
+        return isSearchMode() && position == getCount() - 1;
+    }
+
+    @Override
+    public int getItemViewType(int position) {
+        if (isSearchAllContactsItemPosition(position)) {
+            return IGNORE_ITEM_VIEW_TYPE;
+        }
+
+        return 0;
+    }
+
+    @Override
+    public View getView(int position, View convertView, ViewGroup parent) {
+        if (isSearchAllContactsItemPosition(position)) {
+            return LayoutInflater.from(getContext()).inflate(
+                    R.layout.contacts_list_search_all_item, parent, false);
+        }
+        return super.getView(position, convertView, parent);
     }
 }
diff --git a/src/com/android/contacts/list/ContactEntryListFragment.java b/src/com/android/contacts/list/ContactEntryListFragment.java
index aaa7f13..fefa718 100644
--- a/src/com/android/contacts/list/ContactEntryListFragment.java
+++ b/src/com/android/contacts/list/ContactEntryListFragment.java
@@ -46,7 +46,6 @@
 import android.view.inputmethod.InputMethodManager;
 import android.widget.AbsListView;
 import android.widget.AdapterView;
-import android.widget.Filter;
 import android.widget.ListView;
 import android.widget.TextView;
 import android.widget.AbsListView.OnScrollListener;
@@ -56,7 +55,8 @@
 /**
  * Common base class for various contact-related list fragments.
  */
-public abstract class ContactEntryListFragment extends LoaderManagingFragment<Cursor>
+public abstract class ContactEntryListFragment<T extends ContactEntryListAdapter>
+        extends LoaderManagingFragment<Cursor>
         implements OnItemClickListener,
         OnScrollListener, TextWatcher, OnEditorActionListener, OnCloseListener,
         OnFocusChangeListener, OnTouchListener {
@@ -70,7 +70,8 @@
     private String mQueryString;
 
     private ContactsApplicationController mAppController;
-    private ContactEntryListAdapter mAdapter;
+    private T mAdapter;
+    private View mView;
     private ListView mListView;
 
     /**
@@ -88,10 +89,10 @@
 
 
     protected abstract View inflateView(LayoutInflater inflater, ViewGroup container);
-    protected abstract ContactEntryListAdapter createListAdapter();
+    protected abstract T createListAdapter();
     protected abstract void onItemClick(int position, long id);
 
-    public ContactEntryListAdapter getAdapter() {
+    public T getAdapter() {
         return mAdapter;
     }
 
@@ -106,10 +107,14 @@
     protected void onInitializeLoaders() {
     }
 
-    // TODO make abstract
     @Override
     protected void onLoadFinished(Loader<Cursor> loader, Cursor data) {
-        throw new UnsupportedOperationException();
+        if (data != null && isSearchResultsMode()) {
+            TextView foundContactsText = (TextView)mView.findViewById(R.id.search_results_found);
+            String text = getQuantityText(data.getCount(),
+                    R.string.listFoundAllContactsZero, R.plurals.listFoundAllContacts);
+            foundContactsText.setText(text);
+        }
     }
 
     protected void reloadData() {
@@ -217,10 +222,10 @@
 
     @Override
     public View onCreateView(LayoutInflater inflater, ViewGroup container) {
-        View view = inflateView(inflater, container);
+        mView = inflateView(inflater, container);
         mAdapter = createListAdapter();
-        configureView(view);
-        return view;
+        configureView(mView);
+        return mView;
     }
 
     protected void configureView(View view) {
@@ -415,4 +420,15 @@
             mListState = null;
         }
     }
+
+    // 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 getActivity().getString(zeroResourceId);
+        } else {
+            String format = getActivity().getResources()
+                    .getQuantityText(pluralResourceId, count).toString();
+            return String.format(format, count);
+        }
+    }
 }
diff --git a/src/com/android/contacts/list/ContactItemListAdapter.java b/src/com/android/contacts/list/ContactItemListAdapter.java
index 35c0fb5..fb7f942 100644
--- a/src/com/android/contacts/list/ContactItemListAdapter.java
+++ b/src/com/android/contacts/list/ContactItemListAdapter.java
@@ -21,6 +21,7 @@
 import com.android.contacts.R;
 import com.android.contacts.widget.TextWithHighlighting;
 
+import android.app.patterns.CursorLoader;
 import android.content.Context;
 import android.database.CharArrayBuffer;
 import android.database.Cursor;
@@ -45,6 +46,7 @@
 import android.widget.QuickContactBadge;
 import android.widget.TextView;
 
+@Deprecated
 public class ContactItemListAdapter extends ContactEntryListAdapter {
 
     private final ContactsListActivity contactsListActivity;
@@ -81,6 +83,7 @@
         }
     }
 
+    @Override
     public void setDisplayPhotos(boolean flag) {
         mDisplayPhotos = flag;
     }
@@ -226,6 +229,7 @@
         return view;
     }
 
+    @Override
     public boolean isSearchAllContactsItemPosition(int position) {
         return contactsListActivity.mSearchMode && contactsListActivity.mMode != ContactsListActivity.MODE_PICK_MULTIPLE_PHONES && position == getCount() - 1;
     }
@@ -309,7 +313,8 @@
         if (size != 0) {
             if (highlightingEnabled) {
                 if (view.textWithHighlighting == null) {
-                    view.textWithHighlighting = createTextWithHighlighting();
+                    view.textWithHighlighting =
+                            getTextWithHighlightingFactory().createTextWithHighlighting();
                 }
                 buildDisplayNameWithHighlighting(nameView, cursor, view.nameBuffer,
                         view.highlightedTextBuffer, view.textWithHighlighting);
@@ -698,4 +703,17 @@
         }
         return super.getItemId(realPosition);
     }
+
+    @Override
+    public void configureLoader(CursorLoader loader) {
+    }
+
+    @Override
+    public String getContactDisplayName() {
+        return "TODO";
+    }
+
+    public boolean isContactStarred() {
+        return false;
+    }
 }
\ No newline at end of file
diff --git a/src/com/android/contacts/list/ContactListAdapter.java b/src/com/android/contacts/list/ContactListAdapter.java
new file mode 100644
index 0000000..db4017c
--- /dev/null
+++ b/src/com/android/contacts/list/ContactListAdapter.java
@@ -0,0 +1,242 @@
+/*
+ * 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.app.patterns.CursorLoader;
+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.SearchSnippetColumns;
+import android.text.TextUtils;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.ImageView;
+import android.widget.ListView;
+import android.widget.QuickContactBadge;
+
+/**
+ * A cursor adapter for the {@link ContactsContract.Contacts#CONTENT_TYPE} content type.
+ */
+public class ContactListAdapter extends ContactEntryListAdapter {
+
+    private static final String[] CONTACTS_SUMMARY_PROJECTION = new String[] {
+        Contacts._ID,                       // 0
+        Contacts.DISPLAY_NAME_PRIMARY,      // 1
+        Contacts.DISPLAY_NAME_ALTERNATIVE,  // 2
+        Contacts.SORT_KEY_PRIMARY,          // 3
+        Contacts.STARRED,                   // 4
+        Contacts.TIMES_CONTACTED,           // 5
+        Contacts.CONTACT_PRESENCE,          // 6
+        Contacts.PHOTO_ID,                  // 7
+        Contacts.LOOKUP_KEY,                // 8
+        Contacts.PHONETIC_NAME,             // 9
+        Contacts.HAS_PHONE_NUMBER,          // 10
+    };
+
+    private static final String[] CONTACTS_SUMMARY_FILTER_PROJECTION = new String[] {
+        Contacts._ID,                       // 0
+        Contacts.DISPLAY_NAME_PRIMARY,      // 1
+        Contacts.DISPLAY_NAME_ALTERNATIVE,  // 2
+        Contacts.SORT_KEY_PRIMARY,          // 3
+        Contacts.STARRED,                   // 4
+        Contacts.TIMES_CONTACTED,           // 5
+        Contacts.CONTACT_PRESENCE,          // 6
+        Contacts.PHOTO_ID,                  // 7
+        Contacts.LOOKUP_KEY,                // 8
+        Contacts.PHONETIC_NAME,             // 9
+        Contacts.HAS_PHONE_NUMBER,          // 10
+        SearchSnippetColumns.SNIPPET_MIMETYPE, // 11
+        SearchSnippetColumns.SNIPPET_DATA1,     // 12
+        SearchSnippetColumns.SNIPPET_DATA4,     // 13
+    };
+
+    private static final int SUMMARY_ID_COLUMN_INDEX = 0;
+    private static final int SUMMARY_DISPLAY_NAME_PRIMARY_COLUMN_INDEX = 1;
+    private static final int SUMMARY_DISPLAY_NAME_ALTERNATIVE_COLUMN_INDEX = 2;
+    private static final int SUMMARY_SORT_KEY_PRIMARY_COLUMN_INDEX = 3;
+    private static final int SUMMARY_STARRED_COLUMN_INDEX = 4;
+    private static final int SUMMARY_TIMES_CONTACTED_COLUMN_INDEX = 5;
+    private static final int SUMMARY_PRESENCE_STATUS_COLUMN_INDEX = 6;
+    private static final int SUMMARY_PHOTO_ID_COLUMN_INDEX = 7;
+    private static final int SUMMARY_LOOKUP_KEY_COLUMN_INDEX = 8;
+    private static final int SUMMARY_PHONETIC_NAME_COLUMN_INDEX = 9;
+    private static final int SUMMARY_HAS_PHONE_COLUMN_INDEX = 10;
+    private static final int SUMMARY_SNIPPET_MIMETYPE_COLUMN_INDEX = 11;
+    private static final int SUMMARY_SNIPPET_DATA1_COLUMN_INDEX = 12;
+    private static final int SUMMARY_SNIPPET_DATA4_COLUMN_INDEX = 13;
+
+    private boolean mQuickContactEnabled;
+    private CharSequence mUnknownNameText;
+    private int mDisplayNameColumnIndex;
+    private int mAlternativeDisplayNameColumnIndex;
+
+    public ContactListAdapter(Context context) {
+        super(context);
+
+        mUnknownNameText = context.getText(android.R.string.unknownName);
+    }
+
+    @Override
+    public void configureLoader(CursorLoader loader) {
+        Uri uri;
+        if (isSearchMode() || isSearchResultsMode()) {
+            String query = getQueryString();
+            uri = Uri.withAppendedPath(Contacts.CONTENT_FILTER_URI,
+                    TextUtils.isEmpty(query) ? "" : Uri.encode(query));
+            loader.setProjection(CONTACTS_SUMMARY_FILTER_PROJECTION);
+            if (!isSearchResultsMode()) {
+                loader.setSelection(Contacts.IN_VISIBLE_GROUP + "=1");
+            }
+        } else {
+            uri = Contacts.CONTENT_URI;
+            loader.setProjection(CONTACTS_SUMMARY_PROJECTION);
+            loader.setSelection(Contacts.IN_VISIBLE_GROUP + "=1");
+        }
+
+        if (isSectionHeaderDisplayEnabled()) {
+            uri = buildSectionIndexerUri(uri);
+        }
+
+        loader.setUri(uri);
+
+        if (getSortOrder() == ContactsContract.Preferences.SORT_ORDER_PRIMARY) {
+            loader.setSortOrder(Contacts.SORT_KEY_PRIMARY);
+        } else {
+            loader.setSortOrder(Contacts.SORT_KEY_ALTERNATIVE);
+        }
+    }
+
+    private static Uri buildSectionIndexerUri(Uri uri) {
+        return uri.buildUpon()
+                .appendQueryParameter(ContactCounts.ADDRESS_BOOK_INDEX_EXTRAS, "true").build();
+    }
+
+    public boolean getHasPhoneNumber() {
+        return getCursor().getInt(SUMMARY_HAS_PHONE_COLUMN_INDEX) != 0;
+    }
+
+    public boolean isContactStarred() {
+        return getCursor().getInt(SUMMARY_STARRED_COLUMN_INDEX) != 0;
+    }
+
+    @Override
+    public String getContactDisplayName() {
+        return getCursor().getString(mDisplayNameColumnIndex);
+    }
+
+    public void setQuickContactEnabled(boolean quickContactEnabled) {
+        mQuickContactEnabled = quickContactEnabled;
+    }
+
+    @Override
+    public void setContactNameDisplayOrder(int displayOrder) {
+        super.setContactNameDisplayOrder(displayOrder);
+        if (getContactNameDisplayOrder() == ContactsContract.Preferences.DISPLAY_ORDER_PRIMARY) {
+            mDisplayNameColumnIndex = SUMMARY_DISPLAY_NAME_PRIMARY_COLUMN_INDEX;
+            mAlternativeDisplayNameColumnIndex = SUMMARY_DISPLAY_NAME_ALTERNATIVE_COLUMN_INDEX;
+        } else {
+            mDisplayNameColumnIndex = SUMMARY_DISPLAY_NAME_ALTERNATIVE_COLUMN_INDEX;
+            mAlternativeDisplayNameColumnIndex = SUMMARY_DISPLAY_NAME_PRIMARY_COLUMN_INDEX;
+        }
+    }
+
+    /**
+     * Builds the {@link Contacts#CONTENT_LOOKUP_URI} for the given
+     * {@link ListView} position.
+     */
+    public Uri getContactUri() {
+        Cursor cursor = getCursor();
+        long contactId = cursor.getLong(SUMMARY_ID_COLUMN_INDEX);
+        String lookupKey = cursor.getString(SUMMARY_LOOKUP_KEY_COLUMN_INDEX);
+        return Contacts.getLookupUri(contactId, lookupKey);
+    }
+
+    @Override
+    public View newView(Context context, Cursor cursor, ViewGroup parent) {
+        final ContactListItemView view = new ContactListItemView(context, null);
+        view.setUnknownNameText(mUnknownNameText);
+        view.setTextWithHighlightingFactory(getTextWithHighlightingFactory());
+        // TODO
+//        view.setOnCallButtonClickListener(contactsListActivity);
+        return view;
+    }
+
+    @Override
+    public void bindView(View itemView, Context context, Cursor cursor) {
+        final ContactListItemView view = (ContactListItemView)itemView;
+
+        if (isSectionHeaderDisplayEnabled()) {
+            final int position = cursor.getPosition();
+            final int section = getSectionForPosition(position);
+            if (getPositionForSection(section) == position) {
+                String title = (String)getSections()[section];
+                view.setSectionHeader(title);
+            } else {
+                view.setDividerVisible(false);
+                view.setSectionHeader(null);
+            }
+
+            // move the divider for the last item in a section
+            if (getPositionForSection(section + 1) - 1 == position) {
+                view.setDividerVisible(false);
+            } else {
+                view.setDividerVisible(true);
+            }
+        }
+
+        view.showDisplayName(cursor, mDisplayNameColumnIndex, isNameHighlightingEnabled(),
+                mAlternativeDisplayNameColumnIndex);
+//
+//        // Make the call button visible if requested.
+//        if (mDisplayCallButton
+//                && cursor.getColumnCount() > ContactsListActivity.SUMMARY_HAS_PHONE_COLUMN_INDEX
+//                && cursor.getInt(ContactsListActivity.SUMMARY_HAS_PHONE_COLUMN_INDEX) != 0) {
+//            int pos = cursor.getPosition();
+//            view.showCallButton(android.R.id.button1, pos);
+//        } else {
+//            view.hideCallButton();
+//        }
+//
+
+        // Set the photo, if available
+        long photoId = 0;
+        if (!cursor.isNull(SUMMARY_PHOTO_ID_COLUMN_INDEX)) {
+            photoId = cursor.getLong(SUMMARY_PHOTO_ID_COLUMN_INDEX);
+        }
+
+        ImageView viewToUse;
+        if (mQuickContactEnabled) {
+            QuickContactBadge quickContact = view.getQuickContact();
+            quickContact.assignContactUri(getContactUri());
+            viewToUse = quickContact;
+        } else {
+            viewToUse = view.getPhotoView();
+        }
+
+        getPhotoLoader().loadPhoto(viewToUse, photoId);
+
+        view.showPresence(cursor, SUMMARY_PRESENCE_STATUS_COLUMN_INDEX);
+        view.showPhoneticName(cursor, SUMMARY_PHONETIC_NAME_COLUMN_INDEX);
+
+        if (isSearchMode() || isSearchResultsMode()) {
+            view.showSnippet(cursor, SUMMARY_SNIPPET_MIMETYPE_COLUMN_INDEX,
+                    SUMMARY_SNIPPET_DATA1_COLUMN_INDEX, SUMMARY_SNIPPET_DATA4_COLUMN_INDEX);
+        }
+    }
+}
diff --git a/src/com/android/contacts/list/ContactListItemView.java b/src/com/android/contacts/list/ContactListItemView.java
index 4db52fa..ef131d1 100644
--- a/src/com/android/contacts/list/ContactListItemView.java
+++ b/src/com/android/contacts/list/ContactListItemView.java
@@ -16,24 +16,29 @@
 
 package com.android.contacts.list;
 
+import com.android.contacts.ContactPresenceIconUtil;
 import com.android.contacts.R;
 import com.android.contacts.widget.TextWithHighlighting;
+import com.android.contacts.widget.TextWithHighlightingFactory;
 
 import android.content.Context;
 import android.content.res.Resources;
 import android.content.res.TypedArray;
 import android.database.CharArrayBuffer;
+import android.database.Cursor;
 import android.graphics.Canvas;
 import android.graphics.Typeface;
 import android.graphics.drawable.Drawable;
 import android.provider.ContactsContract.Contacts;
+import android.provider.ContactsContract.CommonDataKinds.Email;
+import android.provider.ContactsContract.CommonDataKinds.Nickname;
+import android.provider.ContactsContract.CommonDataKinds.Organization;
 import android.text.TextUtils;
 import android.text.TextUtils.TruncateAt;
 import android.util.AttributeSet;
 import android.view.Gravity;
 import android.view.View;
 import android.view.ViewGroup;
-import android.widget.CheckBox;
 import android.widget.ImageView;
 import android.widget.QuickContactBadge;
 import android.widget.TextView;
@@ -61,7 +66,7 @@
     private final int mPresenceIconMargin;
     private final int mHeaderTextWidth;
 
-    private boolean mHorizontalDividerVisible;
+    private boolean mHorizontalDividerVisible = true;
     private Drawable mHorizontalDividerDrawable;
     private int mHorizontalDividerHeight;
 
@@ -90,13 +95,15 @@
     private int mLine3Height;
 
     private OnClickListener mCallButtonClickListener;
-
+    private TextWithHighlightingFactory mTextWithHighlightingFactory;
     public CharArrayBuffer nameBuffer = new CharArrayBuffer(128);
     public CharArrayBuffer dataBuffer = new CharArrayBuffer(128);
     public CharArrayBuffer highlightedTextBuffer = new CharArrayBuffer(128);
     public TextWithHighlighting textWithHighlighting;
     public CharArrayBuffer phoneticNameBuffer = new CharArrayBuffer(128);
 
+    private CharSequence mUnknownNameText;
+
     /**
      * Special class to allow the parent to be pressed without being pressed itself.
      * This way the line of a tab can be pressed, but the image itself is not.
@@ -158,6 +165,14 @@
         mCallButtonClickListener = callButtonClickListener;
     }
 
+    public void setTextWithHighlightingFactory(TextWithHighlightingFactory factory) {
+        mTextWithHighlightingFactory = factory;
+    }
+
+    public void setUnknownNameText(CharSequence unknownNameText) {
+        mUnknownNameText = unknownNameText;
+    }
+
     @Override
     protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
         // We will match parent's width and wrap content vertically, but make sure
@@ -655,4 +670,90 @@
             }
         }
     }
+
+    public void showDisplayName(Cursor cursor, int nameColumnIndex, boolean highlightingEnabled,
+            int alternativeNameColumnIndex) {
+        cursor.copyStringToBuffer(nameColumnIndex, nameBuffer);
+        TextView nameView = getNameTextView();
+        int size = nameBuffer.sizeCopied;
+        if (size != 0) {
+            if (highlightingEnabled) {
+                if (textWithHighlighting == null) {
+                    textWithHighlighting =
+                            mTextWithHighlightingFactory.createTextWithHighlighting();
+                }
+                cursor.copyStringToBuffer(alternativeNameColumnIndex, highlightedTextBuffer);
+                textWithHighlighting.setText(nameBuffer, highlightedTextBuffer);
+                nameView.setText(textWithHighlighting);
+            } else {
+                nameView.setText(nameBuffer.data, 0, size);
+            }
+        } else {
+            nameView.setText(mUnknownNameText);
+        }
+    }
+
+    public void showPhoneticName(Cursor cursor, int phoneticNameColumnIndex) {
+        cursor.copyStringToBuffer(phoneticNameColumnIndex, phoneticNameBuffer);
+        int phoneticNameSize = phoneticNameBuffer.sizeCopied;
+        if (phoneticNameSize != 0) {
+            setLabel(phoneticNameBuffer.data, phoneticNameSize);
+        } else {
+            setLabel(null);
+        }
+    }
+
+    /**
+     * Sets the proper icon (star or presence or nothing)
+     */
+    public void showPresence(Cursor cursor, int presenceColumnIndex) {
+        int serverStatus;
+        if (!cursor.isNull(presenceColumnIndex)) {
+            serverStatus = cursor.getInt(presenceColumnIndex);
+
+            // TODO consider caching these drawables
+            Drawable icon = ContactPresenceIconUtil.getPresenceIcon(getContext(), serverStatus);
+            if (icon != null) {
+                setPresence(icon);
+            } else {
+                setPresence(null);
+            }
+        } else {
+            setPresence(null);
+        }
+    }
+
+    /**
+     * Shows search snippet.
+     */
+    public void showSnippet(Cursor cursor, int summarySnippetMimetypeColumnIndex,
+            int summarySnippetData1ColumnIndex, int summarySnippetData4ColumnIndex) {
+        String snippet = null;
+        String snippetMimeType = cursor.getString(summarySnippetMimetypeColumnIndex);
+        if (Email.CONTENT_ITEM_TYPE.equals(snippetMimeType)) {
+            String email = cursor.getString(summarySnippetData1ColumnIndex);
+            if (!TextUtils.isEmpty(email)) {
+                snippet = email;
+            }
+        } else if (Organization.CONTENT_ITEM_TYPE.equals(snippetMimeType)) {
+            String company = cursor.getString(summarySnippetData1ColumnIndex);
+            String title = cursor.getString(summarySnippetData4ColumnIndex);
+            if (!TextUtils.isEmpty(company)) {
+                if (!TextUtils.isEmpty(title)) {
+                    snippet = company + " / " + title;
+                } else {
+                    snippet = company;
+                }
+            } else if (!TextUtils.isEmpty(title)) {
+                snippet = title;
+            }
+        } else if (Nickname.CONTENT_ITEM_TYPE.equals(snippetMimeType)) {
+            String nickname = cursor.getString(summarySnippetData1ColumnIndex);
+            if (!TextUtils.isEmpty(nickname)) {
+                snippet = nickname;
+            }
+        }
+
+        setSnippet(snippet);
+    }
 }
diff --git a/src/com/android/contacts/list/ContactPickerFragment.java b/src/com/android/contacts/list/ContactPickerFragment.java
index b1768a2..07908dd 100644
--- a/src/com/android/contacts/list/ContactPickerFragment.java
+++ b/src/com/android/contacts/list/ContactPickerFragment.java
@@ -15,11 +15,9 @@
  */
 package com.android.contacts.list;
 
-import com.android.contacts.ContactsListActivity;
 import com.android.contacts.R;
 
 import android.content.Intent;
-import android.net.Uri;
 import android.view.LayoutInflater;
 import android.view.View;
 import android.view.ViewGroup;
@@ -28,7 +26,7 @@
  * Fragment for the contact list used for browsing contacts (as compared to
  * picking a contact with one of the PICK or SHORTCUT intents).
  */
-public class ContactPickerFragment extends ContactEntryListFragment {
+public class ContactPickerFragment extends ContactEntryListFragment<ContactListAdapter> {
 
     private OnContactPickerActionListener mListener;
     private boolean mCreateContactEnabled;
@@ -50,7 +48,7 @@
             if (position == 0 && !isSearchMode() && isCreateContactEnabled()) {
                 mListener.onCreateNewContactAction();
             } else {
-                ContactEntryListAdapter adapter = getAdapter();
+                ContactListAdapter adapter = getAdapter();
                 adapter.moveToPosition(position);
                 mListener.onPickContactAction(adapter.getContactUri());
             }
@@ -58,11 +56,13 @@
     }
 
     @Override
-    protected ContactEntryListAdapter createListAdapter() {
-        ContactItemListAdapter adapter =
-                new ContactItemListAdapter((ContactsListActivity)getActivity());
+    protected ContactListAdapter createListAdapter() {
+        ContactListAdapter adapter = new ContactListAdapter(getActivity());
         adapter.setSectionHeaderDisplayEnabled(isSectionHeaderDisplayEnabled());
         adapter.setDisplayPhotos(isPhotoLoaderEnabled());
+
+        // TODO more settings
+
         return adapter;
     }
 
diff --git a/src/com/android/contacts/widget/PinnedHeaderListView.java b/src/com/android/contacts/widget/PinnedHeaderListView.java
index f37423d..24e5638 100644
--- a/src/com/android/contacts/widget/PinnedHeaderListView.java
+++ b/src/com/android/contacts/widget/PinnedHeaderListView.java
@@ -124,6 +124,7 @@
     protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
         super.onMeasure(widthMeasureSpec, heightMeasureSpec);
         if (mHeaderView != null) {
+            configureHeaderView(getFirstVisiblePosition());
             measureChild(mHeaderView, widthMeasureSpec, heightMeasureSpec);
             mHeaderViewWidth = mHeaderView.getMeasuredWidth();
             mHeaderViewHeight = mHeaderView.getMeasuredHeight();
@@ -135,7 +136,6 @@
         super.onLayout(changed, left, top, right, bottom);
         if (mHeaderView != null) {
             mHeaderView.layout(0, 0, mHeaderViewWidth, mHeaderViewHeight);
-            configureHeaderView(getFirstVisiblePosition());
         }
     }