Introducing ContactsApplicationController

Also, breaking out JoinContactListAdapter

Change-Id: If37410ec5d15612a772c0ad2e946df6e91339b0d
diff --git a/src/com/android/contacts/ContactsApplicationController.java b/src/com/android/contacts/ContactsApplicationController.java
new file mode 100644
index 0000000..ac7fd3f
--- /dev/null
+++ b/src/com/android/contacts/ContactsApplicationController.java
@@ -0,0 +1,31 @@
+/*
+ * 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;
+
+/**
+ * An interface that captures various top-level actions that can be performed in
+ * the Contacts app.
+ */
+public interface ContactsApplicationController {
+
+    /**
+     * This is a temporary bridge to the ContactsListActivity indended to be used ONLY
+     * during the refactoring phase.
+     */
+    @Deprecated
+    public void onListItemClick(int position, long id);
+
+}
diff --git a/src/com/android/contacts/ContactsListActivity.java b/src/com/android/contacts/ContactsListActivity.java
index ccbe0ac..400763e 100644
--- a/src/com/android/contacts/ContactsListActivity.java
+++ b/src/com/android/contacts/ContactsListActivity.java
@@ -108,6 +108,7 @@
 import android.widget.ArrayAdapter;
 import android.widget.Button;
 import android.widget.Filter;
+import android.widget.ListAdapter;
 import android.widget.ListView;
 import android.widget.TextView;
 import android.widget.Toast;
@@ -124,7 +125,7 @@
 @SuppressWarnings("deprecation")
 public class ContactsListActivity extends ListActivity implements View.OnCreateContextMenuListener,
         View.OnClickListener, View.OnKeyListener, TextWatcher, TextView.OnEditorActionListener,
