Join activity is converted to loaders/fragments

Change-Id: I3d02fac52daffa5e0921a05fcad6932bc94e7274
diff --git a/AndroidManifest.xml b/AndroidManifest.xml
index 10a0645..05797f3 100644
--- a/AndroidManifest.xml
+++ b/AndroidManifest.xml
@@ -386,7 +386,7 @@
 
         <!-- Edit or insert details for a contact -->
         <activity
-            android:name=".activities.ContactEditActivity"
+            android:name=".ui.EditContactActivity"
             android:windowSoftInputMode="stateHidden|adjustResize">
 
             <intent-filter android:label="@string/editContactDescription">
diff --git a/res/layout-finger/contacts_list_content_join.xml b/res/layout-finger/contacts_list_content_join.xml
index b50713b..b59c240 100644
--- a/res/layout-finger/contacts_list_content_join.xml
+++ b/res/layout-finger/contacts_list_content_join.xml
@@ -61,10 +61,12 @@
         </LinearLayout>
     </LinearLayout>
 
-    <ListView android:id="@android:id/list"
-            android:layout_width="match_parent"
-            android:layout_height="match_parent"
-            android:fastScrollEnabled="true"
+    <view
+        class="com.android.contacts.ContactEntryListView" 
+        android:id="@android:id/list"
+        android:layout_width="match_parent"
+        android:layout_height="match_parent"
+        android:fastScrollEnabled="true"
     />
 </LinearLayout>
 
diff --git a/src/com/android/contacts/JoinContactActivity.java b/src/com/android/contacts/JoinContactActivity.java
index 9024e0b..501739d 100644
--- a/src/com/android/contacts/JoinContactActivity.java
+++ b/src/com/android/contacts/JoinContactActivity.java
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2007 The Android Open Source Project
+ * Copyright (C) 2009 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.
@@ -17,28 +17,21 @@
 package com.android.contacts;
 
 
-import com.android.contacts.list.JoinContactListAdapter;
 import com.android.contacts.list.JoinContactListFragment;
-import com.android.internal.telephony.gsm.stk.ResultCode;
+import com.android.contacts.list.OnContactPickerActionListener;
 
-import android.content.ContentUris;
+import android.app.Activity;
+import android.app.FragmentTransaction;
 import android.content.Intent;
-import android.database.Cursor;
-import android.database.MatrixCursor;
 import android.net.Uri;
-import android.net.Uri.Builder;
+import android.os.Bundle;
 import android.provider.ContactsContract;
-import android.provider.ContactsContract.Contacts;
-import android.provider.ContactsContract.Contacts.AggregationSuggestions;
-import android.text.TextUtils;
 import android.util.Log;
-import android.widget.ListView;
-import android.widget.TextView;
 
 /**
  * An activity that shows a list of contacts that can be joined with the target contact.
  */