-        OnFocusChangeListener, OnTouchListener, OnScrollListener {
+        OnFocusChangeListener, OnTouchListener, OnScrollListener, ContactsApplicationController {
 
     private static final String TAG = "ContactsListActivity";
 
@@ -507,10 +508,10 @@
     };
 
     private ContactsIntentResolver mIntentResolver;
-    private ContactEntryListConfiguration mConfig;
+    protected ContactEntryListConfiguration mConfig;
 
     public ContactsListActivity() {
-        mIntentResolver = new ContactsIntentResolver(this);
+        mIntentResolver = new ContactsIntentResolver(this, this);
     }
 
     /**
@@ -535,29 +536,28 @@
         // Resolve the intent
         final Intent intent = getIntent();
 
-        resolveIntent(intent);
+        mConfig = resolveIntent(intent);
         initContentView();
     }
 
-    protected void resolveIntent(final Intent intent) {
+    protected ContactEntryListConfiguration resolveIntent(final Intent intent) {
         mIntentResolver.setIntent(intent);
 
         if (!mIntentResolver.isValid()) {           // Invalid intent
             setResult(RESULT_CANCELED);
             finish();
-            return;
+            return null;
         }
 
         Intent redirect = mIntentResolver.getRedirectIntent();
         if (redirect != null) {             // Need to start a different activity
             startActivity(redirect);
             finish();
-            return;
+            return null;
         }
 
         setTitle(mIntentResolver.getActivityTitle());
 
-        mConfig = mIntentResolver.getConfiguration();
 
         // This is strictly temporary. Its purpose is to allow us to refactor this class in
         // small increments.  We should expect all of these modes to go away.
@@ -572,6 +572,8 @@
         mSearchResultsMode = mIntentResolver.mSearchResultsMode;
         mShowNumberOfContacts = mIntentResolver.mShowNumberOfContacts;
         mGroupName = mIntentResolver.mGroupName;
+
+        return mIntentResolver.getConfiguration();
     }
 
     public void initContentView() {
@@ -586,7 +588,8 @@
             setContentView(R.layout.contacts_list_content);
         }
 
-        setupListView(createListAdapter());
+        mConfig.configureListView(getListView());
+
         if (mSearchMode) {
             setupSearchView();
         }
@@ -597,29 +600,9 @@
         }
     }
 
-    protected ContactItemListAdapter createListAdapter() {
-        // TODO there should be no need to cast
-        return (ContactItemListAdapter)mConfig.createListAdapter();
-    }
-
-    /**
-     * Register an observer for provider status changes - we will need to
-     * reflect them in the UI.
-     */
-    private void registerProviderStatusObserver() {
-        getContentResolver().registerContentObserver(ProviderStatus.CONTENT_URI,
-                false, mProviderStatusObserver);
-    }
-
-    /**
-     * Register an observer for provider status changes - we will need to
-     * reflect them in the UI.
-     */
-    private void unregisterProviderStatusObserver() {
-        getContentResolver().unregisterContentObserver(mProviderStatusObserver);
-    }
-
-    protected void setupListView(ContactItemListAdapter adapter) {
+    // TODO move this to the configuration object(s)
+    @Deprecated
+    public void setupListView(ListAdapter adapter) {
         final ListView list = getListView();
         final LayoutInflater inflater = getLayoutInflater();
 
@@ -631,7 +614,7 @@
         list.setDividerHeight(0);
         list.setOnCreateContextMenuListener(this);
 
-        mAdapter = adapter;
+        mAdapter = (ContactEntryListAdapter)adapter;
         setListAdapter(mAdapter);
 
         if (list instanceof PinnedHeaderListView && mConfig.isSectionHeaderDisplayEnabled()) {
@@ -651,6 +634,23 @@
         list.setSaveEnabled(false);
     }
 
+    /**
+     * Register an observer for provider status changes - we will need to
+     * reflect them in the UI.
+     */
+    private void registerProviderStatusObserver() {
+        getContentResolver().registerContentObserver(ProviderStatus.CONTENT_URI,
+                false, mProviderStatusObserver);
+    }
+
+    /**
+     * Register an observer for provider status changes - we will need to
+     * reflect them in the UI.
+     */
+    private void unregisterProviderStatusObserver() {
+        getContentResolver().unregisterContentObserver(mProviderStatusObserver);
+    }
+
     public void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount,
             int totalItemCount) {
         if (view instanceof PinnedHeaderListView) {
@@ -1508,14 +1508,7 @@
         return false;
     }
 
-    @Override
-    protected void onListItemClick(ListView l, View v, int position, long id) {
-        hideSoftKeyboard();
-
-        onListItemClick(position, id);
-    }
-
-    protected void onListItemClick(int position, long id) {
+    public void onListItemClick(int position, long id) {
         if (mSearchMode &&
                 ((ContactItemListAdapter)(mAdapter)).isSearchAllContactsItemPosition(position)) {
             doSearch();
diff --git a/src/com/android/contacts/JoinContactActivity.java b/src/com/android/contacts/JoinContactActivity.java
index 58a14a6..5e737a7 100644
--- a/src/com/android/contacts/JoinContactActivity.java
+++ b/src/com/android/contacts/JoinContactActivity.java
@@ -16,10 +16,12 @@
 
 package com.android.contacts;
 
-import com.android.contacts.list.ContactItemListAdapter;
+
+import com.android.contacts.list.ContactEntryListConfiguration;
+import com.android.contacts.list.JoinContactListAdapter;
+import com.android.contacts.list.JoinContactListConfiguration;
 
 import android.content.ContentUris;
-import android.content.Context;
 import android.content.Intent;
 import android.database.Cursor;
 import android.database.MatrixCursor;
@@ -30,8 +32,6 @@
 import android.provider.ContactsContract.Contacts.AggregationSuggestions;
 import android.text.TextUtils;
 import android.util.Log;
-import android.view.View;
-import android.view.ViewGroup;
 import android.widget.TextView;
 
 /**
@@ -62,12 +62,6 @@
     private long mTargetContactId;
 
     /**
-     * Determines whether we display a list item with the label
-     * "Show all contacts" or actually show all contacts
-     */
-    private boolean mJoinModeShowAllContacts;
-
-    /**
      * The ID of the special item described above.
      */
     private static final long JOIN_MODE_SHOW_ALL_CONTACTS_ID = -2;
@@ -77,7 +71,7 @@
     private JoinContactListAdapter mAdapter;
 
     @Override
-    protected void resolveIntent(Intent intent) {
+    protected ContactEntryListConfiguration resolveIntent(Intent intent) {
         mMode = MODE_PICK_CONTACT;
         mTargetContactId = intent.getLongExtra(EXTRA_TARGET_CONTACT_ID, -1);
         if (mTargetContactId == -1) {
@@ -85,7 +79,9 @@
                     + EXTRA_TARGET_CONTACT_ID);
             setResult(RESULT_CANCELED);
             finish();
+            return null;
         }
+        return new JoinContactListConfiguration(this, this);
     }
 
     @Override
@@ -96,15 +92,16 @@
         String blurb = getString(R.string.blurbJoinContactDataWith,
                 getContactDisplayName(mTargetContactId));
         blurbView.setText(blurb);
-        mJoinModeShowAllContacts = true;
-        mAdapter = new JoinContactListAdapter(this);
-        setupListView(mAdapter);
+        mConfig.configureListView(getListView());
+
+        mAdapter = (JoinContactListAdapter)getListView().getAdapter();
+        mAdapter.setJoinModeShowAllContacts(true);
     }
 
     @Override
-    protected void onListItemClick(int position, long id) {
+    public void onListItemClick(int position, long id) {
         if (id == JOIN_MODE_SHOW_ALL_CONTACTS_ID) {
-            mJoinModeShowAllContacts = false;
+            mAdapter.setJoinModeShowAllContacts(false);
             startQuery();
         } else {
             final Uri uri = getSelectedUri(position);
@@ -187,8 +184,8 @@
                 mAdapter.setSuggestionsCursor(null);
             }
 
-            if (mAdapter.mSuggestionsCursorCount == 0
-                    || !mJoinModeShowAllContacts) {
+            if (mAdapter.getSuggestionsCursorCount() == 0
+                    || !mAdapter.isJoinModeShowAllContacts()) {
                 startQuery(getContactFilterUri(getTextFilter()),
                         CONTACTS_SUMMARY_PROJECTION,
                         Contacts._ID + " != " + mTargetContactId
@@ -202,185 +199,4 @@
 
         super.onQueryComplete(cursor);
     }
-
-    private class JoinContactListAdapter extends ContactItemListAdapter {
-        Cursor mSuggestionsCursor;
-        int mSuggestionsCursorCount;
-
-        public JoinContactListAdapter(ContactsListActivity context) {
-            super(context);
-        }
-
-        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;
-        }
-
-        @Override
-        public View getView(int position, View convertView, ViewGroup parent) {
-            if (!mDataValid) {
-                throw new IllegalStateException(
-                        "this should only be called when the cursor is valid");
-            }
-
-            if (isShowAllContactsItemPosition(position)) {
-                return getLayoutInflater().
-                        inflate(R.layout.contacts_list_show_all_item, parent, false);
-            }
-
-            // Handle the separator specially
-            int separatorId = getSeparatorId(position);
-            if (separatorId != 0) {
-                TextView view = (TextView) getLayoutInflater().
-                        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;
-            } else {
-                showingSuggestion = false;
-                cursor = mCursor;
-            }
-
-            int realPosition = getRealPosition(position);
-            if (!cursor.moveToPosition(realPosition)) {
-                throw new IllegalStateException("couldn't move cursor to position " + position);
-            }
-
-            boolean newView;
-            View v;
-            if (convertView == null || convertView.getTag() == null) {
-                newView = true;
-                v = newView(mContext, cursor, parent);
-            } else {
-                newView = false;
-                v = convertView;
-            }
-            bindView(v, mContext, cursor);
-            bindSectionHeader(v, realPosition, !showingSuggestion);
-            return v;
-        }
-
-        @Override
-        public void changeCursor(Cursor cursor) {
-            if (cursor == null) {
-                mAdapter.setSuggestionsCursor(null);
-            }
-
-            super.changeCursor(cursor);
-        }
-        @Override
-        public int getItemViewType(int position) {
-            if (isShowAllContactsItemPosition(position)) {
-                return IGNORE_ITEM_VIEW_TYPE;
-            }
-
-            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;
-                }
-            }
-            return 0;
-        }
-
-        @Override
-        public boolean areAllItemsEnabled() {
-            return super.areAllItemsEnabled() && mSuggestionsCursorCount == 0;
-        }
-
-        @Override
-        public boolean isEnabled(int position) {
-            if (position == 0) {
-                return false;
-            }
-
-            if (mSuggestionsCursorCount > 0) {
-                return position != 0 && position != mSuggestionsCursorCount + 1;
-            }
-            return true;
-        }
-
-        @Override
-        public int getCount() {
-            if (!mDataValid) {
-                return 0;
-            }
-            int superCount = super.getCount();
-            if (mSuggestionsCursorCount != 0) {
-                // When showing suggestions, we have 2 additional list items: the "Suggestions"
-                // and "All contacts" headers.
-                return mSuggestionsCursorCount + superCount + 2;
-            }
-            return superCount;
-        }
-
-        @Override
-        protected int getRealPosition(int pos) {
-            if (mSuggestionsCursorCount != 0) {
-                // 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) {
-                    return null;
-                }
-                return super.getItem(realPosition);
-            }
-        }
-
-        @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);
-        }
-    }
 }
diff --git a/src/com/android/contacts/MultiplePhonePickerActivity.java b/src/com/android/contacts/MultiplePhonePickerActivity.java
index 549fde8..6c27c3e 100644
--- a/src/com/android/contacts/MultiplePhonePickerActivity.java
+++ b/src/com/android/contacts/MultiplePhonePickerActivity.java
@@ -16,9 +16,9 @@
 
 package com.android.contacts;
 
-import com.android.contacts.list.ContactItemListAdapter;
 import com.android.contacts.list.MultiplePhoneExtraAdapter;
 import com.android.contacts.list.MultiplePhonePickerAdapter;
+import com.android.contacts.list.MultiplePhonePickerConfiguration;
 import com.android.contacts.list.MultiplePhoneSelection;
 
 import android.app.ProgressDialog;
@@ -126,13 +126,10 @@
     }
 
     @Override
-    protected ContactItemListAdapter createListAdapter() {
-        return new MultiplePhonePickerAdapter(this, mPhoneNumberAdapter);
-    }
-
-    @Override
     public void initContentView() {
         super.initContentView();
+        ((MultiplePhonePickerAdapter)getListView().getAdapter())
+                .setExtraAdapter(mPhoneNumberAdapter);
         ViewStub stub = (ViewStub)findViewById(R.id.footer_stub);
         if (stub != null) {
             View stubView = stub.inflate();
diff --git a/src/com/android/contacts/list/ContactEntryListAdapter.java b/src/com/android/contacts/list/ContactEntryListAdapter.java
index 6a45a87..a353fed 100644
--- a/src/com/android/contacts/list/ContactEntryListAdapter.java
+++ b/src/com/android/contacts/list/ContactEntryListAdapter.java
@@ -24,8 +24,15 @@
  */
 public abstract class ContactEntryListAdapter extends CursorAdapter {
 
+    private final Context mContext;
+
     public ContactEntryListAdapter(Context context) {
         super(context, null, false);
+        this.mContext = context;
+    }
+
+    public Context getContext() {
+        return mContext;
     }
 
     /*
diff --git a/src/com/android/contacts/list/ContactEntryListConfiguration.java b/src/com/android/contacts/list/ContactEntryListConfiguration.java
index 6caef33..98ad73a 100644
--- a/src/com/android/contacts/list/ContactEntryListConfiguration.java
+++ b/src/com/android/contacts/list/ContactEntryListConfiguration.java
@@ -16,8 +16,12 @@
 
 package com.android.contacts.list;
 
+import com.android.contacts.ContactsApplicationController;
+import com.android.contacts.ContactsListActivity;
+
 import android.content.Context;
 import android.widget.ListAdapter;
+import android.widget.ListView;
 
 /**
  * Common base class for configurations of various contact-related lists, e.g.
@@ -26,18 +30,37 @@
 public abstract class ContactEntryListConfiguration {
 
     private final Context mContext;
+    private final ContactsApplicationController mApplicationController;
     private boolean mSectionHeaderDisplayEnabled;
     private boolean mPhotoLoaderEnabled;
 
-    public ContactEntryListConfiguration(Context context) {
+    public ContactEntryListConfiguration(Context context,
+            ContactsApplicationController applicationController) {
         this.mContext = context;
+        this.mApplicationController = applicationController;
     }
 
     public Context getContext() {
         return mContext;
     }
 
+    public ContactsApplicationController getApplicationController() {
+        return mApplicationController;
+    }
+
     public abstract ListAdapter createListAdapter();
+    public abstract ContactEntryListController createController();
+
+    public void configureListView(ListView listView) {
+        ListAdapter adapter = createListAdapter();
+        ContactEntryListController controller = createController();
+        controller.setAdapter(adapter);
+        listView.setAdapter(adapter);
+        listView.setOnItemClickListener(controller);
+        controller.setListView(listView);
+
+        ((ContactsListActivity)mContext).setupListView(adapter);
+    }
 
     public void setSectionHeaderDisplayEnabled(boolean flag) {
         mSectionHeaderDisplayEnabled = flag;
diff --git a/src/com/android/contacts/list/ContactEntryListController.java b/src/com/android/contacts/list/ContactEntryListController.java
index ca8c176..a190b4e 100644
--- a/src/com/android/contacts/list/ContactEntryListController.java
+++ b/src/com/android/contacts/list/ContactEntryListController.java
@@ -16,11 +16,67 @@
 
 package com.android.contacts.list;
 
+import com.android.contacts.ContactsApplicationController;
+
+import android.content.Context;
+import android.view.View;
+import android.view.inputmethod.InputMethodManager;
+import android.widget.AdapterView;
+import android.widget.ListAdapter;
+import android.widget.ListView;
 
 /**
- * Common base class for various contact-related lists, e.g. contact list, phone number list
- * etc.
+ * Common base class for various contact-related list controllers.
  */
-public abstract class ContactEntryListController {
+public abstract class ContactEntryListController implements AdapterView.OnItemClickListener {
 
+    private final Context mContext;
+    private final ContactsApplicationController mAppController;
+    private ListAdapter mAdapter;
+    private ListView mListView;
+
+    public ContactEntryListController(Context context,
+            ContactsApplicationController appController) {
+        this.mContext = context;
+        this.mAppController = appController;
+    }
+
+    public Context getContext() {
+        return mContext;
+    }
+
+    public ContactsApplicationController getContactsApplicationController() {
+        return mAppController;
+    }
+
+    public void setAdapter(ListAdapter adapter) {
+        mAdapter = adapter;
+    }
+
+    public ListAdapter getAdapter() {
+        return mAdapter;
+    }
+
+    public void setListView(ListView listView) {
+        mListView = listView;
+    }
+
+    public ListView getListView() {
+        return mListView;
+    }
+
+    public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
+        hideSoftKeyboard();
+
+        onItemClick(position, id);
+    }
+
+    protected abstract void onItemClick(int position, long id);
+
+    private void hideSoftKeyboard() {
+        // Hide soft keyboard, if visible
+        InputMethodManager inputMethodManager = (InputMethodManager)
+                mContext.getSystemService(Context.INPUT_METHOD_SERVICE);
+        inputMethodManager.hideSoftInputFromWindow(mListView.getWindowToken(), 0);
+    }
 }
diff --git a/src/com/android/contacts/list/ContactItemListAdapter.java b/src/com/android/contacts/list/ContactItemListAdapter.java
index 7437d6a..d7cd0c9 100644
--- a/src/com/android/contacts/list/ContactItemListAdapter.java
+++ b/src/com/android/contacts/list/ContactItemListAdapter.java
@@ -207,12 +207,12 @@
         View v;
         if (convertView == null || convertView.getTag() == null) {
             newView = true;
-            v = newView(mContext, mCursor, parent);
+            v = newView(getContext(), mCursor, parent);
         } else {
             newView = false;
             v = convertView;
         }
-        bindView(v, mContext, mCursor);
+        bindView(v, getContext(), mCursor);
         bindSectionHeader(v, realPosition, mSectionHeaderDisplayEnabled);
         return v;
     }
@@ -384,7 +384,7 @@
             if (!cursor.isNull(ContactsListActivity.SUMMARY_PRESENCE_STATUS_COLUMN_INDEX)) {
                 serverStatus =
                         cursor.getInt(ContactsListActivity.SUMMARY_PRESENCE_STATUS_COLUMN_INDEX);
-                Drawable icon = ContactPresenceIconUtil.getPresenceIcon(mContext, serverStatus);
+                Drawable icon = ContactPresenceIconUtil.getPresenceIcon(getContext(), serverStatus);
                 if (icon != null) {
                     view.setPresence(icon);
                 } else {
diff --git a/src/com/android/contacts/list/ContactsIntentResolver.java b/src/com/android/contacts/list/ContactsIntentResolver.java
index 4f06855..f0643ee 100644
--- a/src/com/android/contacts/list/ContactsIntentResolver.java
+++ b/src/com/android/contacts/list/ContactsIntentResolver.java
@@ -16,6 +16,7 @@
 
 package com.android.contacts.list;
 
+import com.android.contacts.ContactsApplicationController;
 import com.android.contacts.ContactsSearchManager;
 import com.android.contacts.JoinContactActivity;
 import com.android.contacts.R;
@@ -199,8 +200,11 @@
     private static final int QUERY_MODE_MAILTO = 1;
     private static final int QUERY_MODE_TEL = 2;
 
-    public ContactsIntentResolver(Activity context) {
+    private final ContactsApplicationController mAppController;
+
+    public ContactsIntentResolver(Activity context, ContactsApplicationController appController) {
         this.mContext = context;
+        this.mAppController = appController;
     }
 
     public void setIntent(Intent intent) {
@@ -480,11 +484,15 @@
             case MODE_PICK_PHONE:
             case MODE_STREQUENT:
             case MODE_FREQUENT: {
-                config = new DefaultContactListConfiguration(mContext);
+                config = new DefaultContactListConfiguration(mContext, mAppController);
+                break;
+            }
+            case MODE_PICK_MULTIPLE_PHONES: {
+                config = new MultiplePhonePickerConfiguration(mContext, mAppController);
                 break;
             }
             default: {
-                config = new DefaultContactListConfiguration(mContext);
+                config = new DefaultContactListConfiguration(mContext, mAppController);
                 if (!mSearchMode) {
                     config.setSectionHeaderDisplayEnabled(true);
                 }
diff --git a/src/com/android/contacts/list/DefaultContactListConfiguration.java b/src/com/android/contacts/list/DefaultContactListConfiguration.java
index 455d3e8..a681f3a 100644
--- a/src/com/android/contacts/list/DefaultContactListConfiguration.java
+++ b/src/com/android/contacts/list/DefaultContactListConfiguration.java
@@ -15,6 +15,7 @@
  */
 package com.android.contacts.list;
 
+import com.android.contacts.ContactsApplicationController;
 import com.android.contacts.ContactsListActivity;
 
 import android.content.Context;
@@ -25,8 +26,9 @@
  */
 public class DefaultContactListConfiguration extends ContactEntryListConfiguration {
 
-    public DefaultContactListConfiguration(Context context) {
-        super(context);
+    public DefaultContactListConfiguration(Context context,
+            ContactsApplicationController applicationController) {
+        super(context, applicationController);
     }
 
     @Override
@@ -37,4 +39,9 @@
         adapter.setDisplayPhotos(isPhotoLoaderEnabled());
         return adapter;
     }
+
+    @Override
+    public ContactEntryListController createController() {
+        return new DefaultContactListController(getContext(), getApplicationController());
+    }
 }
diff --git a/src/com/android/contacts/list/DefaultContactListController.java b/src/com/android/contacts/list/DefaultContactListController.java
new file mode 100644
index 0000000..4bb1416
--- /dev/null
+++ b/src/com/android/contacts/list/DefaultContactListController.java
@@ -0,0 +1,39 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.contacts.list;
+
+import com.android.contacts.ContactsApplicationController;
+
+import android.content.Context;
+
+/**
+ * Controller for the default contact list.
+ */
+public class DefaultContactListController extends ContactEntryListController {
+
+    public DefaultContactListController(Context context,
+            ContactsApplicationController appController) {
+        super(context, appController);
+    }
+
+    @Override
+    protected void onItemClick(int position, long id) {
+        // TODO instead of delegating the entire procedure to the ContactsListActivity,
+        // figure out what the specific action is and delegate the specific action.
+        getContactsApplicationController().onListItemClick(position, id);
+    }
+}
diff --git a/src/com/android/contacts/list/JoinContactListAdapter.java b/src/com/android/contacts/list/JoinContactListAdapter.java
new file mode 100644
index 0000000..58f1c97
--- /dev/null
+++ b/src/com/android/contacts/list/JoinContactListAdapter.java
@@ -0,0 +1,225 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.contacts.list;
+
+import com.android.contacts.ContactsListActivity;
+import com.android.contacts.R;
+import com.android.contacts.list.ContactItemListAdapter;
+
+import android.database.Cursor;
+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;
+
+    /**
+     * Determines whether we display a list item with the label
+     * "Show all contacts" or actually show all contacts
+     */
+    boolean mJoinModeShowAllContacts;
+
+    public JoinContactListAdapter(ContactsListActivity context) {
+        super(context);
+    }
+
+    public boolean isJoinModeShowAllContacts() {
+        return mJoinModeShowAllContacts;
+    }
+
+    public void setJoinModeShowAllContacts(boolean flag) {
+        mJoinModeShowAllContacts = 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;
+    }
+
+    @Override
+    public View getView(int position, View convertView, ViewGroup parent) {
+        if (!mDataValid) {
+            throw new IllegalStateException(
+                    "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;
+        } else {
+            showingSuggestion = false;
+            cursor = mCursor;
+        }
+
+        int realPosition = getRealPosition(position);
+        if (!cursor.moveToPosition(realPosition)) {
+            throw new IllegalStateException("couldn't move cursor to position " + position);
+        }
+
+        boolean newView;
+        View v;
+        if (convertView == null || convertView.getTag() == null) {
+            newView = true;
+            v = newView(getContext(), cursor, parent);
+        } else {
+            newView = false;
+            v = convertView;
+        }
+        bindView(v, getContext(), cursor);
+        bindSectionHeader(v, realPosition, !showingSuggestion);
+        return v;
+    }
+
+    @Override
+    public void changeCursor(Cursor cursor) {
+        if (cursor == null) {
+            setSuggestionsCursor(null);
+        }
+
+        super.changeCursor(cursor);
+    }
+    @Override
+    public int getItemViewType(int position) {
+        if (isShowAllContactsItemPosition(position)) {
+            return IGNORE_ITEM_VIEW_TYPE;
+        }
+
+        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;
+            }
+        }
+        return 0;
+    }
+
+    @Override
+    public boolean areAllItemsEnabled() {
+        return super.areAllItemsEnabled() && mSuggestionsCursorCount == 0;
+    }
+
+    @Override
+    public boolean isEnabled(int position) {
+        if (position == 0) {
+            return false;
+        }
+
+        if (mSuggestionsCursorCount > 0) {
+            return position != 0 && position != mSuggestionsCursorCount + 1;
+        }
+        return true;
+    }
+
+    @Override
+    public int getCount() {
+        if (!mDataValid) {
+            return 0;
+        }
+        int superCount = super.getCount();
+        if (mSuggestionsCursorCount != 0) {
+            // When showing suggestions, we have 2 additional list items: the "Suggestions"
+            // and "All contacts" headers.
+            return mSuggestionsCursorCount + superCount + 2;
+        }
+        return superCount;
+    }
+
+    public int getSuggestionsCursorCount() {
+        return mSuggestionsCursorCount;
+    }
+
+    @Override
+    protected int getRealPosition(int pos) {
+        if (mSuggestionsCursorCount != 0) {
+            // 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) {
+                return null;
+            }
+            return super.getItem(realPosition);
+        }
+    }
+
+    @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);
+    }
+}
\ No newline at end of file
diff --git a/src/com/android/contacts/list/JoinContactListConfiguration.java b/src/com/android/contacts/list/JoinContactListConfiguration.java
new file mode 100644
index 0000000..6c8443a
--- /dev/null
+++ b/src/com/android/contacts/list/JoinContactListConfiguration.java
@@ -0,0 +1,49 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.contacts.list;
+
+import com.android.contacts.ContactsApplicationController;
+import com.android.contacts.JoinContactActivity;
+
+import android.content.Context;
+import android.widget.ListAdapter;
+
+/**
+ * Configuration for the default contact list.
+ */
+public class JoinContactListConfiguration extends ContactEntryListConfiguration {
+
+    public JoinContactListConfiguration(Context context,
+            ContactsApplicationController applicationController) {
+        super(context, applicationController);
+    }
+
+    @Override
+    public ListAdapter createListAdapter() {
+        JoinContactListAdapter adapter =
+                new JoinContactListAdapter((JoinContactActivity)getContext());
+        adapter.setSectionHeaderDisplayEnabled(true);
+        adapter.setDisplayPhotos(true);
+        return adapter;
+    }
+
+    @Override
+    public ContactEntryListController createController() {
+
+        // TODO needs a separate controller
+        return new DefaultContactListController(getContext(), getApplicationController());
+    }
+}
diff --git a/src/com/android/contacts/list/MultiplePhonePickerAdapter.java b/src/com/android/contacts/list/MultiplePhonePickerAdapter.java
index cf322f9..fd0feeb 100644
--- a/src/com/android/contacts/list/MultiplePhonePickerAdapter.java
+++ b/src/com/android/contacts/list/MultiplePhonePickerAdapter.java
@@ -34,13 +34,15 @@
 public class MultiplePhonePickerAdapter extends ContactItemListAdapter {
 
     private final MultiplePhonePickerActivity mMultiplePhonePickerActivity;
-    private final MultiplePhoneExtraAdapter mExtraAdapter;
+    private MultiplePhoneExtraAdapter mExtraAdapter;
 
-    public MultiplePhonePickerAdapter(MultiplePhonePickerActivity multiplePhonePickerActivity,
-            MultiplePhoneExtraAdapter extraAdapter) {
+    public MultiplePhonePickerAdapter(MultiplePhonePickerActivity multiplePhonePickerActivity) {
         super(multiplePhonePickerActivity);
         this.mMultiplePhonePickerActivity = multiplePhonePickerActivity;
-        this.mExtraAdapter = extraAdapter;
+    }
+
+    public void setExtraAdapter(MultiplePhoneExtraAdapter extraAdapter) {
+        mExtraAdapter = extraAdapter;
     }
 
     @Override
diff --git a/src/com/android/contacts/list/MultiplePhonePickerConfiguration.java b/src/com/android/contacts/list/MultiplePhonePickerConfiguration.java
new file mode 100644
index 0000000..8331282
--- /dev/null
+++ b/src/com/android/contacts/list/MultiplePhonePickerConfiguration.java
@@ -0,0 +1,49 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.contacts.list;
+
+import com.android.contacts.ContactsApplicationController;
+import com.android.contacts.MultiplePhonePickerActivity;
+
+import android.content.Context;
+import android.widget.ListAdapter;
+
+/**
+ * Configuration for the multiple phone picker.
+ */
+public class MultiplePhonePickerConfiguration extends ContactEntryListConfiguration {
+
+    public MultiplePhonePickerConfiguration(Context context,
+            ContactsApplicationController applicationController) {
+        super(context, applicationController);
+    }
+
+    @Override
+    public ListAdapter createListAdapter() {
+        MultiplePhonePickerAdapter adapter =
+                new MultiplePhonePickerAdapter((MultiplePhonePickerActivity)getContext());
+        adapter.setSectionHeaderDisplayEnabled(true);
+        adapter.setDisplayPhotos(true);
+        return adapter;
+    }
+
+    @Override
+    public ContactEntryListController createController() {
+
+        // TODO this needs a separate controller
+        return new DefaultContactListController(getContext(), getApplicationController());
+    }
+}