-public class JoinContactActivity extends ContactsListActivity {
+public class JoinContactActivity extends Activity {
 
     private static final String TAG = "JoinContactActivity";
 
@@ -57,153 +50,43 @@
      */
     public static final String EXTRA_TARGET_CONTACT_ID = "com.android.contacts.action.CONTACT_ID";
 
-    /** Maximum number of suggestions shown for joining aggregates */
-    private static final int MAX_SUGGESTIONS = 4;
-
     private long mTargetContactId;
 
-    /**
-     * The ID of the special item described above.
-     */
-    private static final long JOIN_MODE_SHOW_ALL_CONTACTS_ID = -2;
-
-    private boolean mLoadingJoinSuggestions;
-
-    private JoinContactListAdapter mAdapter;
-
     @Override
-    protected boolean resolveIntent(Intent intent) {
-        mMode = MODE_PICK_CONTACT;
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+
+        Intent intent = getIntent();
         mTargetContactId = intent.getLongExtra(EXTRA_TARGET_CONTACT_ID, -1);
         if (mTargetContactId == -1) {
             Log.e(TAG, "Intent " + intent.getAction() + " is missing required extra: "
                     + EXTRA_TARGET_CONTACT_ID);
             setResult(RESULT_CANCELED);
             finish();
-            return false;
+            return;
         }
 
-        mListFragment = new JoinContactListFragment();
-
-        return true;
-    }
-
-    @Override
-    protected void onResume() {
-        super.onResume();
-
-        // TODO move this to onAttach of the corresponding fragment
-        TextView blurbView = (TextView)findViewById(R.id.join_contact_blurb);
-
-        String blurb = getString(R.string.blurbJoinContactDataWith,
-                getContactDisplayName(mTargetContactId));
-        blurbView.setText(blurb);
-
-        ListView listView = (ListView)findViewById(android.R.id.list);
-        mAdapter = (JoinContactListAdapter)listView.getAdapter();
-        mAdapter.setJoinModeShowAllContacts(true);
-    }
-
-    @Override
-    public void onListItemClick(int position, long id) {
-        if (id == JOIN_MODE_SHOW_ALL_CONTACTS_ID) {
-            mAdapter.setJoinModeShowAllContacts(false);
-            startQuery();
-        } else {
-            final Uri uri = getSelectedUri(position);
-            setResult(RESULT_OK, new Intent(null, uri));
-            finish();
-        }
-    }
-
-    @Override
-    protected Uri getUriToQuery() {
-        return getJoinSuggestionsUri(null);
-    }
-
-    /*
-     * TODO: move to a background thread.
-     */
-    private String getContactDisplayName(long contactId) {
-        String contactName = null;
-        Cursor c = getContentResolver().query(
-                ContentUris.withAppendedId(Contacts.CONTENT_URI, contactId),
-                new String[] {Contacts.DISPLAY_NAME}, null, null, null);
-        try {
-            if (c != null && c.moveToFirst()) {
-                contactName = c.getString(0);
-            }
-        } finally {
-            if (c != null) {
-                c.close();
-            }
-        }
-
-        if (contactName == null) {
-            contactName = "";
-        }
-
-        return contactName;
-    }
-
-    private Uri getJoinSuggestionsUri(String filter) {
-        Builder builder = Contacts.CONTENT_URI.buildUpon();
-        builder.appendEncodedPath(String.valueOf(mTargetContactId));
-        builder.appendEncodedPath(AggregationSuggestions.CONTENT_DIRECTORY);
-        if (!TextUtils.isEmpty(filter)) {
-            builder.appendEncodedPath(Uri.encode(filter));
-        }
-        builder.appendQueryParameter("limit", String.valueOf(MAX_SUGGESTIONS));
-        return builder.build();
-    }
-
-    @Override
-    public
-    Cursor doFilter(String filter) {
-        throw new UnsupportedOperationException();
-    }
-
-    private Cursor getShowAllContactsLabelCursor(String[] projection) {
-        MatrixCursor matrixCursor = new MatrixCursor(projection);
-        Object[] row = new Object[projection.length];
-        // The only columns we care about is the id
-        row[SUMMARY_ID_COLUMN_INDEX] = JOIN_MODE_SHOW_ALL_CONTACTS_ID;
-        matrixCursor.addRow(row);
-        return matrixCursor;
-    }
-
-    @Override
-    protected void startQuery(Uri uri, String[] projection) {
-        mLoadingJoinSuggestions = true;
-        startQuery(uri, projection, null, null, null);
-    }
-
-    @Override
-    protected void onQueryComplete(Cursor cursor) {
-        // Whenever we get a suggestions cursor, we need to immediately kick off
-        // another query for the complete list of contacts
-        if (cursor != null && mLoadingJoinSuggestions) {
-            mLoadingJoinSuggestions = false;
-            if (cursor.getCount() > 0) {
-                mAdapter.setSuggestionsCursor(cursor);
-            } else {
-                cursor.close();
-                mAdapter.setSuggestionsCursor(null);
+        JoinContactListFragment fragment = new JoinContactListFragment();
+        fragment.setTargetContactId(mTargetContactId);
+        fragment.setOnContactPickerActionListener(new OnContactPickerActionListener() {
+            public void onPickContactAction(Uri contactUri) {
+                Intent intent = new Intent(null, contactUri);
+                setResult(RESULT_OK, intent);
+                finish();
             }
 
-            if (mAdapter.getSuggestionsCursorCount() == 0
-                    || !mAdapter.isJoinModeShowAllContacts()) {
-                startQuery(getContactFilterUri(mListFragment.getQueryString()),
-                        CONTACTS_SUMMARY_PROJECTION,
-                        Contacts._ID + " != " + mTargetContactId
-                                + " AND " + ContactsContract.Contacts.IN_VISIBLE_GROUP + "=1", null,
-                        getSortOrder(CONTACTS_SUMMARY_PROJECTION));
-                return;
+            public void onSearchAllContactsAction(String string) {
             }
 
-            cursor = getShowAllContactsLabelCursor(CONTACTS_SUMMARY_PROJECTION);
-        }
+            public void onShortcutIntentCreated(Intent intent) {
+            }
 
-        super.onQueryComplete(cursor);
+            public void onCreateNewContactAction() {
+            }
+        });
+
+        FragmentTransaction transaction = openFragmentTransaction();
+        transaction.add(fragment, android.R.id.content);
+        transaction.commit();
     }
 }
diff --git a/src/com/android/contacts/list/ContactEntryListFragment.java b/src/com/android/contacts/list/ContactEntryListFragment.java
index 30eef5a..c1b90a5 100644
--- a/src/com/android/contacts/list/ContactEntryListFragment.java
+++ b/src/com/android/contacts/list/ContactEntryListFragment.java
@@ -164,18 +164,16 @@
             return;
         }
 
-        if (data == null) {
-            return;
-        }
-
         if (mEmptyView != null && (data == null || data.getCount() == 0)) {
             prepareEmptyView();
         }
 
         mAdapter.changeCursor(data);
-        showCount(data);
 
-        completeRestoreInstanceState();
+        if (data != null) {
+            showCount(data);
+            completeRestoreInstanceState();
+        }
     }
 
     protected void reloadData() {
@@ -461,7 +459,9 @@
 
     @Override
     public void onDestroy() {
-        mPhotoLoader.stop();
+        if (isPhotoLoaderEnabled()) {
+            mPhotoLoader.stop();
+        }
         super.onDestroy();
     }
 
diff --git a/src/com/android/contacts/list/ContactItemListAdapter.java b/src/com/android/contacts/list/ContactItemListAdapter.java
index 733edab..bfae731 100644
--- a/src/com/android/contacts/list/ContactItemListAdapter.java
+++ b/src/com/android/contacts/list/ContactItemListAdapter.java
@@ -652,11 +652,6 @@
         return super.getCount();
     }
 
-    @Override
-    protected int getCursorPosition(int position) {
-        return getRealPosition(position);
-    }
-
     protected int getRealPosition(int pos) {
         if (contactsListActivity.mShowNumberOfContacts) {
             pos--;
diff --git a/src/com/android/contacts/list/ContactListAdapter.java b/src/com/android/contacts/list/ContactListAdapter.java
index e6fe10a..3dfde7d 100644
--- a/src/com/android/contacts/list/ContactListAdapter.java
+++ b/src/com/android/contacts/list/ContactListAdapter.java
@@ -147,10 +147,13 @@
         return view;
     }
 
-    protected void bindSectionHeaderAndDivider(final ContactListItemView view, Cursor cursor) {
-        final int position = cursor.getPosition();
+    protected void bindSectionHeaderAndDivider(ContactListItemView view, Cursor cursor) {
+        bindSectionHeaderAndDivider(view, cursor.getPosition());
+    }
+
+    protected void bindSectionHeaderAndDivider(ContactListItemView view, int position) {
         final int section = getSectionForPosition(position);
-        if (getPositionForSection(section) == position) {
+        if (section != -1 && getPositionForSection(section) == position) {
             String title = (String)getSections()[section];
             view.setSectionHeader(title);
         } else {
diff --git a/src/com/android/contacts/list/DefaultContactListAdapter.java b/src/com/android/contacts/list/DefaultContactListAdapter.java
index 0a108d2..984e808 100644
--- a/src/com/android/contacts/list/DefaultContactListAdapter.java
+++ b/src/com/android/contacts/list/DefaultContactListAdapter.java
@@ -81,14 +81,6 @@
     }
 
     @Override
-    public View newView(Context context, Cursor cursor, ViewGroup parent) {
-        final ContactListItemView view = new ContactListItemView(context, null);
-        view.setUnknownNameText(getUnknownNameText());
-        view.setTextWithHighlightingFactory(getTextWithHighlightingFactory());
-        return view;
-    }
-
-    @Override
     public void bindView(View itemView, Context context, Cursor cursor) {
         final ContactListItemView view = (ContactListItemView)itemView;
 
diff --git a/src/com/android/contacts/list/JoinContactListAdapter.java b/src/com/android/contacts/list/JoinContactListAdapter.java
index 58f1c97..26c2e22 100644
--- a/src/com/android/contacts/list/JoinContactListAdapter.java
+++ b/src/com/android/contacts/list/JoinContactListAdapter.java
@@ -15,49 +15,105 @@
  */
 package com.android.contacts.list;
 
-import com.android.contacts.ContactsListActivity;
 import com.android.contacts.R;
-import com.android.contacts.list.ContactItemListAdapter;
 
+import android.app.patterns.CursorLoader;
+import android.content.Context;
 import android.database.Cursor;
+import android.database.MatrixCursor;
+import android.net.Uri;
+import android.net.Uri.Builder;
+import android.provider.ContactsContract;
+import android.provider.ContactsContract.Contacts;
+import android.provider.ContactsContract.Contacts.AggregationSuggestions;
+import android.text.TextUtils;
 import android.view.LayoutInflater;
 import android.view.View;
 import android.view.ViewGroup;
 import android.widget.TextView;
 
-public class JoinContactListAdapter extends ContactItemListAdapter {
-    Cursor mSuggestionsCursor;
-    int mSuggestionsCursorCount;
+public class JoinContactListAdapter extends ContactListAdapter {
+
+    /** Maximum number of suggestions shown for joining aggregates */
+    private static final int MAX_SUGGESTIONS = 4;
+
+    private Cursor mSuggestionsCursor;
+    private int mSuggestionsCursorCount;
+    private long mTargetContactId;
 
     /**
      * Determines whether we display a list item with the label
      * "Show all contacts" or actually show all contacts
      */
-    boolean mJoinModeShowAllContacts;
+    private boolean mAllContactsListShown;
 
-    public JoinContactListAdapter(ContactsListActivity context) {
+    public JoinContactListAdapter(Context context) {
         super(context);
+        setSectionHeaderDisplayEnabled(true);
     }
 
-    public boolean isJoinModeShowAllContacts() {
-        return mJoinModeShowAllContacts;
+    public void setTargetContactId(long targetContactId) {
+        this.mTargetContactId = targetContactId;
     }
 
-    public void setJoinModeShowAllContacts(boolean flag) {
-        mJoinModeShowAllContacts = flag;
+    @Override
+    public void configureLoader(CursorLoader cursorLoader) {
+        JoinContactLoader loader = (JoinContactLoader)cursorLoader;
+        loader.setLoadSuggestionsAndAllContacts(mAllContactsListShown);
+
+        Builder builder = Contacts.CONTENT_URI.buildUpon();
+        builder.appendEncodedPath(String.valueOf(mTargetContactId));
+        builder.appendEncodedPath(AggregationSuggestions.CONTENT_DIRECTORY);
+
+        String filter = getQueryString();
+        if (!TextUtils.isEmpty(filter)) {
+            builder.appendEncodedPath(Uri.encode(filter));
+        }
+
+        builder.appendQueryParameter("limit", String.valueOf(MAX_SUGGESTIONS));
+
+        loader.setSuggestionUri(builder.build());
+
+        // TODO simplify projection
+        loader.setProjection(PROJECTION);
+
+        if (mAllContactsListShown) {
+            loader.setUri(buildSectionIndexerUri(Contacts.CONTENT_URI));
+            loader.setSelection(Contacts.IN_VISIBLE_GROUP + "=1 AND " + Contacts._ID + "!=?");
+            loader.setSelectionArgs(new String[]{String.valueOf(mTargetContactId)});
+            if (getSortOrder() == ContactsContract.Preferences.SORT_ORDER_PRIMARY) {
+                loader.setSortOrder(Contacts.SORT_KEY_PRIMARY);
+            } else {
+                loader.setSortOrder(Contacts.SORT_KEY_ALTERNATIVE);
+            }
+        }
+    }
+
+    @Override
+    public boolean isEmpty() {
+        return false;
+    }
+
+    private boolean hasSuggestions() {
+        return mSuggestionsCursorCount != 0;
+    }
+
+    public boolean isAllContactsListShown() {
+        return mAllContactsListShown;
+    }
+
+    public void setAllContactsListShown(boolean flag) {
+        mAllContactsListShown = flag;
     }
 
     public void setSuggestionsCursor(Cursor cursor) {
-        if (mSuggestionsCursor != null) {
-            mSuggestionsCursor.close();
-        }
         mSuggestionsCursor = cursor;
         mSuggestionsCursorCount = cursor == null ? 0 : cursor.getCount();
     }
 
-    private boolean isShowAllContactsItemPosition(int position) {
-        return mJoinModeShowAllContacts
-                && mSuggestionsCursorCount != 0 && position == mSuggestionsCursorCount + 2;
+    public boolean isShowAllContactsItemPosition(int position) {
+        return !mAllContactsListShown
+                && hasSuggestions() && position == mSuggestionsCursorCount + 2;
     }
 
     @Override
@@ -67,33 +123,32 @@
                     "this should only be called when the cursor is valid");
         }
 
-        if (isShowAllContactsItemPosition(position)) {
-            return LayoutInflater.from(getContext()).
-                    inflate(R.layout.contacts_list_show_all_item, parent, false);
-        }
-
-        // Handle the separator specially
-        int separatorId = getSeparatorId(position);
-        if (separatorId != 0) {
-            TextView view = (TextView) LayoutInflater.from(getContext()).
-                    inflate(R.layout.list_separator, parent, false);
-            view.setText(separatorId);
-            return view;
-        }
-
-        boolean showingSuggestion;
         Cursor cursor;
-        if (mSuggestionsCursorCount != 0 && position < mSuggestionsCursorCount + 2) {
-            showingSuggestion = true;
-            cursor = mSuggestionsCursor;
+        boolean showingSuggestion = false;
+        if (hasSuggestions()) {
+            if (position == 0) {
+                // First section: "suggestions"
+                TextView view = (TextView) inflate(R.layout.list_separator, parent);
+                view.setText(R.string.separatorJoinAggregateSuggestions);
+                return view;
+            } else if (position < mSuggestionsCursorCount + 1) {
+                showingSuggestion = true;
+                cursor = mSuggestionsCursor;
+                cursor.moveToPosition(position - 1);
+            } else if (position == mSuggestionsCursorCount + 1) {
+                // Second section: "all contacts"
+                TextView view = (TextView) inflate(R.layout.list_separator, parent);
+                view.setText(R.string.separatorJoinAggregateAll);
+                return view;
+            } else if (!mAllContactsListShown && position == mSuggestionsCursorCount + 2) {
+                return inflate(R.layout.contacts_list_show_all_item, parent);
+            } else {
+                cursor = mCursor;
+                cursor.moveToPosition(position - mSuggestionsCursorCount - 2);
+            }
         } else {
-            showingSuggestion = false;
             cursor = mCursor;
-        }
-
-        int realPosition = getRealPosition(position);
-        if (!cursor.moveToPosition(realPosition)) {
-            throw new IllegalStateException("couldn't move cursor to position " + position);
+            cursor.moveToPosition(position);
         }
 
         boolean newView;
@@ -105,11 +160,35 @@
             newView = false;
             v = convertView;
         }
-        bindView(v, getContext(), cursor);
-        bindSectionHeader(v, realPosition, !showingSuggestion);
+        bindView(position, v, cursor, showingSuggestion);
         return v;
     }
 
+    private View inflate(int layoutId, ViewGroup parent) {
+        return LayoutInflater.from(getContext()).inflate(layoutId, parent, false);
+    }
+
+    @Override
+    public void bindView(View view, Context context, Cursor cursor) {
+        // not used
+    }
+
+    public void bindView(int position, View itemView, Cursor cursor, boolean showingSuggestion) {
+        final ContactListItemView view = (ContactListItemView)itemView;
+        if (!showingSuggestion) {
+            bindSectionHeaderAndDivider(view, position);
+        }
+        bindPhoto(view, cursor);
+        bindName(view, cursor);
+    }
+
+    public Cursor getShowAllContactsLabelCursor() {
+        MatrixCursor matrixCursor = new MatrixCursor(PROJECTION);
+        Object[] row = new Object[PROJECTION.length];
+        matrixCursor.addRow(row);
+        return matrixCursor;
+    }
+
     @Override
     public void changeCursor(Cursor cursor) {
         if (cursor == null) {
@@ -118,6 +197,7 @@
 
         super.changeCursor(cursor);
     }
+
     @Override
     public int getItemViewType(int position) {
         if (isShowAllContactsItemPosition(position)) {
@@ -127,15 +207,28 @@
         return super.getItemViewType(position);
     }
 
-    private int getSeparatorId(int position) {
-        if (mSuggestionsCursorCount != 0) {
-            if (position == 0) {
-                return R.string.separatorJoinAggregateSuggestions;
-            } else if (position == mSuggestionsCursorCount + 1) {
-                return R.string.separatorJoinAggregateAll;
-            }
+    @Override
+    public int getPositionForSection(int sectionIndex) {
+        if (mSuggestionsCursorCount == 0) {
+            return super.getPositionForSection(sectionIndex);
         }
-        return 0;
+
+        // Get section position in the full list
+        int position = super.getPositionForSection(sectionIndex);
+        return position + mSuggestionsCursorCount + 2;
+    }
+
+    @Override
+    public int getSectionForPosition(int position) {
+        if (mSuggestionsCursorCount == 0) {
+            return super.getSectionForPosition(position);
+        }
+
+        if (position < mSuggestionsCursorCount + 2) {
+            return -1;
+        }
+
+        return super.getSectionForPosition(position - mSuggestionsCursorCount - 2);
     }
 
     @Override
@@ -161,7 +254,7 @@
             return 0;
         }
         int superCount = super.getCount();
-        if (mSuggestionsCursorCount != 0) {
+        if (hasSuggestions()) {
             // When showing suggestions, we have 2 additional list items: the "Suggestions"
             // and "All contacts" headers.
             return mSuggestionsCursorCount + superCount + 2;
@@ -174,52 +267,51 @@
     }
 
     @Override
-    protected int getRealPosition(int pos) {
-        if (mSuggestionsCursorCount != 0) {
+    public Object getItem(int pos) {
+        if (hasSuggestions()) {
             // When showing suggestions, we have 2 additional list items: the "Suggestions"
             // and "All contacts" separators.
-            if (pos < mSuggestionsCursorCount + 2) {
-                // We are in the upper partition (Suggestions). Adjusting for the "Suggestions"
-                // separator.
-                return pos - 1;
-            } else {
-                // We are in the lower partition (All contacts). Adjusting for the size
-                // of the upper partition plus the two separators.
-                return pos - mSuggestionsCursorCount - 2;
-            }
-        } else {
-            // No separator, identity map
-            return pos;
-        }
-    }
-
-    @Override
-    public Object getItem(int pos) {
-        if (mSuggestionsCursorCount != 0 && pos <= mSuggestionsCursorCount) {
-            mSuggestionsCursor.moveToPosition(getRealPosition(pos));
-            return mSuggestionsCursor;
-        } else {
-            int realPosition = getRealPosition(pos);
-            if (realPosition < 0) {
+            if (pos == 0) {
                 return null;
             }
-            return super.getItem(realPosition);
+            else if (pos < mSuggestionsCursorCount + 1) {
+                // We are in the upper partition (Suggestions). Adjusting for the "Suggestions"
+                // separator.
+                mSuggestionsCursor.moveToPosition(pos - 1);
+                return mSuggestionsCursor;
+            } else if (pos == mSuggestionsCursorCount + 1) {
+                // This is the "All contacts" separator
+                return null;
+            } else {
+                if (!isAllContactsListShown()) {
+                    // This is the "Show all contacts" item
+                    return null;
+                } else {
+                    // We are in the lower partition (All contacts). Adjusting for the size
+                    // of the upper partition plus the two separators.
+                    mCursor.moveToPosition(pos - mSuggestionsCursorCount - 2);
+                    return mCursor;
+                }
+            }
+        } else if (mCursor != null) {
+            // No separators
+            mCursor.moveToPosition(pos);
+            return mCursor;
+        } else {
+            return null;
         }
     }
 
     @Override
     public long getItemId(int pos) {
-        if (mSuggestionsCursorCount != 0 && pos < mSuggestionsCursorCount + 2) {
-            if (mSuggestionsCursor.moveToPosition(pos - 1)) {
-                return mSuggestionsCursor.getLong(mRowIDColumn);
-            } else {
-                return 0;
-            }
-        }
-        int realPosition = getRealPosition(pos);
-        if (realPosition < 0) {
-            return 0;
-        }
-        return super.getItemId(realPosition);
+        Cursor cursor = (Cursor)getItem(pos);
+        return cursor == null ? 0 : cursor.getLong(mRowIDColumn);
+    }
+
+    public Uri getContactUri(int position) {
+        Cursor cursor = (Cursor)getItem(position);
+        long contactId = cursor.getLong(CONTACT_ID_COLUMN_INDEX);
+        String lookupKey = cursor.getString(CONTACT_LOOKUP_KEY_COLUMN_INDEX);
+        return Contacts.getLookupUri(contactId, lookupKey);
     }
 }
\ No newline at end of file
diff --git a/src/com/android/contacts/list/JoinContactListFragment.java b/src/com/android/contacts/list/JoinContactListFragment.java
index 3693070..202ee67 100644
--- a/src/com/android/contacts/list/JoinContactListFragment.java
+++ b/src/com/android/contacts/list/JoinContactListFragment.java
@@ -15,25 +15,99 @@
  */
 package com.android.contacts.list;
 
-import com.android.contacts.JoinContactActivity;
 import com.android.contacts.R;
 
+import android.app.Activity;
+import android.app.patterns.CursorLoader;
+import android.app.patterns.Loader;
+import android.content.ContentUris;
+import android.database.Cursor;
+import android.os.Bundle;
+import android.provider.ContactsContract.Contacts;
 import android.view.LayoutInflater;
 import android.view.View;
 import android.view.ViewGroup;
+import android.widget.TextView;
 
 /**
  * Fragment for the Join Contact list.
  */
-public class JoinContactListFragment extends ContactEntryListFragment {
+public class JoinContactListFragment extends ContactEntryListFragment<JoinContactListAdapter> {
+
+    private static final int DISPLAY_NAME_LOADER = 1;
+
+    private OnContactPickerActionListener mListener;
+    private long mTargetContactId;
+    private boolean mAllContactsListShown = false;
+
+    public JoinContactListFragment() {
+        setPhotoLoaderEnabled(true);
+        setSectionHeaderDisplayEnabled(true);
+    }
+
+    public void setOnContactPickerActionListener(OnContactPickerActionListener listener) {
+        mListener = listener;
+    }
 
     @Override
-    public ContactEntryListAdapter createListAdapter() {
-        JoinContactListAdapter adapter =
-                new JoinContactListAdapter((JoinContactActivity)getActivity());
-        adapter.setSectionHeaderDisplayEnabled(true);
-        adapter.setDisplayPhotos(true);
-        return adapter;
+    protected void onInitializeLoaders() {
+        super.onInitializeLoaders();
+        startLoading(DISPLAY_NAME_LOADER, null);
+    }
+
+    @Override
+    protected Loader<Cursor> onCreateLoader(int id, Bundle args) {
+        if (id == DISPLAY_NAME_LOADER) {
+            // Loader for the display name of the target contact
+            return new CursorLoader(getActivity(),
+                    ContentUris.withAppendedId(Contacts.CONTENT_URI, mTargetContactId),
+                    new String[] {Contacts.DISPLAY_NAME}, null, null, null);
+        } else {
+            return new JoinContactLoader(getActivity());
+        }
+    }
+
+    @Override
+    protected void onLoadFinished(Loader<Cursor> loader, Cursor data) {
+        if (loader.getId() == DISPLAY_NAME_LOADER) {
+            if (data != null && data.moveToFirst()) {
+                showTargetContactName(data.getString(0));
+            }
+        } else {
+            JoinContactListAdapter adapter = getAdapter();
+            if (adapter.isAllContactsListShown()) {
+                Cursor suggestionsCursor = ((JoinContactLoader)loader).getSuggestionsCursor();
+                adapter.setSuggestionsCursor(suggestionsCursor);
+                super.onLoadFinished(loader, data);
+            } else {
+                adapter.setSuggestionsCursor(data);
+                super.onLoadFinished(loader, adapter.getShowAllContactsLabelCursor());
+            }
+        }
+    }
+
+     private void showTargetContactName(String displayName) {
+        Activity activity = getActivity();
+        TextView blurbView = (TextView)activity.findViewById(R.id.join_contact_blurb);
+        String blurb = activity.getString(R.string.blurbJoinContactDataWith, displayName);
+        blurbView.setText(blurb);
+    }
+
+    public void setTargetContactId(long targetContactId) {
+        mTargetContactId = targetContactId;
+    }
+
+    @Override
+    public JoinContactListAdapter createListAdapter() {
+        return new JoinContactListAdapter(getActivity());
+    }
+
+    @Override
+    protected void configureAdapter() {
+        super.configureAdapter();
+        JoinContactListAdapter adapter = getAdapter();
+        adapter.setAllContactsListShown(mAllContactsListShown);
+        adapter.setTargetContactId(mTargetContactId);
     }
 
     @Override
@@ -43,7 +117,12 @@
 
     @Override
     protected void onItemClick(int position, long id) {
-        // TODO
-        throw new UnsupportedOperationException();
+        JoinContactListAdapter adapter = getAdapter();
+        if (adapter.isShowAllContactsItemPosition(position)) {
+            mAllContactsListShown = true;
+            reloadData();
+        } else {
+            mListener.onPickContactAction(adapter.getContactUri(position));
+        }
     }
 }
diff --git a/src/com/android/contacts/list/JoinContactLoader.java b/src/com/android/contacts/list/JoinContactLoader.java
new file mode 100644
index 0000000..c15acb1
--- /dev/null
+++ b/src/com/android/contacts/list/JoinContactLoader.java
@@ -0,0 +1,95 @@
+/*
+ * 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.database.MatrixCursor;
+import android.net.Uri;
+
+/**
+ * A specialized loader for the Join Contacts UI.  It executes up to two queries:
+ * join suggestions and (optionally) the full contact list.
+ */
+public class JoinContactLoader extends CursorLoader {
+
+    private boolean mLoadSuggestionsAndAllContact;
+    private String[] mProjection;
+    private Uri mSuggestionUri;
+    private MatrixCursor mCachedCursor;
+
+    public JoinContactLoader(Context context) {
+        super(context, null, null, null, null, null);
+    }
+
+    public void setSuggestionUri(Uri uri) {
+        this.mSuggestionUri = uri;
+    }
+
+    @Override
+    public void setProjection(String[] projection) {
+        super.setProjection(projection);
+        this.mProjection = projection;
+    }
+
+    public Cursor getSuggestionsCursor() {
+        return mCachedCursor;
+    }
+
+    @Override
+    protected Cursor loadInBackground() {
+        if (mLoadSuggestionsAndAllContact) {
+            // First execute the suggestions query, then call super.loadInBackground
+            // to load the entire list
+            mCachedCursor = loadSuggestions();
+            return super.loadInBackground();
+        } else {
+            // Use the default behavior of the super.loadInBackground to load join
+            // suggestions only
+            setUri(mSuggestionUri);
+            setSelection(null);
+            setSelectionArgs(null);
+            setSortOrder(null);
+            return super.loadInBackground();
+        }
+    }
+
+    /**
+     * Loads join suggestions into a MatrixCursor.
+     */
+    private MatrixCursor loadSuggestions() {
+        Cursor cursor = getContext().getContentResolver().query(mSuggestionUri, 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();
+        }
+    }
+
+    public void setLoadSuggestionsAndAllContacts(boolean flag) {
+        mLoadSuggestionsAndAllContact = flag;
+    }
+}
\ No newline at end of file
diff --git a/src/com/android/contacts/list/StrequentContactListAdapter.java b/src/com/android/contacts/list/StrequentContactListAdapter.java
index 239f804..cef0604 100644
--- a/src/com/android/contacts/list/StrequentContactListAdapter.java
+++ b/src/com/android/contacts/list/StrequentContactListAdapter.java
@@ -147,9 +147,7 @@
 
     @Override
     public View newView(Context context, Cursor cursor, ViewGroup parent) {
-        final ContactListItemView view = new ContactListItemView(context, null);
-        view.setUnknownNameText(getUnknownNameText());
-        view.setTextWithHighlightingFactory(getTextWithHighlightingFactory());
+        ContactListItemView view = (ContactListItemView)super.newView(context, cursor, parent);
         view.setOnCallButtonClickListener(mCallButtonListener);
         return view;
     }
diff --git a/src/com/android/contacts/widget/PinnedHeaderListAdapter.java b/src/com/android/contacts/widget/PinnedHeaderListAdapter.java
index db99e14..7492de1 100644
--- a/src/com/android/contacts/widget/PinnedHeaderListAdapter.java
+++ b/src/com/android/contacts/widget/PinnedHeaderListAdapter.java
@@ -64,16 +64,6 @@
         mIndexer = indexer;
     }
 
-    /**
-     * Maps an adapter position to the corresponding cursor position.  Should not
-     * be needed once we have switched to using header views and composite
-     * list adapters.
-     */
-    @Deprecated
-    protected int getCursorPosition(int position) {
-        return position;
-    }
-
     public Object [] getSections() {
         if (mIndexer == null) {
             return new String[] { " " };
@@ -107,16 +97,15 @@
             return PINNED_HEADER_GONE;
         }
 
-        int realPosition = getCursorPosition(position);
-        if (realPosition < 0) {
+        // The header should get pushed up if the top item shown
+        // is the last item in a section for a particular letter.
+        int section = getSectionForPosition(position);
+        if (section == -1) {
             return PINNED_HEADER_GONE;
         }
 
-        // The header should get pushed up if the top item shown
-        // is the last item in a section for a particular letter.
-        int section = getSectionForPosition(realPosition);
         int nextSectionPosition = getPositionForSection(section + 1);
-        if (nextSectionPosition != -1 && realPosition == nextSectionPosition - 1) {
+        if (nextSectionPosition != -1 && position == nextSectionPosition - 1) {
             return PINNED_HEADER_PUSHED_UP;
         }
 
@@ -144,8 +133,7 @@
             header.setTag(cache);
         }
 
-        int realPosition = getCursorPosition(position);
-        int section = getSectionForPosition(realPosition);
+        int section = getSectionForPosition(position);
 
         String title = (String)mIndexer.getSections()[section];
         cache.titleView.setText(title);