Merge "Add explanation for where you can import contacts from"
diff --git a/AndroidManifest.xml b/AndroidManifest.xml
index d920701..05797f3 100644
--- a/AndroidManifest.xml
+++ b/AndroidManifest.xml
@@ -63,7 +63,7 @@
             </intent-filter>
         </activity>
 
-        <!-- Tab container for TwelveKeyDialer and RecentCallsList -->
+        <!-- Tab container for all tabs -->
         <activity android:name="DialtactsActivity"
             android:label="@string/launcherDialer"
             android:theme="@style/DialtactsTheme"
@@ -115,7 +115,7 @@
             </intent-filter>
         </activity>
 
-        <!-- Tab container for Activity Stream and Contacts -->
+        <!-- Tab container for all tabs -->
         <activity-alias android:name="DialtactsContactsEntryActivity"
             android:targetActivity="DialtactsActivity"
             android:label="@string/contactsList"
@@ -137,6 +137,22 @@
 
         </activity-alias>
 
+        <!-- Main launch Intent to open the Contacts app. This will open the app in its last manual
+        state. This is the state that has been explicitly set by the user (e.g. by clicking a tab).
+        States configured via other Intents (e.g. CallLog after Call) are not considered manual
+        state. At the moment, the Intent always goes to the DialtactsActivity, but this might later
+        be changed to also include sub-activities like Edit or View if they were left open -->
+
+        <activity-alias android:name="ContactsLaunchActivity"
+            android:targetActivity="DialtactsActivity"
+        >
+            <intent-filter>
+                <action android:name="android.intent.action.MAIN" />
+                <category android:name="android.intent.category.DEFAULT" />
+                <category android:name="android.intent.category.BROWSABLE" />
+            </intent-filter>
+        </activity-alias>
+
         <!-- An empty activity that presents the DialtactActivity's Favorites tab -->
         <activity-alias android:name="DialtactsFavoritesEntryActivity"
             android:targetActivity="DialtactsActivity"
@@ -223,12 +239,6 @@
                 <data android:mimeType="vnd.android.cursor.item/postal-address_v2" android:host="com.android.contacts" />
                 <data android:mimeType="vnd.android.cursor.item/postal-address" android:host="contacts" />
             </intent-filter>
-
-            <intent-filter>
-                <action android:name="com.android.contacts.action.GET_MULTIPLE_PHONES" />
-                <category android:name="android.intent.category.DEFAULT" />
-                <data android:mimeType="vnd.android.cursor.dir/phone_v2" android:host="com.android.contacts" />
-            </intent-filter>
         </activity>
 
         <!-- An activity for joining contacts -->
@@ -242,8 +252,17 @@
             </intent-filter>
         </activity>
 
+        <!-- An activity for selecting multiple phone numbers -->
+        <activity android:name="MultiplePhonePickerActivity">
+            <intent-filter>
+                <action android:name="com.android.contacts.action.GET_MULTIPLE_PHONES" />
+                <category android:name="android.intent.category.DEFAULT" />
+                <data android:mimeType="vnd.android.cursor.dir/phone_v2" android:host="com.android.contacts" />
+            </intent-filter>
+        </activity>
+
         <!-- The contacts search/filter UI -->
-        <activity android:name="ContactsListActivity$ContactsSearchActivity"
+        <activity android:name="ContactsSearchActivity"
             android:theme="@style/ContactsSearchTheme"
             android:windowSoftInputMode="stateAlwaysVisible|adjustPan"
         >
diff --git a/res/layout-finger/contacts_list_content.xml b/res/layout-finger/contacts_list_content.xml
index 4dd680f..f697612 100644
--- a/res/layout-finger/contacts_list_content.xml
+++ b/res/layout-finger/contacts_list_content.xml
@@ -23,7 +23,7 @@
         >
 
     <view
-        class="com.android.contacts.PinnedHeaderListView" 
+        class="com.android.contacts.widget.PinnedHeaderListView" 
         android:id="@android:id/list"
         android:layout_width="match_parent"
         android:layout_height="0dip"
diff --git a/res/layout-finger/contacts_list_search_results.xml b/res/layout-finger/contacts_list_search_results.xml
index 244ca80..04c1f13 100644
--- a/res/layout-finger/contacts_list_search_results.xml
+++ b/res/layout-finger/contacts_list_search_results.xml
@@ -53,7 +53,7 @@
     </LinearLayout>
 
     <view
-        class="com.android.contacts.PinnedHeaderListView"
+        class="com.android.contacts.widget.PinnedHeaderListView"
         android:id="@android:id/list"
         android:layout_width="match_parent"
         android:layout_height="match_parent"
diff --git a/res/layout/contact_detail.xml b/res/layout/contact_detail.xml
index 541ba1c..b4f8101 100644
--- a/res/layout/contact_detail.xml
+++ b/res/layout/contact_detail.xml
@@ -14,7 +14,7 @@
      limitations under the License.
 -->
 
-<com.android.contacts.views.detail.ContactDetailView xmlns:android="http://schemas.android.com/apk/res/android"
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
     android:id="@+id/contact_details"
     android:orientation="vertical"
     android:layout_width="match_parent"
@@ -49,5 +49,5 @@
         />
     </ScrollView>
             
-</com.android.contacts.views.detail.ContactDetailView>
+</LinearLayout>
 
diff --git a/src/android/app/patterns/AsyncTaskLoader.java b/src/android/app/patterns/AsyncTaskLoader.java
new file mode 100644
index 0000000..01b3e24
--- /dev/null
+++ b/src/android/app/patterns/AsyncTaskLoader.java
@@ -0,0 +1,61 @@
+/*
+ * 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 android.app.patterns;
+
+import android.content.Context;
+import android.os.AsyncTask;
+
+/**
+ * Abstract Loader that provides an {@link AsyncTask} to do the work.
+ * 
+ * @param <D> the data type to be loaded.
+ */
+public abstract class AsyncTaskLoader<D> extends Loader<D> {
+    final class LoadListTask extends AsyncTask<Void, Void, D> {
+        /* Runs on a worker thread */
+        @Override
+        protected D doInBackground(Void... params) {
+            return AsyncTaskLoader.this.loadInBackground();
+        }
+
+        /* Runs on the UI thread */
+        @Override
+        protected void onPostExecute(D data) {
+            AsyncTaskLoader.this.onLoadComplete(data);
+        }
+    }
+
+    public AsyncTaskLoader(Context context) {
+        super(context);
+    }
+
+    /**
+     * Called on a worker thread to perform the actual load. Implementions should not deliver the
+     * results directly, but should return them from this this method and deliver them from
+     * {@link #onPostExecute()}
+     *
+     * @return the result of the load
+     */
+    protected abstract D loadInBackground();
+
+    /**
+     * Called on the UI thread with the result of the load.
+     *
+     * @param data the result of the load
+     */
+    protected abstract void onLoadComplete(D data);
+}
diff --git a/src/android/app/patterns/CursorLoader.java b/src/android/app/patterns/CursorLoader.java
new file mode 100644
index 0000000..a1f8132
--- /dev/null
+++ b/src/android/app/patterns/CursorLoader.java
@@ -0,0 +1,115 @@
+/*
+ * Copyright (C) 2010 Google Inc.
+ *
+ * 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 android.app.patterns;
+
+import android.content.Context;
+import android.database.Cursor;
+import android.net.Uri;
+
+public class CursorLoader extends AsyncTaskLoader<Cursor> {
+    Cursor mCursor;
+    ForceLoadContentObserver mObserver;
+    boolean mStopped;
+    Uri mUri;
+    String[] mProjection;
+    String mSelection;
+    String[] mSelectionArgs;
+    String mSortOrder;
+
+    /* Runs on a worker thread */
+    @Override
+    protected Cursor loadInBackground() {
+        Cursor cursor = getContext().getContentResolver().query(mUri, mProjection, mSelection,
+                mSelectionArgs, mSortOrder);
+        // Ensure the cursor window is filled
+        if (cursor != null) {
+            cursor.getCount();
+            cursor.registerContentObserver(mObserver);
+        }
+        return cursor;
+    }
+
+    /* Runs on the UI thread */
+    @Override
+    protected void onLoadComplete(Cursor cursor) {
+        if (mStopped) {
+            // An async query came in while the loader is stopped
+            cursor.close();
+            return;
+        }
+        mCursor = cursor;
+        deliverResult(cursor);
+    }
+
+    public CursorLoader(Context context, Uri uri, String[] projection, String selection,
+            String[] selectionArgs, String sortOrder) {
+        super(context);
+        mObserver = new ForceLoadContentObserver();
+        mUri = uri;
+        mProjection = projection;
+        mSelection = selection;
+        mSelectionArgs = selectionArgs;
+        mSortOrder = sortOrder;
+    }
+
+    /**
+     * Starts an asynchronous load of the contacts list data. When the result is ready the callbacks
+     * will be called on the UI thread. If a previous load has been completed and is still valid
+     * the result may be passed to the callbacks immediately. 
+     *
+     * Must be called from the UI thread
+     */
+    @Override
+    public void startLoading() {
+        mStopped = false;
+
+        if (mCursor != null) {
+            deliverResult(mCursor);
+        } else {
+            forceLoad();
+        }
+    }
+
+    /**
+     * Force an asynchronous load. Unlike {@link #startLoading()} this will ignore a previously
+     * loaded data set and load a new one.
+     */
+    @Override
+    public void forceLoad() {
+        new LoadListTask().execute((Void[]) null);
+    }
+
+    /**
+     * Must be called from the UI thread
+     */
+    @Override
+    public void stopLoading() {
+        if (mCursor != null && !mCursor.isClosed()) {
+            mCursor.close();
+            mCursor = null;
+        }
+
+        // Make sure that any outstanding loads clean themselves up properly
+        mStopped = true;
+    }
+
+    @Override
+    public void destroy() {
+        // Ensure the loader is stopped
+        stopLoading();
+    }
+}
diff --git a/src/com/android/contacts/mvcframework/Loader.java b/src/android/app/patterns/Loader.java
similarity index 71%
rename from src/com/android/contacts/mvcframework/Loader.java
rename to src/android/app/patterns/Loader.java
index fab2f46..dfc35e9 100644
--- a/src/com/android/contacts/mvcframework/Loader.java
+++ b/src/android/app/patterns/Loader.java
@@ -14,7 +14,7 @@
  * limitations under the License
  */
 
-package com.android.contacts.mvcframework;
+package android.app.patterns;
 
 import android.content.Context;
 import android.database.ContentObserver;
@@ -42,37 +42,60 @@
     }
 
     public interface OnLoadCompleteListener<D> {
-        public void onLoadComplete(int id, D data);
+        /**
+         * Called on the thread that created the Loader when the load is complete.
+         *
+         * @param loader the loader that completed the load
+         * @param data the result of the load
+         */
+        public void onLoadComplete(Loader loader, D data);
     }
 
+    /**
+     * Sends the result of the load to the register listener.
+     *
+     * @param data the result of the load
+     */
     protected void deliverResult(D data) {
         if (mListener != null) {
-            mListener.onLoadComplete(mId, data);
-
+            mListener.onLoadComplete(this, data);
         }
     }
 
+    /**
+     * Stores away the application context associated with context. Since Loaders can be used
+     * across multiple activities it's dangerous to store the context directly.
+     *
+     * @param context used to retrieve the application context.
+     */
     public Loader(Context context) {
         mContext = context.getApplicationContext();
     }
 
     /**
-     * @return an application context
+     * @return an application context retrieved from the Context passed to the constructor.
      */
     public Context getContext() {
         return mContext;
     }
 
     /**
+     * @return the ID of this loader
+     */
+    public int getId() {
+        return mId;
+    }
+
+    /**
      * Registers a class that will receive callbacks when a load is complete. The callbacks will
      * be called on the UI thread so it's safe to pass the results to widgets.
-     *
+     * 
      * Must be called from the UI thread
      */
     public void registerListener(int id, OnLoadCompleteListener<D> listener) {
-//        if (mListener != null) {
- //           throw new IllegalStateException("There is already a listener registered");
-  //      }
+        if (mListener != null) {
+            throw new IllegalStateException("There is already a listener registered");
+        }
         mListener = listener;
         mId = id;
     }
@@ -95,7 +118,7 @@
      * will be called on the UI thread. If a previous load has been completed and is still valid
      * the result may be passed to the callbacks immediately. The loader will monitor the source of
      * the data set and may deliver future callbacks if the source changes. Calling
-     * {@link #stopLoading} will stop the delivery of callbacks.
+     * {@link #stopLoading} will stop the delivery of callbacks. 
      *
      * Must be called from the UI thread
      */
@@ -108,11 +131,15 @@
     public abstract void forceLoad();
 
     /**
-     * Stops delivery of updates.
+     * Stops delivery of updates until the next time {@link #startLoading()} is called
+     *
+     * Must be called from the UI thread
      */
     public abstract void stopLoading();
 
     /**
+     * Destroys the loader and frees it's resources, making it unusable.
+     *
      * Must be called from the UI thread
      */
     public abstract void destroy();
diff --git a/src/com/android/contacts/mvcframework/LoaderActivity.java b/src/android/app/patterns/LoaderActivity.java
similarity index 69%
rename from src/com/android/contacts/mvcframework/LoaderActivity.java
rename to src/android/app/patterns/LoaderActivity.java
index 0e74d62..664a8bc 100644
--- a/src/com/android/contacts/mvcframework/LoaderActivity.java
+++ b/src/android/app/patterns/LoaderActivity.java
@@ -14,7 +14,7 @@
  * limitations under the License
  */
 
-package com.android.contacts.mvcframework;
+package android.app.patterns;
 
 
 import android.app.Activity;
@@ -28,40 +28,61 @@
  */
 public abstract class LoaderActivity<D> extends Activity implements
         Loader.OnLoadCompleteListener<D> {
+    private boolean mStarted = false;
+
     static final class LoaderInfo {
         public Bundle args;
         public Loader loader;
     }
     private HashMap<Integer, LoaderInfo> mLoaders;
+    private HashMap<Integer, LoaderInfo> mInactiveLoaders;
 
     /**
      * Registers a loader with this activity, registers the callbacks on it, and starts it loading.
+     * If a loader with the same id has previously been started it will automatically be destroyed
+     * when the new loader completes it's work. The callback will be delivered before the old loader
+     * is destroyed.
      */
     protected void startLoading(int id, Bundle args) {
         LoaderInfo info = mLoaders.get(id);
-        Loader loader;
         if (info != null) {
-            loader = info.loader;
-            if (loader != null) {
-                loader.unregisterListener(this);
-                loader.destroy();
-                info.loader = null;
-            }
-        } else {
-            info = new LoaderInfo();
-            info.args = args;
+            // Keep track of the previous instance of this loader so we can destroy
+            // it when the new one completes.
+            mInactiveLoaders.put(id, info);
         }
+        info = new LoaderInfo();
+        info.args = args;
         mLoaders.put(id, info);
-        loader = onCreateLoader(id, args);
-        loader.registerListener(id, this);
-        loader.startLoading();
+        Loader loader = onCreateLoader(id, args);
         info.loader = loader;
+        if (mStarted) {
+            // The activity will start all existing loaders in it's onStart(), so only start them
+            // here if we're past that point of the activitiy's life cycle
+            loader.registerListener(id, this);
+            loader.startLoading();
+        }
     }
 
     protected abstract Loader onCreateLoader(int id, Bundle args);
     protected abstract void onInitializeLoaders();
+    protected abstract void onLoadFinished(Loader loader, D data);
 
-    public abstract void onLoadComplete(int id, D data);
+    public final void onLoadComplete(Loader loader, D data) {
+        // Notify of the new data so the app can switch out the old data before
+        // we try to destroy it.
+        onLoadFinished(loader, data);
+
+        // Look for an inactive loader and destroy it if found
+        int id = loader.getId();
+        LoaderInfo info = mInactiveLoaders.get(id);
+        if (info != null) {
+            Loader oldLoader = info.loader;
+            if (oldLoader != null) {
+                oldLoader.destroy();
+            }
+            mInactiveLoaders.remove(id);
+        }
+    }
 
     @Override
     public void onCreate(Bundle savedState) {
@@ -75,6 +96,9 @@
                 onInitializeLoaders();
             }
         }
+        if (mInactiveLoaders == null) {
+            mInactiveLoaders = new HashMap<Integer, LoaderInfo>();
+        }
     }
 
     @Override
@@ -90,11 +114,12 @@
             if (loader == null) {
                loader = onCreateLoader(id, info.args);
                info.loader = loader;
-            } else {
-                loader.registerListener(id, this);
             }
+            loader.registerListener(id, this);
             loader.startLoading();
         }
+
+        mStarted = true;
     }
 
     @Override
@@ -116,6 +141,8 @@
 //                loader.stopLoading();
 //            }
         }
+
+        mStarted = false;
     }
 
     @Override
diff --git a/src/com/android/contacts/ContactListEmptyView.java b/src/com/android/contacts/ContactListEmptyView.java
index 58573f1..40d5152 100644
--- a/src/com/android/contacts/ContactListEmptyView.java
+++ b/src/com/android/contacts/ContactListEmptyView.java
@@ -21,7 +21,6 @@
 import android.content.ContentResolver;
 import android.content.Context;
 import android.content.IContentService;
-import android.content.Intent;
 import android.os.RemoteException;
 import android.provider.ContactsContract;
 import android.telephony.TelephonyManager;
@@ -46,7 +45,7 @@
         empty.setVisibility(GONE);
     }
 
-    protected void show(boolean searchMode, boolean displayOnlyPhones,
+    public void show(boolean searchMode, boolean displayOnlyPhones,
             boolean isFavoritesMode, boolean isQueryMode, boolean isShortcutAction,
             boolean isMultipleSelectionEnabled, boolean showSelectedOnly) {
         if (searchMode) {
diff --git a/src/com/android/contacts/ContactListItemView.java b/src/com/android/contacts/ContactListItemView.java
index db2bb48..9be3fba 100644
--- a/src/com/android/contacts/ContactListItemView.java
+++ b/src/com/android/contacts/ContactListItemView.java
@@ -146,6 +146,7 @@
     public void setOnCheckBoxClickListener(OnClickListener checkBoxClickListener) {
         mCheckBoxClickListener = checkBoxClickListener;
     }
+
     @Override
     protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
         // We will match parent's width and wrap content vertically, but make sure
@@ -470,6 +471,24 @@
     }
 
     /**
+     * Removes the photo view.  Should not be needed once we start handling different
+     * types of views as different types of views from the List's perspective.
+     *
+     * @deprecated
+     */
+    @Deprecated
+    public void removePhotoView() {
+        if (mPhotoView != null) {
+            removeView(mPhotoView);
+            mPhotoView = null;
+        }
+        if (mQuickContact != null) {
+            removeView(mQuickContact);
+            mQuickContact = null;
+        }
+    }
+
+    /**
      * Returns the text view for the contact name, creating it if necessary.
      */
     public TextView getNameTextView() {
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 533888f..8303414 100644
--- a/src/com/android/contacts/ContactsListActivity.java
+++ b/src/com/android/contacts/ContactsListActivity.java
@@ -17,6 +17,10 @@
 package com.android.contacts;
 
 import com.android.contacts.TextHighlightingAnimation.TextWithHighlighting;
+import com.android.contacts.list.ContactEntryListAdapter;
+import com.android.contacts.list.ContactEntryListConfiguration;
+import com.android.contacts.list.ContactItemListAdapter;
+import com.android.contacts.list.ContactsIntentResolver;
 import com.android.contacts.model.ContactsSource;
 import com.android.contacts.model.Sources;
 import com.android.contacts.ui.ContactsPreferences;
@@ -30,7 +34,6 @@
 import android.app.AlertDialog;
 import android.app.Dialog;
 import android.app.ListActivity;
-import android.app.ProgressDialog;
 import android.app.SearchManager;
 import android.content.AsyncQueryHandler;
 import android.content.ContentResolver;
@@ -50,7 +53,6 @@
 import android.graphics.Bitmap;
 import android.graphics.BitmapFactory;
 import android.graphics.Canvas;
-import android.graphics.Color;
 import android.graphics.Paint;
 import android.graphics.Rect;
 import android.graphics.Typeface;
@@ -75,20 +77,16 @@
 import android.provider.ContactsContract.RawContacts;
 import android.provider.ContactsContract.SearchSnippetColumns;
 import android.provider.ContactsContract.CommonDataKinds.Email;
-import android.provider.ContactsContract.CommonDataKinds.Nickname;
-import android.provider.ContactsContract.CommonDataKinds.Organization;
 import android.provider.ContactsContract.CommonDataKinds.Phone;
 import android.provider.ContactsContract.CommonDataKinds.Photo;
 import android.provider.ContactsContract.CommonDataKinds.StructuredPostal;
 import android.provider.ContactsContract.Intents.Insert;
-import android.provider.ContactsContract.Intents.UI;
 import android.telephony.TelephonyManager;
 import android.text.Editable;
 import android.text.Html;
 import android.text.TextUtils;
 import android.text.TextWatcher;
 import android.util.Log;
-import android.util.SparseIntArray;
 import android.view.ContextMenu;
 import android.view.ContextThemeWrapper;
 import android.view.KeyEvent;
@@ -99,34 +97,25 @@
 import android.view.MotionEvent;
 import android.view.View;
 import android.view.ViewGroup;
-import android.view.ViewStub;
 import android.view.ContextMenu.ContextMenuInfo;
 import android.view.View.OnClickListener;
 import android.view.View.OnFocusChangeListener;
 import android.view.View.OnTouchListener;
-import android.view.animation.AnimationUtils;
 import android.view.inputmethod.EditorInfo;
 import android.view.inputmethod.InputMethodManager;
 import android.widget.AbsListView;
 import android.widget.AdapterView;
 import android.widget.ArrayAdapter;
-import android.widget.BaseAdapter;
 import android.widget.Button;
-import android.widget.CheckBox;
-import android.widget.CursorAdapter;
 import android.widget.Filter;
-import android.widget.ImageView;
+import android.widget.ListAdapter;
 import android.widget.ListView;
-import android.widget.QuickContactBadge;
-import android.widget.SectionIndexer;
 import android.widget.TextView;
 import android.widget.Toast;
 import android.widget.AbsListView.OnScrollListener;
 
 import java.lang.ref.WeakReference;
 import java.util.ArrayList;
-import java.util.HashSet;
-import java.util.Iterator;
 import java.util.List;
 import java.util.Random;
 
@@ -136,11 +125,7 @@
 @SuppressWarnings("deprecation")
 public class ContactsListActivity extends ListActivity implements View.OnCreateContextMenuListener,
         View.OnClickListener, View.OnKeyListener, TextWatcher, TextView.OnEditorActionListener,
-        OnFocusChangeListener, OnTouchListener {
-
-    public static class ContactsSearchActivity extends ContactsListActivity {
-
-    }
+        OnFocusChangeListener, OnTouchListener, OnScrollListener, ContactsApplicationController {
 
     private static final String TAG = "ContactsListActivity";
 
@@ -162,7 +147,7 @@
     private static final int SUBACTIVITY_VIEW_CONTACT = 2;
     private static final int SUBACTIVITY_DISPLAY_GROUP = 3;
     private static final int SUBACTIVITY_SEARCH = 4;
-    private static final int SUBACTIVITY_FILTER = 5;
+    protected static final int SUBACTIVITY_FILTER = 5;
 
     private static final int TEXT_HIGHLIGHTING_ANIMATION_DURATION = 350;
 
@@ -172,87 +157,87 @@
             buildSectionIndexerUri(Contacts.CONTENT_URI);
 
     /** Mask for picker mode */
-    static final int MODE_MASK_PICKER = 0x80000000;
+    public static final int MODE_MASK_PICKER = 0x80000000;
     /** Mask for no presence mode */
-    static final int MODE_MASK_NO_PRESENCE = 0x40000000;
+    public static final int MODE_MASK_NO_PRESENCE = 0x40000000;
     /** Mask for enabling list filtering */
-    static final int MODE_MASK_NO_FILTER = 0x20000000;
+    public static final int MODE_MASK_NO_FILTER = 0x20000000;
     /** Mask for having a "create new contact" header in the list */
-    static final int MODE_MASK_CREATE_NEW = 0x10000000;
+    public static final int MODE_MASK_CREATE_NEW = 0x10000000;
     /** Mask for showing photos in the list */
-    static final int MODE_MASK_SHOW_PHOTOS = 0x08000000;
+    public static final int MODE_MASK_SHOW_PHOTOS = 0x08000000;
     /** Mask for hiding additional information e.g. primary phone number in the list */
-    static final int MODE_MASK_NO_DATA = 0x04000000;
+    public static final int MODE_MASK_NO_DATA = 0x04000000;
     /** Mask for showing a call button in the list */
-    static final int MODE_MASK_SHOW_CALL_BUTTON = 0x02000000;
+    public static final int MODE_MASK_SHOW_CALL_BUTTON = 0x02000000;
     /** Mask to disable quickcontact (images will show as normal images) */
-    static final int MODE_MASK_DISABLE_QUIKCCONTACT = 0x01000000;
+    public static final int MODE_MASK_DISABLE_QUIKCCONTACT = 0x01000000;
     /** Mask to show the total number of contacts at the top */
-    static final int MODE_MASK_SHOW_NUMBER_OF_CONTACTS = 0x00800000;
+    public static final int MODE_MASK_SHOW_NUMBER_OF_CONTACTS = 0x00800000;
 
     /** Unknown mode */
-    static final int MODE_UNKNOWN = 0;
+    public static final int MODE_UNKNOWN = 0;
     /** Default mode */
-    static final int MODE_DEFAULT = 4 | MODE_MASK_SHOW_PHOTOS | MODE_MASK_SHOW_NUMBER_OF_CONTACTS;
+    public static final int MODE_DEFAULT = 4 | MODE_MASK_SHOW_PHOTOS | MODE_MASK_SHOW_NUMBER_OF_CONTACTS;
     /** Custom mode */
-    static final int MODE_CUSTOM = 8;
+    public static final int MODE_CUSTOM = 8;
     /** Show all starred contacts */
-    static final int MODE_STARRED = 20 | MODE_MASK_SHOW_PHOTOS;
+    public static final int MODE_STARRED = 20 | MODE_MASK_SHOW_PHOTOS;
     /** Show frequently contacted contacts */
-    static final int MODE_FREQUENT = 30 | MODE_MASK_SHOW_PHOTOS;
+    public static final int MODE_FREQUENT = 30 | MODE_MASK_SHOW_PHOTOS;
     /** Show starred and the frequent */
-    static final int MODE_STREQUENT = 35 | MODE_MASK_SHOW_PHOTOS | MODE_MASK_SHOW_CALL_BUTTON;
+    public static final int MODE_STREQUENT = 35 | MODE_MASK_SHOW_PHOTOS | MODE_MASK_SHOW_CALL_BUTTON;
     /** Show all contacts and pick them when clicking */
-    static final int MODE_PICK_CONTACT = 40 | MODE_MASK_PICKER | MODE_MASK_SHOW_PHOTOS
+    public static final int MODE_PICK_CONTACT = 40 | MODE_MASK_PICKER | MODE_MASK_SHOW_PHOTOS
             | MODE_MASK_DISABLE_QUIKCCONTACT;
     /** Show all contacts as well as the option to create a new one */
-    static final int MODE_PICK_OR_CREATE_CONTACT = 42 | MODE_MASK_PICKER | MODE_MASK_CREATE_NEW
+    public static final int MODE_PICK_OR_CREATE_CONTACT = 42 | MODE_MASK_PICKER | MODE_MASK_CREATE_NEW
             | MODE_MASK_SHOW_PHOTOS | MODE_MASK_DISABLE_QUIKCCONTACT;
     /** Show all people through the legacy provider and pick them when clicking */
-    static final int MODE_LEGACY_PICK_PERSON = 43 | MODE_MASK_PICKER
+    public static final int MODE_LEGACY_PICK_PERSON = 43 | MODE_MASK_PICKER
             | MODE_MASK_DISABLE_QUIKCCONTACT;
     /** Show all people through the legacy provider as well as the option to create a new one */
-    static final int MODE_LEGACY_PICK_OR_CREATE_PERSON = 44 | MODE_MASK_PICKER
+    public static final int MODE_LEGACY_PICK_OR_CREATE_PERSON = 44 | MODE_MASK_PICKER
             | MODE_MASK_CREATE_NEW | MODE_MASK_DISABLE_QUIKCCONTACT;
     /** Show all contacts and pick them when clicking, and allow creating a new contact */
-    static final int MODE_INSERT_OR_EDIT_CONTACT = 45 | MODE_MASK_PICKER | MODE_MASK_CREATE_NEW
+    public static final int MODE_INSERT_OR_EDIT_CONTACT = 45 | MODE_MASK_PICKER | MODE_MASK_CREATE_NEW
             | MODE_MASK_SHOW_PHOTOS | MODE_MASK_DISABLE_QUIKCCONTACT;
     /** Show all phone numbers and pick them when clicking */
-    static final int MODE_PICK_PHONE = 50 | MODE_MASK_PICKER | MODE_MASK_NO_PRESENCE;
+    public static final int MODE_PICK_PHONE = 50 | MODE_MASK_PICKER | MODE_MASK_NO_PRESENCE;
     /** Show all phone numbers through the legacy provider and pick them when clicking */
-    static final int MODE_LEGACY_PICK_PHONE =
+    public static final int MODE_LEGACY_PICK_PHONE =
             51 | MODE_MASK_PICKER | MODE_MASK_NO_PRESENCE | MODE_MASK_NO_FILTER;
     /** Show all postal addresses and pick them when clicking */
-    static final int MODE_PICK_POSTAL =
+    public static final int MODE_PICK_POSTAL =
             55 | MODE_MASK_PICKER | MODE_MASK_NO_PRESENCE | MODE_MASK_NO_FILTER;
     /** Show all postal addresses and pick them when clicking */
-    static final int MODE_LEGACY_PICK_POSTAL =
+    public static final int MODE_LEGACY_PICK_POSTAL =
             56 | MODE_MASK_PICKER | MODE_MASK_NO_PRESENCE | MODE_MASK_NO_FILTER;
-    static final int MODE_GROUP = 57 | MODE_MASK_SHOW_PHOTOS;
+    public static final int MODE_GROUP = 57 | MODE_MASK_SHOW_PHOTOS;
     /** Run a search query */
-    static final int MODE_QUERY = 60 | MODE_MASK_SHOW_PHOTOS | MODE_MASK_NO_FILTER
+    public static final int MODE_QUERY = 60 | MODE_MASK_SHOW_PHOTOS | MODE_MASK_NO_FILTER
             | MODE_MASK_SHOW_NUMBER_OF_CONTACTS;
     /** Run a search query in PICK mode, but that still launches to VIEW */
-    static final int MODE_QUERY_PICK_TO_VIEW = 65 | MODE_MASK_SHOW_PHOTOS | MODE_MASK_PICKER
+    public static final int MODE_QUERY_PICK_TO_VIEW = 65 | MODE_MASK_SHOW_PHOTOS | MODE_MASK_PICKER
             | MODE_MASK_SHOW_NUMBER_OF_CONTACTS;
 
     /** Run a search query in a PICK mode */
-    static final int MODE_QUERY_PICK = 75 | MODE_MASK_SHOW_PHOTOS | MODE_MASK_NO_FILTER
+    public static final int MODE_QUERY_PICK = 75 | MODE_MASK_SHOW_PHOTOS | MODE_MASK_NO_FILTER
             | MODE_MASK_PICKER | MODE_MASK_DISABLE_QUIKCCONTACT | MODE_MASK_SHOW_NUMBER_OF_CONTACTS;
 
     /** Run a search query in a PICK_PHONE mode */
-    static final int MODE_QUERY_PICK_PHONE = 80 | MODE_MASK_NO_FILTER | MODE_MASK_PICKER
+    public static final int MODE_QUERY_PICK_PHONE = 80 | MODE_MASK_NO_FILTER | MODE_MASK_PICKER
             | MODE_MASK_SHOW_NUMBER_OF_CONTACTS;
 
     /** Run a search query in PICK mode, but that still launches to EDIT */
-    static final int MODE_QUERY_PICK_TO_EDIT = 85 | MODE_MASK_NO_FILTER | MODE_MASK_SHOW_PHOTOS
+    public static final int MODE_QUERY_PICK_TO_EDIT = 85 | MODE_MASK_NO_FILTER | MODE_MASK_SHOW_PHOTOS
             | MODE_MASK_PICKER | MODE_MASK_SHOW_NUMBER_OF_CONTACTS;
 
     /**
      * Show all phone numbers and do multiple pick when clicking. This mode has phone filtering
      * feature, but doesn't support 'search for all contacts'.
      */
-    static final int MODE_PICK_MULTIPLE_PHONES = 80 | MODE_MASK_PICKER
+    public static final int MODE_PICK_MULTIPLE_PHONES = 80 | MODE_MASK_PICKER
             | MODE_MASK_NO_PRESENCE | MODE_MASK_SHOW_PHOTOS | MODE_MASK_DISABLE_QUIKCCONTACT;
 
     /**
@@ -314,20 +299,20 @@
         PeopleColumns.TIMES_CONTACTED,      // 5
         People.PRESENCE_STATUS,             // 6
     };
-    static final int SUMMARY_ID_COLUMN_INDEX = 0;
-    static final int SUMMARY_DISPLAY_NAME_PRIMARY_COLUMN_INDEX = 1;
-    static final int SUMMARY_DISPLAY_NAME_ALTERNATIVE_COLUMN_INDEX = 2;
+    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;
     static final int SUMMARY_SORT_KEY_PRIMARY_COLUMN_INDEX = 3;
-    static final int SUMMARY_STARRED_COLUMN_INDEX = 4;
+    public static final int SUMMARY_STARRED_COLUMN_INDEX = 4;
     static final int SUMMARY_TIMES_CONTACTED_COLUMN_INDEX = 5;
-    static final int SUMMARY_PRESENCE_STATUS_COLUMN_INDEX = 6;
-    static final int SUMMARY_PHOTO_ID_COLUMN_INDEX = 7;
-    static final int SUMMARY_LOOKUP_KEY_COLUMN_INDEX = 8;
-    static final int SUMMARY_PHONETIC_NAME_COLUMN_INDEX = 9;
-    static final int SUMMARY_HAS_PHONE_COLUMN_INDEX = 10;
-    static final int SUMMARY_SNIPPET_MIMETYPE_COLUMN_INDEX = 11;
-    static final int SUMMARY_SNIPPET_DATA1_COLUMN_INDEX = 12;
-    static final int SUMMARY_SNIPPET_DATA4_COLUMN_INDEX = 13;
+    public static final int SUMMARY_PRESENCE_STATUS_COLUMN_INDEX = 6;
+    public static final int SUMMARY_PHOTO_ID_COLUMN_INDEX = 7;
+    public static final int SUMMARY_LOOKUP_KEY_COLUMN_INDEX = 8;
+    public static final int SUMMARY_PHONETIC_NAME_COLUMN_INDEX = 9;
+    public static final int SUMMARY_HAS_PHONE_COLUMN_INDEX = 10;
+    public static final int SUMMARY_SNIPPET_MIMETYPE_COLUMN_INDEX = 11;
+    public static final int SUMMARY_SNIPPET_DATA1_COLUMN_INDEX = 12;
+    public static final int SUMMARY_SNIPPET_DATA4_COLUMN_INDEX = 13;
 
     static final String[] PHONES_PROJECTION = new String[] {
         Phone._ID, //0
@@ -346,14 +331,14 @@
         Phones.NUMBER, //3
         People.DISPLAY_NAME, // 4
     };
-    static final int PHONE_ID_COLUMN_INDEX = 0;
-    static final int PHONE_TYPE_COLUMN_INDEX = 1;
-    static final int PHONE_LABEL_COLUMN_INDEX = 2;
-    static final int PHONE_NUMBER_COLUMN_INDEX = 3;
-    static final int PHONE_DISPLAY_NAME_COLUMN_INDEX = 4;
-    static final int PHONE_CONTACT_ID_COLUMN_INDEX = 5;
+    public static final int PHONE_ID_COLUMN_INDEX = 0;
+    public static final int PHONE_TYPE_COLUMN_INDEX = 1;
+    public static final int PHONE_LABEL_COLUMN_INDEX = 2;
+    public static final int PHONE_NUMBER_COLUMN_INDEX = 3;
+    public static final int PHONE_DISPLAY_NAME_COLUMN_INDEX = 4;
+    public static final int PHONE_CONTACT_ID_COLUMN_INDEX = 5;
     static final int PHONE_SORT_KEY_PRIMARY_COLUMN_INDEX = 6;
-    static final int PHONE_PHOTO_ID_COLUMN_INDEX = 7;
+    public static final int PHONE_PHOTO_ID_COLUMN_INDEX = 7;
 
     static final String[] POSTALS_PROJECTION = new String[] {
         StructuredPostal._ID, //0
@@ -376,33 +361,29 @@
     };
 
     static final int POSTAL_ID_COLUMN_INDEX = 0;
-    static final int POSTAL_TYPE_COLUMN_INDEX = 1;
-    static final int POSTAL_LABEL_COLUMN_INDEX = 2;
-    static final int POSTAL_ADDRESS_COLUMN_INDEX = 3;
-    static final int POSTAL_DISPLAY_NAME_COLUMN_INDEX = 4;
+    public static final int POSTAL_TYPE_COLUMN_INDEX = 1;
+    public static final int POSTAL_LABEL_COLUMN_INDEX = 2;
+    public static final int POSTAL_ADDRESS_COLUMN_INDEX = 3;
+    public static final int POSTAL_DISPLAY_NAME_COLUMN_INDEX = 4;
 
-    private static final int QUERY_TOKEN = 42;
+    protected static final int QUERY_TOKEN = 42;
 
     static final String KEY_PICKER_MODE = "picker_mode";
 
-    private static final String TEL_SCHEME = "tel";
-    private static final String CONTENT_SCHEME = "content";
+    public ContactEntryListAdapter mAdapter;
+    public ContactListEmptyView mEmptyView;
 
-    private ContactItemListAdapter mAdapter;
-    private ContactListEmptyView mEmptyView;
-
-    int mMode = MODE_DEFAULT;
-
+    public int mMode = MODE_DEFAULT;
     private boolean mRunQueriesSynchronously;
-    private QueryHandler mQueryHandler;
+    protected QueryHandler mQueryHandler;
     private boolean mJustCreated;
     private boolean mSyncEnabled;
     Uri mSelectedContactUri;
 
 //    private boolean mDisplayAll;
-    private boolean mDisplayOnlyPhones;
+    public boolean mDisplayOnlyPhones;
 
-    private Uri mGroupUri;
+    private String mGroupName;
 
     private ArrayList<Long> mWritableRawContactIds = new ArrayList<Long>();
     private int  mWritableSourcesCnt;
@@ -413,29 +394,29 @@
      */
     private Parcelable mListState = null;
 
-    private String mShortcutAction;
+    public String mShortcutAction;
 
     /**
      * Internal query type when in mode {@link #MODE_QUERY_PICK_TO_VIEW}.
      */
-    private int mQueryMode = QUERY_MODE_NONE;
+    public int mQueryMode = QUERY_MODE_NONE;
 
-    private static final int QUERY_MODE_NONE = -1;
+    public static final int QUERY_MODE_NONE = -1;
     private static final int QUERY_MODE_MAILTO = 1;
     private static final int QUERY_MODE_TEL = 2;
 
-    private int mProviderStatus = ProviderStatus.STATUS_NORMAL;
+    public int mProviderStatus = ProviderStatus.STATUS_NORMAL;
 
-    private boolean mSearchMode;
-    private boolean mSearchResultsMode;
-    private boolean mShowNumberOfContacts;
+    public boolean mSearchMode;
+    public boolean mSearchResultsMode;
+    public boolean mShowNumberOfContacts;
 
-    private boolean mShowSearchSnippets;
+    public boolean mShowSearchSnippets;
     private boolean mSearchInitiated;
 
     private String mInitialFilter;
 
-    private static final String CLAUSE_ONLY_VISIBLE = Contacts.IN_VISIBLE_GROUP + "=1";
+    protected static final String CLAUSE_ONLY_VISIBLE = Contacts.IN_VISIBLE_GROUP + "=1";
     private static final String CLAUSE_ONLY_PHONES = Contacts.HAS_PHONE_NUMBER + "=1";
 
 
@@ -443,63 +424,12 @@
     private static final int CONTACTS_ID = 1001;
     private static final UriMatcher sContactsIdMatcher;
 
-    private ContactPhotoLoader mPhotoLoader;
+    public ContactPhotoLoader mPhotoLoader;
 
     final String[] sLookupProjection = new String[] {
             Contacts.LOOKUP_KEY
     };
 
-    /**
-     * User selected phone number and id in MODE_PICK_MULTIPLE_PHONES mode.
-     */
-    private UserSelection mUserSelection = new UserSelection(null, null);
-
-    /**
-     * The adapter for the phone numbers, used in MODE_PICK_MULTIPLE_PHONES mode.
-     */
-    private PhoneNumberAdapter mPhoneNumberAdapter = new PhoneNumberAdapter(this, null);
-
-    private static int[] CHIP_COLOR_ARRAY = {
-        R.drawable.appointment_indicator_leftside_1,
-        R.drawable.appointment_indicator_leftside_2,
-        R.drawable.appointment_indicator_leftside_3,
-        R.drawable.appointment_indicator_leftside_4,
-        R.drawable.appointment_indicator_leftside_5,
-        R.drawable.appointment_indicator_leftside_6,
-        R.drawable.appointment_indicator_leftside_7,
-        R.drawable.appointment_indicator_leftside_8,
-        R.drawable.appointment_indicator_leftside_9,
-        R.drawable.appointment_indicator_leftside_10,
-        R.drawable.appointment_indicator_leftside_11,
-        R.drawable.appointment_indicator_leftside_12,
-        R.drawable.appointment_indicator_leftside_13,
-        R.drawable.appointment_indicator_leftside_14,
-        R.drawable.appointment_indicator_leftside_15,
-        R.drawable.appointment_indicator_leftside_16,
-        R.drawable.appointment_indicator_leftside_17,
-        R.drawable.appointment_indicator_leftside_18,
-        R.drawable.appointment_indicator_leftside_19,
-        R.drawable.appointment_indicator_leftside_20,
-        R.drawable.appointment_indicator_leftside_21,
-    };
-
-    /**
-     * This is the map from contact to color index.
-     * A colored chip in MODE_PICK_MULTIPLE_PHONES mode is used to indicate the number of phone
-     * numbers belong to one contact
-     */
-    SparseIntArray mContactColor;
-
-    /**
-     * UI control of action panel in MODE_PICK_MULTIPLE_PHONES mode.
-     */
-    private View mFooterView;
-
-    /**
-     * Display only selected recipients or not in MODE_PICK_MULTIPLE_PHONES mode
-     */
-    private boolean mShowSelectedOnly = false;
-
     static {
         sContactsIdMatcher = new UriMatcher(UriMatcher.NO_MATCH);
         sContactsIdMatcher.addURI(ContactsContract.AUTHORITY, "contacts/#", CONTACTS_ID);
@@ -554,21 +484,12 @@
     // The size of a home screen shortcut icon.
     private int mIconSize;
     private ContactsPreferences mContactsPrefs;
-    private int mDisplayOrder;
+    public int mDisplayOrder;
     private int mSortOrder;
-    private boolean mHighlightWhenScrolling;
-    private TextHighlightingAnimation mHighlightingAnimation;
+    public boolean mHighlightWhenScrolling;
+    public TextHighlightingAnimation mHighlightingAnimation;
     private SearchEditText mSearchEditText;
 
-    /**
-     * An approximation of the background color of the pinned header. This color
-     * is used when the pinned header is being pushed up.  At that point the header
-     * "fades away".  Rather than computing a faded bitmap based on the 9-patch
-     * normally used for the background, we will use a solid color, which will
-     * provide better performance and reduced complexity.
-     */
-    private int mPinnedHeaderBackgroundColor;
-
     private ContentObserver mProviderStatusObserver = new ContentObserver(new Handler()) {
 
         @Override
@@ -577,18 +498,12 @@
         }
     };
 
-    private OnClickListener mCheckBoxClickerListener = new OnClickListener () {
-        public void onClick(View v) {
-            final ContactListItemCache cache = (ContactListItemCache) v.getTag();
-            if (cache.phoneId != PhoneNumberAdapter.INVALID_PHONE_ID) {
-                mUserSelection.setPhoneSelected(cache.phoneId, ((CheckBox) v).isChecked());
-            } else {
-                mUserSelection.setPhoneSelected(cache.phoneNumber,
-                        ((CheckBox) v).isChecked());
-            }
-            updateWidgets(true);
-        }
-    };
+    private ContactsIntentResolver mIntentResolver;
+    protected ContactEntryListConfiguration mConfig;
+
+    public ContactsListActivity() {
+        mIntentResolver = new ContactsIntentResolver(this, this);
+    }
 
     /**
      * Visible for testing: makes queries run on the UI thread.
@@ -612,259 +527,44 @@
         // Resolve the intent
         final Intent intent = getIntent();
 
-        resolveIntent(intent);
+        mConfig = resolveIntent(intent);
         initContentView();
     }
 
-    protected void resolveIntent(final Intent intent) {
-        // Allow the title to be set to a custom String using an extra on the intent
-        String title = intent.getStringExtra(UI.TITLE_EXTRA_KEY);
-        if (title != null) {
-            setTitle(title);
-        }
+    protected ContactEntryListConfiguration resolveIntent(final Intent intent) {
+        mIntentResolver.setIntent(intent);
 
-        String action = intent.getAction();
-        String component = intent.getComponent().getClassName();
-        String type = intent.getType();
-
-        // When we get a FILTER_CONTACTS_ACTION, it represents search in the context
-        // of some other action. Let's retrieve the original action to provide proper
-        // context for the search queries.
-        if (UI.FILTER_CONTACTS_ACTION.equals(action)) {
-            mSearchMode = true;
-            mShowSearchSnippets = true;
-            Bundle extras = intent.getExtras();
-            if (extras != null) {
-                mInitialFilter = extras.getString(UI.FILTER_TEXT_EXTRA_KEY);
-                String originalAction =
-                        extras.getString(ContactsSearchManager.ORIGINAL_ACTION_EXTRA_KEY);
-                if (originalAction != null) {
-                    action = originalAction;
-                }
-                String originalComponent =
-                        extras.getString(ContactsSearchManager.ORIGINAL_COMPONENT_EXTRA_KEY);
-                if (originalComponent != null) {
-                    component = originalComponent;
-                }
-                String originalType =
-                    extras.getString(ContactsSearchManager.ORIGINAL_TYPE_EXTRA_KEY);
-                if (originalType != null) {
-                    type = originalType;
-                }
-            } else {
-                mInitialFilter = null;
-            }
-        }
-
-        Log.i(TAG, "Called with action: " + action);
-        mMode = MODE_UNKNOWN;
-        if (UI.LIST_DEFAULT.equals(action) || UI.FILTER_CONTACTS_ACTION.equals(action)) {
-            mMode = MODE_DEFAULT;
-            // When mDefaultMode is true the mode is set in onResume(), since the preferneces
-            // activity may change it whenever this activity isn't running
-        } else if (UI.LIST_GROUP_ACTION.equals(action)) {
-            mMode = MODE_GROUP;
-            String groupName = intent.getStringExtra(UI.GROUP_NAME_EXTRA_KEY);
-            if (TextUtils.isEmpty(groupName)) {
-                finish();
-                return;
-            }
-            buildUserGroupUri(groupName);
-        } else if (UI.LIST_ALL_CONTACTS_ACTION.equals(action)) {
-            mMode = MODE_CUSTOM;
-            mDisplayOnlyPhones = false;
-        } else if (UI.LIST_STARRED_ACTION.equals(action)) {
-            mMode = mSearchMode ? MODE_DEFAULT : MODE_STARRED;
-        } else if (UI.LIST_FREQUENT_ACTION.equals(action)) {
-            mMode = mSearchMode ? MODE_DEFAULT : MODE_FREQUENT;
-        } else if (UI.LIST_STREQUENT_ACTION.equals(action)) {
-            mMode = mSearchMode ? MODE_DEFAULT : MODE_STREQUENT;
-        } else if (UI.LIST_CONTACTS_WITH_PHONES_ACTION.equals(action)) {
-            mMode = MODE_CUSTOM;
-            mDisplayOnlyPhones = true;
-        } else if (Intent.ACTION_PICK.equals(action)) {
-            // XXX These should be showing the data from the URI given in
-            // the Intent.
-           // TODO : Does it work in mSearchMode?
-            final String resolvedType = intent.resolveType(this);
-            if (Contacts.CONTENT_TYPE.equals(resolvedType)) {
-                mMode = MODE_PICK_CONTACT;
-            } else if (People.CONTENT_TYPE.equals(resolvedType)) {
-                mMode = MODE_LEGACY_PICK_PERSON;
-            } else if (Phone.CONTENT_TYPE.equals(resolvedType)) {
-                mMode = MODE_PICK_PHONE;
-            } else if (Phones.CONTENT_TYPE.equals(resolvedType)) {
-                mMode = MODE_LEGACY_PICK_PHONE;
-            } else if (StructuredPostal.CONTENT_TYPE.equals(resolvedType)) {
-                mMode = MODE_PICK_POSTAL;
-            } else if (ContactMethods.CONTENT_POSTAL_TYPE.equals(resolvedType)) {
-                mMode = MODE_LEGACY_PICK_POSTAL;
-            }
-        } else if (Intent.ACTION_CREATE_SHORTCUT.equals(action)) {
-            if (component.equals("alias.DialShortcut")) {
-                mMode = MODE_PICK_PHONE;
-                mShortcutAction = Intent.ACTION_CALL;
-                mShowSearchSnippets = false;
-                setTitle(R.string.callShortcutActivityTitle);
-            } else if (component.equals("alias.MessageShortcut")) {
-                mMode = MODE_PICK_PHONE;
-                mShortcutAction = Intent.ACTION_SENDTO;
-                mShowSearchSnippets = false;
-                setTitle(R.string.messageShortcutActivityTitle);
-            } else if (mSearchMode) {
-                mMode = MODE_PICK_CONTACT;
-                mShortcutAction = Intent.ACTION_VIEW;
-                setTitle(R.string.shortcutActivityTitle);
-            } else {
-                mMode = MODE_PICK_OR_CREATE_CONTACT;
-                mShortcutAction = Intent.ACTION_VIEW;
-                setTitle(R.string.shortcutActivityTitle);
-            }
-        } else if (Intent.ACTION_GET_CONTENT.equals(action)) {
-            // TODO : Does it work in mSearchMode?
-            final String resolvedType = intent.resolveType(this);
-            if (Contacts.CONTENT_ITEM_TYPE.equals(resolvedType)) {
-                if (mSearchMode) {
-                    mMode = MODE_PICK_CONTACT;
-                } else {
-                    mMode = MODE_PICK_OR_CREATE_CONTACT;
-                }
-            } else if (Phone.CONTENT_ITEM_TYPE.equals(resolvedType)) {
-                mMode = MODE_PICK_PHONE;
-            } else if (Phones.CONTENT_ITEM_TYPE.equals(resolvedType)) {
-                mMode = MODE_LEGACY_PICK_PHONE;
-            } else if (StructuredPostal.CONTENT_ITEM_TYPE.equals(resolvedType)) {
-                mMode = MODE_PICK_POSTAL;
-            } else if (ContactMethods.CONTENT_POSTAL_ITEM_TYPE.equals(resolvedType)) {
-                mMode = MODE_LEGACY_PICK_POSTAL;
-            }  else if (People.CONTENT_ITEM_TYPE.equals(resolvedType)) {
-                if (mSearchMode) {
-                    mMode = MODE_LEGACY_PICK_PERSON;
-                } else {
-                    mMode = MODE_LEGACY_PICK_OR_CREATE_PERSON;
-                }
-            }
-
-        } else if (Intent.ACTION_INSERT_OR_EDIT.equals(action)) {
-            mMode = MODE_INSERT_OR_EDIT_CONTACT;
-        } else if (Intent.ACTION_SEARCH.equals(action)) {
-            // See if the suggestion was clicked with a search action key (call button)
-            if ("call".equals(intent.getStringExtra(SearchManager.ACTION_MSG))) {
-                String query = intent.getStringExtra(SearchManager.QUERY);
-                if (!TextUtils.isEmpty(query)) {
-                    Intent newIntent = new Intent(Intent.ACTION_CALL_PRIVILEGED,
-                            Uri.fromParts("tel", query, null));
-                    startActivity(newIntent);
-                }
-                finish();
-                return;
-            }
-
-            // See if search request has extras to specify query
-            if (intent.hasExtra(Insert.EMAIL)) {
-                mMode = MODE_QUERY_PICK_TO_VIEW;
-                mQueryMode = QUERY_MODE_MAILTO;
-                mInitialFilter = intent.getStringExtra(Insert.EMAIL);
-            } else if (intent.hasExtra(Insert.PHONE)) {
-                mMode = MODE_QUERY_PICK_TO_VIEW;
-                mQueryMode = QUERY_MODE_TEL;
-                mInitialFilter = intent.getStringExtra(Insert.PHONE);
-            } else {
-                // Otherwise handle the more normal search case
-                mMode = MODE_QUERY;
-                mShowSearchSnippets = true;
-                mInitialFilter = getIntent().getStringExtra(SearchManager.QUERY);
-            }
-            mSearchResultsMode = true;
-        } else if (ACTION_SEARCH_INTERNAL.equals(action)) {
-            String originalAction = null;
-            Bundle extras = intent.getExtras();
-            if (extras != null) {
-                originalAction = extras.getString(ContactsSearchManager.ORIGINAL_ACTION_EXTRA_KEY);
-            }
-            mShortcutAction = intent.getStringExtra(SHORTCUT_ACTION_KEY);
-
-            if (Intent.ACTION_INSERT_OR_EDIT.equals(originalAction)) {
-                mMode = MODE_QUERY_PICK_TO_EDIT;
-                mShowSearchSnippets = true;
-                mInitialFilter = getIntent().getStringExtra(SearchManager.QUERY);
-            } else if (mShortcutAction != null && intent.hasExtra(Insert.PHONE)) {
-                mMode = MODE_QUERY_PICK_PHONE;
-                mQueryMode = QUERY_MODE_TEL;
-                mInitialFilter = intent.getStringExtra(Insert.PHONE);
-            } else {
-                mMode = MODE_QUERY_PICK;
-                mQueryMode = QUERY_MODE_NONE;
-                mShowSearchSnippets = true;
-                mInitialFilter = getIntent().getStringExtra(SearchManager.QUERY);
-            }
-            mSearchResultsMode = true;
-        // Since this is the filter activity it receives all intents
-        // dispatched from the SearchManager for security reasons
-        // so we need to re-dispatch from here to the intended target.
-        } else if (Intents.SEARCH_SUGGESTION_CLICKED.equals(action)) {
-            Uri data = intent.getData();
-            Uri telUri = null;
-            if (sContactsIdMatcher.match(data) == CONTACTS_ID) {
-                long contactId = Long.valueOf(data.getLastPathSegment());
-                final Cursor cursor = queryPhoneNumbers(contactId);
-                if (cursor != null) {
-                    if (cursor.getCount() == 1 && cursor.moveToFirst()) {
-                        int phoneNumberIndex = cursor.getColumnIndex(Phone.NUMBER);
-                        String phoneNumber = cursor.getString(phoneNumberIndex);
-                        telUri = Uri.parse("tel:" + phoneNumber);
-                    }
-                    cursor.close();
-                }
-            }
-            // See if the suggestion was clicked with a search action key (call button)
-            Intent newIntent;
-            if ("call".equals(intent.getStringExtra(SearchManager.ACTION_MSG)) && telUri != null) {
-                newIntent = new Intent(Intent.ACTION_CALL_PRIVILEGED, telUri);
-            } else {
-                newIntent = new Intent(Intent.ACTION_VIEW, data);
-            }
-            startActivity(newIntent);
+        if (!mIntentResolver.isValid()) {           // Invalid intent
+            setResult(RESULT_CANCELED);
             finish();
-            return;
-        } else if (Intents.SEARCH_SUGGESTION_DIAL_NUMBER_CLICKED.equals(action)) {
-            Intent newIntent = new Intent(Intent.ACTION_CALL_PRIVILEGED, intent.getData());
-            startActivity(newIntent);
-            finish();
-            return;
-        } else if (Intents.SEARCH_SUGGESTION_CREATE_CONTACT_CLICKED.equals(action)) {
-            // TODO actually support this in EditContactActivity.
-            String number = intent.getData().getSchemeSpecificPart();
-            Intent newIntent = new Intent(Intent.ACTION_INSERT, Contacts.CONTENT_URI);
-            newIntent.putExtra(Intents.Insert.PHONE, number);
-            startActivity(newIntent);
-            finish();
-            return;
-        } else if (JoinContactActivity.JOIN_CONTACT.equals(action)) {
-            mMode = MODE_PICK_CONTACT;
-        } else if (Intents.ACTION_GET_MULTIPLE_PHONES.equals(action)) {
-            if (mSearchMode) {
-                mShowSearchSnippets = false;
-            }
-            if (Phone.CONTENT_TYPE.equals(type)) {
-                mMode = MODE_PICK_MULTIPLE_PHONES;
-                mContactColor = new SparseIntArray();
-                initMultiPicker(intent);
-            } else {
-                // TODO support other content types
-                Log.e(TAG, "Intent " + action + " is not supported for type " + type);
-                setResult(RESULT_CANCELED);
-                finish();
-            }
-        }
-        if (mMode == MODE_UNKNOWN) {
-            mMode = MODE_DEFAULT;
+            return null;
         }
 
-        if (((mMode & MODE_MASK_SHOW_NUMBER_OF_CONTACTS) != 0 || mSearchMode)
-                && !mSearchResultsMode) {
-            mShowNumberOfContacts = true;
+        Intent redirect = mIntentResolver.getRedirectIntent();
+        if (redirect != null) {             // Need to start a different activity
+            startActivity(redirect);
+            finish();
+            return null;
         }
+
+        setTitle(mIntentResolver.getActivityTitle());
+
+
+        // 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.
+        mMode = mIntentResolver.mMode;
+        mGroupName = mIntentResolver.mGroupName;
+        mQueryMode = mIntentResolver.mQueryMode;
+        mSearchMode = mIntentResolver.mSearchMode;
+        mShowSearchSnippets = mIntentResolver.mShowSearchSnippets;
+        mInitialFilter = mIntentResolver.mInitialFilter;
+        mDisplayOnlyPhones = mIntentResolver.mDisplayOnlyPhones;
+        mShortcutAction = mIntentResolver.mShortcutAction;
+        mSearchResultsMode = mIntentResolver.mSearchResultsMode;
+        mShowNumberOfContacts = mIntentResolver.mShowNumberOfContacts;
+        mGroupName = mIntentResolver.mGroupName;
+
+        return mIntentResolver.getConfiguration();
     }
 
     public void initContentView() {
@@ -879,30 +579,44 @@
             setContentView(R.layout.contacts_list_content);
         }
 
-        setupListView(new ContactItemListAdapter(this));
+        mConfig.configureListView(getListView());
+
         if (mSearchMode) {
             setupSearchView();
         }
 
-        if (mMode == MODE_PICK_MULTIPLE_PHONES) {
-            ViewStub stub = (ViewStub)findViewById(R.id.footer_stub);
-            if (stub != null) {
-                View stubView = stub.inflate();
-                mFooterView = stubView.findViewById(R.id.footer);
-                mFooterView.setVisibility(View.GONE);
-                Button doneButton = (Button) stubView.findViewById(R.id.done);
-                doneButton.setOnClickListener(this);
-                Button revertButton = (Button) stubView.findViewById(R.id.revert);
-                revertButton.setOnClickListener(this);
-            }
-        }
-
         View emptyView = mList.getEmptyView();
         if (emptyView instanceof ContactListEmptyView) {
             mEmptyView = (ContactListEmptyView)emptyView;
         }
     }
 
+    // TODO move this to the configuration object(s)
+    @Deprecated
+    public void setupListView(ListAdapter adapter) {
+        final ListView list = getListView();
+        final LayoutInflater inflater = getLayoutInflater();
+
+        mHighlightingAnimation =
+                new NameHighlightingAnimation(list, TEXT_HIGHLIGHTING_ANIMATION_DURATION);
+
+        // Tell list view to not show dividers. We'll do it ourself so that we can *not* show
+        // them when an A-Z headers is visible.
+        list.setDividerHeight(0);
+        list.setOnCreateContextMenuListener(this);
+
+        mAdapter = (ContactEntryListAdapter)adapter;
+        setListAdapter(mAdapter);
+
+        list.setOnScrollListener(this);
+        list.setOnKeyListener(this);
+        list.setOnFocusChangeListener(this);
+        list.setOnTouchListener(this);
+
+        // We manually save/restore the listview state
+        list.setSaveEnabled(false);
+    }
+
     /**
      * Register an observer for provider status changes - we will need to
      * reflect them in the UI.
@@ -920,36 +634,24 @@
         getContentResolver().unregisterContentObserver(mProviderStatusObserver);
     }
 
-    protected void setupListView(ContactItemListAdapter adapter) {
-        final ListView list = getListView();
-        final LayoutInflater inflater = getLayoutInflater();
+    public void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount,
+            int totalItemCount) {
+    }
 
-        mHighlightingAnimation =
-                new NameHighlightingAnimation(list, TEXT_HIGHLIGHTING_ANIMATION_DURATION);
-
-        // Tell list view to not show dividers. We'll do it ourself so that we can *not* show
-        // them when an A-Z headers is visible.
-        list.setDividerHeight(0);
-        list.setOnCreateContextMenuListener(this);
-
-        mAdapter = adapter;
-        setListAdapter(mAdapter);
-
-        if (list instanceof PinnedHeaderListView && mAdapter.getDisplaySectionHeadersEnabled()) {
-            mPinnedHeaderBackgroundColor =
-                    getResources().getColor(R.color.pinned_header_background);
-            PinnedHeaderListView pinnedHeaderList = (PinnedHeaderListView)list;
-            View pinnedHeader = inflater.inflate(R.layout.list_section, list, false);
-            pinnedHeaderList.setPinnedHeaderView(pinnedHeader);
+    public void onScrollStateChanged(AbsListView view, int scrollState) {
+        if (mHighlightWhenScrolling) {
+            if (scrollState != OnScrollListener.SCROLL_STATE_IDLE) {
+                mHighlightingAnimation.startHighlighting();
+            } else {
+                mHighlightingAnimation.stopHighlighting();
+            }
         }
 
-        list.setOnScrollListener(mAdapter);
-        list.setOnKeyListener(this);
-        list.setOnFocusChangeListener(this);
-        list.setOnTouchListener(this);
-
-        // We manually save/restore the listview state
-        list.setSaveEnabled(false);
+        if (scrollState == OnScrollListener.SCROLL_STATE_FLING) {
+            mPhotoLoader.pause();
+        } else if (mConfig.isPhotoLoaderEnabled()) {
+            mPhotoLoader.resume();
+        }
     }
 
     /**
@@ -961,7 +663,7 @@
         mSearchEditText.setOnEditorActionListener(this);
         mSearchEditText.setText(mInitialFilter);
     }
-    private int getSummaryDisplayNameColumnIndex() {
+    public int getSummaryDisplayNameColumnIndex() {
         if (mDisplayOrder == ContactsContract.Preferences.DISPLAY_ORDER_PRIMARY) {
             return SUMMARY_DISPLAY_NAME_PRIMARY_COLUMN_INDEX;
         } else {
@@ -983,20 +685,9 @@
                 }
                 break;
             }
-            case R.id.done:
-                setMultiPickerResult();
-                finish();
-                break;
-            case R.id.revert:
-                finish();
-                break;
         }
     }
 
-    private void buildUserGroupUri(String group) {
-        mGroupUri = Uri.withAppendedPath(Contacts.CONTENT_GROUP_URI, group);
-    }
-
     /**
      * Sets the mode when the request is for "default"
      */
@@ -1151,7 +842,7 @@
         retryUpgrade.setOnClickListener(listener);
     }
 
-    protected String getTextFilter() {
+    public String getTextFilter() {
         if (mSearchEditText != null) {
             return mSearchEditText.getText().toString();
         }
@@ -1183,9 +874,6 @@
         // Save list state in the bundle so we can restore it after the QueryHandler has run
         if (mList != null) {
             icicle.putParcelable(LIST_STATE_KEY, mList.onSaveInstanceState());
-            if (mMode == MODE_PICK_MULTIPLE_PHONES && mUserSelection != null) {
-                mUserSelection.saveInstanceState(icicle);
-            }
         }
     }
 
@@ -1194,9 +882,6 @@
         super.onRestoreInstanceState(icicle);
         // Retrieve list state. This will be applied after the QueryHandler has run
         mListState = icicle.getParcelable(LIST_STATE_KEY);
-        if (mMode == MODE_PICK_MULTIPLE_PHONES) {
-            mUserSelection = new UserSelection(icicle);
-        }
     }
 
     @Override
@@ -1216,12 +901,6 @@
     public boolean onCreateOptionsMenu(Menu menu) {
         super.onCreateOptionsMenu(menu);
 
-        if (mMode == MODE_PICK_MULTIPLE_PHONES) {
-            final MenuInflater inflater = getMenuInflater();
-            inflater.inflate(R.menu.pick, menu);
-            return true;
-        }
-
         // If Contacts was invoked by another Activity simply as a way of
         // picking a contact, don't show the options menu
         if ((mMode & MODE_MASK_PICKER) == MODE_MASK_PICKER) {
@@ -1235,26 +914,6 @@
 
     @Override
     public boolean onPrepareOptionsMenu(Menu menu) {
-        if (mMode == MODE_PICK_MULTIPLE_PHONES) {
-            if (mShowSelectedOnly) {
-                menu.findItem(R.id.menu_display_selected).setVisible(false);
-                menu.findItem(R.id.menu_display_all).setVisible(true);
-                menu.findItem(R.id.menu_select_all).setVisible(false);
-                menu.findItem(R.id.menu_select_none).setVisible(false);
-                return true;
-            }
-            menu.findItem(R.id.menu_display_all).setVisible(false);
-            menu.findItem(R.id.menu_display_selected).setVisible(true);
-            if (mUserSelection.isAllSelected()) {
-                menu.findItem(R.id.menu_select_all).setVisible(false);
-                menu.findItem(R.id.menu_select_none).setVisible(true);
-            } else {
-                menu.findItem(R.id.menu_select_all).setVisible(true);
-                menu.findItem(R.id.menu_select_none).setVisible(false);
-            }
-            return true;
-        }
-
         final boolean defaultMode = (mMode == MODE_DEFAULT);
         menu.findItem(R.id.menu_display_groups).setVisible(defaultMode);
         return true;
@@ -1289,28 +948,6 @@
                 startActivity(intent);
                 return true;
             }
-            case R.id.menu_select_all: {
-                mUserSelection.setAllPhonesSelected(true);
-                checkAll(true);
-                updateWidgets(true);
-                return true;
-            }
-            case R.id.menu_select_none: {
-                mUserSelection.setAllPhonesSelected(false);
-                checkAll(false);
-                updateWidgets(true);
-                return true;
-            }
-            case R.id.menu_display_selected: {
-                mShowSelectedOnly = true;
-                startQuery();
-                return true;
-            }
-            case R.id.menu_display_all: {
-                mShowSelectedOnly = false;
-                startQuery();
-                return true;
-            }
         }
         return false;
     }
@@ -1327,16 +964,8 @@
         } else {
             if (!mSearchMode && (mMode & MODE_MASK_NO_FILTER) == 0) {
                 if ((mMode & MODE_MASK_PICKER) != 0) {
-                    Bundle extras = null;
-                    if (mMode == MODE_PICK_MULTIPLE_PHONES) {
-                        extras = getIntent().getExtras();
-                        if (extras == null) {
-                            extras = new Bundle();
-                        }
-                        mUserSelection.fillSelectionForSearchMode(extras);
-                    }
                     ContactsSearchManager.startSearchForResult(this, initialQuery,
-                            SUBACTIVITY_FILTER, extras);
+                            SUBACTIVITY_FILTER, null);
                 } else {
                     ContactsSearchManager.startSearch(this, initialQuery);
                 }
@@ -1784,14 +1413,6 @@
         return false;
     }
 
-    @Override
-    public void onBackPressed() {
-        if (mMode == MODE_PICK_MULTIPLE_PHONES) {
-            setMultiPickerResult();
-        }
-        super.onBackPressed();
-    }
-
     /**
      * Prompt the user before deleting the given {@link Contacts} entry.
      */
@@ -1866,15 +1487,9 @@
         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) {
-        if (mSearchMode && mAdapter.isSearchAllContactsItemPosition(position)) {
+    public void onListItemClick(int position, long id) {
+        if (mSearchMode &&
+                ((ContactItemListAdapter)(mAdapter)).isSearchAllContactsItemPosition(position)) {
             doSearch();
         } else if (mMode == MODE_INSERT_OR_EDIT_CONTACT || mMode == MODE_QUERY_PICK_TO_EDIT) {
             Intent intent;
@@ -2192,7 +1807,7 @@
                         Uri.encode(mInitialFilter));
             }
             case MODE_GROUP: {
-                return mGroupUri;
+                return Uri.withAppendedPath(Contacts.CONTENT_GROUP_URI, mGroupName);
             }
             default: {
                 throw new IllegalStateException("Can't generate URI: Unsupported Mode.");
@@ -2384,7 +1999,7 @@
             baseUri = Contacts.CONTENT_URI;
         }
 
-        if (mAdapter.getDisplaySectionHeadersEnabled()) {
+        if (mConfig.isSectionHeaderDisplayEnabled()) {
             return buildSectionIndexerUri(baseUri);
         } else {
             return baseUri;
@@ -2424,7 +2039,7 @@
         return sortKey;
     }
 
-    void startQuery() {
+    public void startQuery() {
         if (mSearchResultsMode) {
             TextView foundContactsText = (TextView)findViewById(R.id.search_results_found);
             foundContactsText.setText(R.string.search_results_searching);
@@ -2434,7 +2049,8 @@
             mEmptyView.hide();
         }
 
-        mAdapter.setLoading(true);
+        // TODO reintroduce the loading state handling
+//        mAdapter.setLoading(true);
 
         // Cancel any pending queries
         mQueryHandler.cancelOperation(QUERY_TOKEN);
@@ -2519,25 +2135,6 @@
                 mQueryHandler.startQuery(QUERY_TOKEN, null, uri, projection, null, null, null);
                 break;
 
-            case MODE_PICK_MULTIPLE_PHONES:
-                // Filter unknown phone numbers first.
-                mPhoneNumberAdapter.doFilter(null, mShowSelectedOnly);
-                if (mShowSelectedOnly) {
-                    StringBuilder idSetBuilder = new StringBuilder();
-                    Iterator<Long> itr = mUserSelection.getSelectedPhonIds();
-                    if (itr.hasNext()) {
-                        idSetBuilder.append(Long.toString(itr.next()));
-                    }
-                    while (itr.hasNext()) {
-                        idSetBuilder.append(',');
-                        idSetBuilder.append(Long.toString(itr.next()));
-                    }
-                    String whereClause = Phone._ID + " IN (" + idSetBuilder.toString() + ")";
-                    mQueryHandler.startQuery(QUERY_TOKEN, null, uri,
-                            projection, whereClause, null, getSortOrder(projection));
-                    break;
-                }
-                // Fall through For other cases
             case MODE_PICK_PHONE:
                 mQueryHandler.startQuery(QUERY_TOKEN, null, uri,
                         projection, CLAUSE_ONLY_VISIBLE, null, getSortOrder(projection));
@@ -2569,7 +2166,7 @@
      * @param filter the text that was entered to filter on
      * @return a cursor with the results of the filter
      */
-    Cursor doFilter(String filter) {
+    public Cursor doFilter(String filter) {
         String[] projection = getProjectionForQuery();
         if (mSearchMode && TextUtils.isEmpty(getTextFilter())) {
             return new MatrixCursor(projection);
@@ -2616,10 +2213,6 @@
                 return resolver.query(uri, projection, null, null, null);
             }
 
-            case MODE_PICK_MULTIPLE_PHONES:
-                // Filter phone numbers as well.
-                mPhoneNumberAdapter.doFilter(filter, mShowSelectedOnly);
-                // Fall through
             case MODE_PICK_PHONE: {
                 Uri uri = getUriToQuery();
                 if (!TextUtils.isEmpty(filter)) {
@@ -2735,6 +2328,8 @@
         return true;
     }
 
+    // TODO: eliminate
+    @Deprecated
     private Cursor queryPhoneNumbers(long contactId) {
         Uri baseUri = ContentUris.withAppendedId(Contacts.CONTENT_URI, contactId);
         Uri dataUri = Uri.withAppendedPath(baseUri, Contacts.Data.CONTENT_DIRECTORY);
@@ -2750,7 +2345,7 @@
     }
 
     // TODO: fix PluralRules to handle zero correctly and use Resources.getQuantityText directly
-    protected String getQuantityText(int count, int zeroResourceId, int pluralResourceId) {
+    public String getQuantityText(int count, int zeroResourceId, int pluralResourceId) {
         if (count == 0) {
             return getString(zeroResourceId);
         } else {
@@ -2775,148 +2370,7 @@
         return (Cursor) listView.getAdapter().getItem(index);
     }
 
-    private void initMultiPicker(final Intent intent) {
-        final Handler handler = new Handler();
-        // TODO : Shall we still show the progressDialog in search mode.
-        final ProgressDialog progressDialog = new ProgressDialog(this);
-        progressDialog.setMessage(getText(R.string.adding_recipients));
-        progressDialog.setIndeterminate(true);
-        progressDialog.setCancelable(false);
-
-        final Runnable showProgress = new Runnable() {
-            public void run() {
-                progressDialog.show();
-            }
-        };
-        handler.postDelayed(showProgress, 1);
-
-        new Thread(new Runnable() {
-            public void run() {
-                try {
-                    loadSelectionFromIntent(intent);
-                } finally {
-                    handler.removeCallbacks(showProgress);
-                    progressDialog.dismiss();
-                }
-                final Runnable populateWorker = new Runnable() {
-                    public void run() {
-                        if (mAdapter != null) {
-                            mAdapter.notifyDataSetChanged();
-                        }
-                        updateWidgets(false);
-                    }
-                };
-                handler.post(populateWorker);
-            }
-        }).start();
-    }
-
-    private void getPhoneNumbersOrIdsFromURIs(final Parcelable[] uris,
-            final List<String> phoneNumbers, final List<Long> phoneIds) {
-        if (uris != null) {
-            for (Parcelable paracelable : uris) {
-                Uri uri = (Uri) paracelable;
-                if (uri == null) continue;
-                String scheme = uri.getScheme();
-                if (phoneNumbers != null && TEL_SCHEME.equals(scheme)) {
-                    phoneNumbers.add(uri.getSchemeSpecificPart());
-                } else if (phoneIds != null && CONTENT_SCHEME.equals(scheme)) {
-                    phoneIds.add(ContentUris.parseId(uri));
-                }
-            }
-        }
-    }
-
-    private void loadSelectionFromIntent(Intent intent) {
-        Parcelable[] uris = intent.getParcelableArrayExtra(Intents.EXTRA_PHONE_URIS);
-        ArrayList<String> phoneNumbers = new ArrayList<String>();
-        ArrayList<Long> phoneIds = new ArrayList<Long>();
-        ArrayList<String> selectedPhoneNumbers = null;
-        if (mSearchMode) {
-            // All selection will be read from EXTRA_SELECTION
-            getPhoneNumbersOrIdsFromURIs(uris, phoneNumbers, null);
-            uris = intent.getParcelableArrayExtra(UserSelection.EXTRA_SELECTION);
-            if (uris != null) {
-                selectedPhoneNumbers = new ArrayList<String>();
-                getPhoneNumbersOrIdsFromURIs(uris, selectedPhoneNumbers, phoneIds);
-            }
-        } else {
-            getPhoneNumbersOrIdsFromURIs(uris, phoneNumbers, phoneIds);
-            selectedPhoneNumbers = phoneNumbers;
-        }
-        mPhoneNumberAdapter = new PhoneNumberAdapter(this, phoneNumbers);
-        mUserSelection = new UserSelection(selectedPhoneNumbers, phoneIds);
-    }
-
-    private void setMultiPickerResult() {
-        setResult(RESULT_OK, mUserSelection.createSelectionIntent());
-    }
-
-    /**
-     * Go through the cursor and assign the chip color to contact who has more than one phone
-     * numbers.
-     * Assume the cursor is sorted by CONTACT_ID.
-     */
-    private void updateChipColor(Cursor cursor) {
-        if (cursor == null || cursor.getCount() == 0) {
-            return;
-        }
-        mContactColor.clear();
-        int backupPos = cursor.getPosition();
-        cursor.moveToFirst();
-        int color = 0;
-        long prevContactId = cursor.getLong(PHONE_CONTACT_ID_COLUMN_INDEX);
-        while (cursor.moveToNext()) {
-            long contactId = cursor.getLong(PHONE_CONTACT_ID_COLUMN_INDEX);
-            if (prevContactId == contactId) {
-                if (mContactColor.indexOfKey(Long.valueOf(contactId).hashCode()) < 0) {
-                    mContactColor.put(Long.valueOf(contactId).hashCode(), CHIP_COLOR_ARRAY[color]);
-                    color++;
-                    if (color >= CHIP_COLOR_ARRAY.length) {
-                        color = 0;
-                    }
-                }
-            }
-            prevContactId = contactId;
-        }
-        cursor.moveToPosition(backupPos);
-    }
-
-    /**
-     * Get assigned chip color resource id for a given contact, 0 is returned if there is no mapped
-     * resource.
-     */
-    private int getChipColor(long contactId) {
-        return mContactColor.get(Long.valueOf(contactId).hashCode());
-    }
-
-    private void updateWidgets(boolean changed) {
-        int selected = mUserSelection.selectedCount();
-
-        if (selected >= 1) {
-            final String format =
-                getResources().getQuantityString(R.plurals.multiple_picker_title, selected);
-            setTitle(String.format(format, selected));
-        } else {
-            setTitle(getString(R.string.contactsList));
-        }
-
-        if (changed && mFooterView.getVisibility() == View.GONE) {
-            mFooterView.setVisibility(View.VISIBLE);
-            mFooterView.startAnimation(AnimationUtils.loadAnimation(this, R.anim.footer_appear));
-        }
-    }
-
-    private void checkAll(boolean checked) {
-        final ListView listView = getListView();
-        int childCount = listView.getChildCount();
-        for (int i = 0; i < childCount; i++) {
-            final ContactListItemView child = (ContactListItemView)listView.getChildAt(i);
-            child.getCheckBoxView().setChecked(checked);
-        }
-    }
-
-    private class QueryHandler extends AsyncQueryHandler {
+    protected class QueryHandler extends AsyncQueryHandler {
         protected final WeakReference<ContactsListActivity> mActivity;
 
         public QueryHandler(Context context) {
@@ -2960,7 +2414,7 @@
         }
     }
 
-    final static class ContactListItemCache {
+    public final static class ContactListItemCache {
         public CharArrayBuffer nameBuffer = new CharArrayBuffer(128);
         public CharArrayBuffer dataBuffer = new CharArrayBuffer(128);
         public CharArrayBuffer highlightedTextBuffer = new CharArrayBuffer(128);
@@ -2971,1113 +2425,4 @@
         public String phoneNumber;
     }
 
-    final static class PinnedHeaderCache {
-        public TextView titleView;
-        public ColorStateList textColor;
-        public Drawable background;
-    }
-
-    protected class ContactItemListAdapter extends CursorAdapter
-            implements SectionIndexer, OnScrollListener, PinnedHeaderListView.PinnedHeaderAdapter {
-        private SectionIndexer mIndexer;
-        private boolean mLoading = true;
-        private CharSequence mUnknownNameText;
-        private boolean mDisplayPhotos = false;
-        private boolean mDisplayCallButton = false;
-        private boolean mDisplayAdditionalData = true;
-        private int mFrequentSeparatorPos = ListView.INVALID_POSITION;
-        private boolean mDisplaySectionHeaders = true;
-
-        public ContactItemListAdapter(Context context) {
-            super(context, null, false);
-
-            mUnknownNameText = context.getText(android.R.string.unknownName);
-            switch (mMode) {
-                case MODE_LEGACY_PICK_POSTAL:
-                case MODE_PICK_POSTAL:
-                case MODE_LEGACY_PICK_PHONE:
-                case MODE_PICK_PHONE:
-                case MODE_STREQUENT:
-                case MODE_FREQUENT:
-                    mDisplaySectionHeaders = false;
-                    break;
-            }
-
-            if (mSearchMode) {
-                mDisplaySectionHeaders = false;
-            }
-
-            // Do not display the second line of text if in a specific SEARCH query mode, usually for
-            // matching a specific E-mail or phone number. Any contact details
-            // shown would be identical, and columns might not even be present
-            // in the returned cursor.
-            if (mMode != MODE_QUERY_PICK_PHONE && mQueryMode != QUERY_MODE_NONE) {
-                mDisplayAdditionalData = false;
-            }
-
-            if ((mMode & MODE_MASK_NO_DATA) == MODE_MASK_NO_DATA) {
-                mDisplayAdditionalData = false;
-            }
-
-            if ((mMode & MODE_MASK_SHOW_CALL_BUTTON) == MODE_MASK_SHOW_CALL_BUTTON) {
-                mDisplayCallButton = true;
-            }
-
-            if ((mMode & MODE_MASK_SHOW_PHOTOS) == MODE_MASK_SHOW_PHOTOS) {
-                mDisplayPhotos = true;
-            }
-        }
-
-        public boolean getDisplaySectionHeadersEnabled() {
-            return mDisplaySectionHeaders;
-        }
-
-        /**
-         * Callback on the UI thread when the content observer on the backing cursor fires.
-         * Instead of calling requery we need to do an async query so that the requery doesn't
-         * block the UI thread for a long time.
-         */
-        @Override
-        protected void onContentChanged() {
-            CharSequence constraint = getTextFilter();
-            if (!TextUtils.isEmpty(constraint)) {
-                // Reset the filter state then start an async filter operation
-                Filter filter = getFilter();
-                filter.filter(constraint);
-            } else {
-                // Start an async query
-                startQuery();
-            }
-        }
-
-        public void setLoading(boolean loading) {
-            mLoading = loading;
-        }
-
-        @Override
-        public boolean isEmpty() {
-            if (mProviderStatus != ProviderStatus.STATUS_NORMAL) {
-                return true;
-            }
-
-            if (mSearchMode) {
-                return TextUtils.isEmpty(getTextFilter());
-            } else if ((mMode & MODE_MASK_CREATE_NEW) == MODE_MASK_CREATE_NEW) {
-                // This mode mask adds a header and we always want it to show up, even
-                // if the list is empty, so always claim the list is not empty.
-                return false;
-            } else {
-                if (mCursor == null || mLoading) {
-                    // We don't want the empty state to show when loading.
-                    return false;
-                } else {
-                    return super.isEmpty();
-                }
-            }
-        }
-
-        @Override
-        public int getItemViewType(int position) {
-            if (position == 0 && (mShowNumberOfContacts || (mMode & MODE_MASK_CREATE_NEW) != 0)) {
-                return IGNORE_ITEM_VIEW_TYPE;
-            }
-
-            if (isSearchAllContactsItemPosition(position)) {
-                return IGNORE_ITEM_VIEW_TYPE;
-            }
-
-            if (getSeparatorId(position) != 0) {
-                // We don't want the separator view to be recycled.
-                return IGNORE_ITEM_VIEW_TYPE;
-            }
-            if (mMode == MODE_PICK_MULTIPLE_PHONES && position < mPhoneNumberAdapter.getCount()) {
-                return mPhoneNumberAdapter.getItemViewType(position);
-            }
-            return super.getItemViewType(position);
-        }
-
-        @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");
-            }
-
-            // handle the total contacts item
-            if (position == 0 && mShowNumberOfContacts) {
-                return getTotalContactCountView(parent);
-            }
-
-            if (position == 0 && (mMode & MODE_MASK_CREATE_NEW) != 0) {
-                // Add the header for creating a new contact
-                return getLayoutInflater().inflate(R.layout.create_new_contact, parent, false);
-            }
-
-            if (isSearchAllContactsItemPosition(position)) {
-                return getLayoutInflater().
-                        inflate(R.layout.contacts_list_search_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;
-            }
-
-            // Check whether this view should be retrieved from mPhoneNumberAdapter
-            if (mMode == MODE_PICK_MULTIPLE_PHONES && position < mPhoneNumberAdapter.getCount()) {
-                return mPhoneNumberAdapter.getView(position, convertView, parent);
-            }
-
-            int realPosition = getRealPosition(position);
-            if (!mCursor.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, mCursor, parent);
-            } else {
-                newView = false;
-                v = convertView;
-            }
-            bindView(v, mContext, mCursor);
-            bindSectionHeader(v, realPosition, mDisplaySectionHeaders);
-            return v;
-        }
-
-        private View getTotalContactCountView(ViewGroup parent) {
-            final LayoutInflater inflater = getLayoutInflater();
-            View view = inflater.inflate(R.layout.total_contacts, parent, false);
-
-            TextView totalContacts = (TextView) view.findViewById(R.id.totalContactsText);
-
-            String text;
-            int count = getRealCount();
-
-            if (mSearchMode && !TextUtils.isEmpty(getTextFilter())) {
-                text = getQuantityText(count, R.string.listFoundAllContactsZero,
-                        R.plurals.searchFoundContacts);
-            } else {
-                if (mDisplayOnlyPhones) {
-                    text = getQuantityText(count, R.string.listTotalPhoneContactsZero,
-                            R.plurals.listTotalPhoneContacts);
-                } else {
-                    text = getQuantityText(count, R.string.listTotalAllContactsZero,
-                            R.plurals.listTotalAllContacts);
-                }
-            }
-            totalContacts.setText(text);
-            return view;
-        }
-
-        private boolean isSearchAllContactsItemPosition(int position) {
-            return mSearchMode && mMode != MODE_PICK_MULTIPLE_PHONES && position == getCount() - 1;
-        }
-
-        private int getSeparatorId(int position) {
-            int separatorId = 0;
-            if (position == mFrequentSeparatorPos) {
-                separatorId = R.string.favoritesFrquentSeparator;
-            }
-            return separatorId;
-        }
-
-        @Override
-        public View newView(Context context, Cursor cursor, ViewGroup parent) {
-            final ContactListItemView view = new ContactListItemView(context, null);
-            view.setOnCallButtonClickListener(ContactsListActivity.this);
-            view.setOnCheckBoxClickListener(mCheckBoxClickerListener);
-            view.setTag(new ContactListItemCache());
-            return view;
-        }
-
-        @Override
-        public void bindView(View itemView, Context context, Cursor cursor) {
-            final ContactListItemView view = (ContactListItemView)itemView;
-            final ContactListItemCache cache = (ContactListItemCache) view.getTag();
-
-            int typeColumnIndex;
-            int dataColumnIndex;
-            int labelColumnIndex;
-            int defaultType;
-            int nameColumnIndex;
-            int phoneticNameColumnIndex;
-            int photoColumnIndex = SUMMARY_PHOTO_ID_COLUMN_INDEX;
-            boolean displayAdditionalData = mDisplayAdditionalData;
-            boolean highlightingEnabled = false;
-            switch(mMode) {
-                case MODE_PICK_MULTIPLE_PHONES:
-                case MODE_PICK_PHONE:
-                case MODE_LEGACY_PICK_PHONE:
-                case MODE_QUERY_PICK_PHONE: {
-                    nameColumnIndex = PHONE_DISPLAY_NAME_COLUMN_INDEX;
-                    phoneticNameColumnIndex = -1;
-                    dataColumnIndex = PHONE_NUMBER_COLUMN_INDEX;
-                    typeColumnIndex = PHONE_TYPE_COLUMN_INDEX;
-                    labelColumnIndex = PHONE_LABEL_COLUMN_INDEX;
-                    defaultType = Phone.TYPE_HOME;
-                    photoColumnIndex = PHONE_PHOTO_ID_COLUMN_INDEX;
-                    break;
-                }
-                case MODE_PICK_POSTAL:
-                case MODE_LEGACY_PICK_POSTAL: {
-                    nameColumnIndex = POSTAL_DISPLAY_NAME_COLUMN_INDEX;
-                    phoneticNameColumnIndex = -1;
-                    dataColumnIndex = POSTAL_ADDRESS_COLUMN_INDEX;
-                    typeColumnIndex = POSTAL_TYPE_COLUMN_INDEX;
-                    labelColumnIndex = POSTAL_LABEL_COLUMN_INDEX;
-                    defaultType = StructuredPostal.TYPE_HOME;
-                    break;
-                }
-                default: {
-                    nameColumnIndex = getSummaryDisplayNameColumnIndex();
-                    if (mMode == MODE_LEGACY_PICK_PERSON
-                            || mMode == MODE_LEGACY_PICK_OR_CREATE_PERSON) {
-                        phoneticNameColumnIndex = -1;
-                    } else {
-                        phoneticNameColumnIndex = SUMMARY_PHONETIC_NAME_COLUMN_INDEX;
-                    }
-                    dataColumnIndex = -1;
-                    typeColumnIndex = -1;
-                    labelColumnIndex = -1;
-                    defaultType = Phone.TYPE_HOME;
-                    displayAdditionalData = false;
-                    highlightingEnabled = mHighlightWhenScrolling && mMode != MODE_STREQUENT;
-                }
-            }
-
-            if (mMode == MODE_PICK_MULTIPLE_PHONES) {
-                cache.phoneId = Long.valueOf(cursor.getLong(PHONE_ID_COLUMN_INDEX));
-                CheckBox checkBox = view.getCheckBoxView();
-                checkBox.setChecked(mUserSelection.isSelected(cache.phoneId));
-                checkBox.setTag(cache);
-                int color = getChipColor(cursor.getLong(PHONE_CONTACT_ID_COLUMN_INDEX));
-                view.getChipView().setBackgroundResource(color);
-            }
-
-            // Set the name
-            cursor.copyStringToBuffer(nameColumnIndex, cache.nameBuffer);
-            TextView nameView = view.getNameTextView();
-            int size = cache.nameBuffer.sizeCopied;
-            if (size != 0) {
-                if (highlightingEnabled) {
-                    if (cache.textWithHighlighting == null) {
-                        cache.textWithHighlighting =
-                                mHighlightingAnimation.createTextWithHighlighting();
-                    }
-                    buildDisplayNameWithHighlighting(nameView, cursor, cache.nameBuffer,
-                            cache.highlightedTextBuffer, cache.textWithHighlighting);
-                } else {
-                    nameView.setText(cache.nameBuffer.data, 0, size);
-                }
-            } else {
-                nameView.setText(mUnknownNameText);
-            }
-
-            // Make the call button visible if requested.
-            if (mDisplayCallButton && cursor.getColumnCount() > SUMMARY_HAS_PHONE_COLUMN_INDEX
-                    && cursor.getInt(SUMMARY_HAS_PHONE_COLUMN_INDEX) != 0) {
-                int pos = cursor.getPosition();
-                view.showCallButton(android.R.id.button1, pos);
-            } else {
-                view.hideCallButton();
-            }
-
-            // Set the photo, if requested
-            if (mDisplayPhotos) {
-                boolean useQuickContact = (mMode & MODE_MASK_DISABLE_QUIKCCONTACT) == 0;
-
-                long photoId = 0;
-                if (!cursor.isNull(photoColumnIndex)) {
-                    photoId = cursor.getLong(photoColumnIndex);
-                }
-
-                ImageView viewToUse;
-                if (useQuickContact) {
-                    // Build soft lookup reference
-                    final long contactId = cursor.getLong(SUMMARY_ID_COLUMN_INDEX);
-                    final String lookupKey = cursor.getString(SUMMARY_LOOKUP_KEY_COLUMN_INDEX);
-                    QuickContactBadge quickContact = view.getQuickContact();
-                    quickContact.assignContactUri(Contacts.getLookupUri(contactId, lookupKey));
-                    viewToUse = quickContact;
-                } else {
-                    viewToUse = view.getPhotoView();
-                }
-
-                final int position = cursor.getPosition();
-                mPhotoLoader.loadPhoto(viewToUse, photoId);
-            }
-
-            if ((mMode & MODE_MASK_NO_PRESENCE) == 0) {
-                // Set the proper icon (star or presence or nothing)
-                int serverStatus;
-                if (!cursor.isNull(SUMMARY_PRESENCE_STATUS_COLUMN_INDEX)) {
-                    serverStatus = cursor.getInt(SUMMARY_PRESENCE_STATUS_COLUMN_INDEX);
-                    Drawable icon = ContactPresenceIconUtil.getPresenceIcon(mContext, serverStatus);
-                    if (icon != null) {
-                        view.setPresence(icon);
-                    } else {
-                        view.setPresence(null);
-                    }
-                } else {
-                    view.setPresence(null);
-                }
-            } else {
-                view.setPresence(null);
-            }
-
-            if (mShowSearchSnippets) {
-                boolean showSnippet = false;
-                String snippetMimeType = cursor.getString(SUMMARY_SNIPPET_MIMETYPE_COLUMN_INDEX);
-                if (Email.CONTENT_ITEM_TYPE.equals(snippetMimeType)) {
-                    String email = cursor.getString(SUMMARY_SNIPPET_DATA1_COLUMN_INDEX);
-                    if (!TextUtils.isEmpty(email)) {
-                        view.setSnippet(email);
-                        showSnippet = true;
-                    }
-                } else if (Organization.CONTENT_ITEM_TYPE.equals(snippetMimeType)) {
-                    String company = cursor.getString(SUMMARY_SNIPPET_DATA1_COLUMN_INDEX);
-                    String title = cursor.getString(SUMMARY_SNIPPET_DATA4_COLUMN_INDEX);
-                    if (!TextUtils.isEmpty(company)) {
-                        if (!TextUtils.isEmpty(title)) {
-                            view.setSnippet(company + " / " + title);
-                        } else {
-                            view.setSnippet(company);
-                        }
-                        showSnippet = true;
-                    } else if (!TextUtils.isEmpty(title)) {
-                        view.setSnippet(title);
-                        showSnippet = true;
-                    }
-                } else if (Nickname.CONTENT_ITEM_TYPE.equals(snippetMimeType)) {
-                    String nickname = cursor.getString(SUMMARY_SNIPPET_DATA1_COLUMN_INDEX);
-                    if (!TextUtils.isEmpty(nickname)) {
-                        view.setSnippet(nickname);
-                        showSnippet = true;
-                    }
-                }
-
-                if (!showSnippet) {
-                    view.setSnippet(null);
-                }
-            }
-
-            if (!displayAdditionalData) {
-                if (phoneticNameColumnIndex != -1) {
-
-                    // Set the name
-                    cursor.copyStringToBuffer(phoneticNameColumnIndex, cache.phoneticNameBuffer);
-                    int phoneticNameSize = cache.phoneticNameBuffer.sizeCopied;
-                    if (phoneticNameSize != 0) {
-                        view.setLabel(cache.phoneticNameBuffer.data, phoneticNameSize);
-                    } else {
-                        view.setLabel(null);
-                    }
-                } else {
-                    view.setLabel(null);
-                }
-                return;
-            }
-
-            // Set the data.
-            cursor.copyStringToBuffer(dataColumnIndex, cache.dataBuffer);
-
-            size = cache.dataBuffer.sizeCopied;
-            view.setData(cache.dataBuffer.data, size);
-
-            // Set the label.
-            if (!cursor.isNull(typeColumnIndex)) {
-                final int type = cursor.getInt(typeColumnIndex);
-                final String label = cursor.getString(labelColumnIndex);
-
-                if (mMode == MODE_LEGACY_PICK_POSTAL || mMode == MODE_PICK_POSTAL) {
-                    // TODO cache
-                    view.setLabel(StructuredPostal.getTypeLabel(context.getResources(), type,
-                            label));
-                } else {
-                    // TODO cache
-                    view.setLabel(Phone.getTypeLabel(context.getResources(), type, label));
-                }
-            } else {
-                view.setLabel(null);
-            }
-        }
-
-        /**
-         * Computes the span of the display name that has highlighted parts and configures
-         * the display name text view accordingly.
-         */
-        private void buildDisplayNameWithHighlighting(TextView textView, Cursor cursor,
-                CharArrayBuffer buffer1, CharArrayBuffer buffer2,
-                TextWithHighlighting textWithHighlighting) {
-            int oppositeDisplayOrderColumnIndex;
-            if (mDisplayOrder == ContactsContract.Preferences.DISPLAY_ORDER_PRIMARY) {
-                oppositeDisplayOrderColumnIndex = SUMMARY_DISPLAY_NAME_ALTERNATIVE_COLUMN_INDEX;
-            } else {
-                oppositeDisplayOrderColumnIndex = SUMMARY_DISPLAY_NAME_PRIMARY_COLUMN_INDEX;
-            }
-            cursor.copyStringToBuffer(oppositeDisplayOrderColumnIndex, buffer2);
-
-            textWithHighlighting.setText(buffer1, buffer2);
-            textView.setText(textWithHighlighting);
-        }
-
-        protected void bindSectionHeader(View itemView, int position, boolean displaySectionHeaders) {
-            final ContactListItemView view = (ContactListItemView)itemView;
-            final ContactListItemCache cache = (ContactListItemCache) view.getTag();
-            if (!displaySectionHeaders) {
-                view.setSectionHeader(null);
-                view.setDividerVisible(true);
-            } else {
-                final int section = getSectionForPosition(position);
-                if (getPositionForSection(section) == position) {
-                    String title = (String)mIndexer.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);
-                }
-            }
-        }
-
-        @Override
-        public void changeCursor(Cursor cursor) {
-            if (cursor != null) {
-                setLoading(false);
-            }
-
-            // Get the split between starred and frequent items, if the mode is strequent
-            mFrequentSeparatorPos = ListView.INVALID_POSITION;
-            int cursorCount = 0;
-            if (cursor != null && (cursorCount = cursor.getCount()) > 0
-                    && mMode == MODE_STREQUENT) {
-                cursor.move(-1);
-                for (int i = 0; cursor.moveToNext(); i++) {
-                    int starred = cursor.getInt(SUMMARY_STARRED_COLUMN_INDEX);
-                    if (starred == 0) {
-                        if (i > 0) {
-                            // Only add the separator when there are starred items present
-                            mFrequentSeparatorPos = i;
-                        }
-                        break;
-                    }
-                }
-            }
-
-            if (cursor != null && mSearchResultsMode) {
-                TextView foundContactsText = (TextView)findViewById(R.id.search_results_found);
-                String text = getQuantityText(cursor.getCount(), R.string.listFoundAllContactsZero,
-                        R.plurals.listFoundAllContacts);
-                foundContactsText.setText(text);
-            }
-
-            if (mEmptyView != null && (cursor == null || cursor.getCount() == 0)) {
-                mEmptyView.show(mSearchMode, mDisplayOnlyPhones,
-                        mMode == MODE_STREQUENT || mMode == MODE_STARRED,
-                        mMode == MODE_QUERY || mMode == MODE_QUERY_PICK
-                        || mMode == MODE_QUERY_PICK_PHONE || mMode == MODE_QUERY_PICK_TO_VIEW
-                        || mMode == MODE_QUERY_PICK_TO_EDIT,
-                        mShortcutAction != null,
-                        mMode == MODE_PICK_MULTIPLE_PHONES,
-                        mShowSelectedOnly);
-            }
-
-            super.changeCursor(cursor);
-
-            // Update the indexer for the fast scroll widget
-            updateIndexer(cursor);
-
-            if (mMode == MODE_PICK_MULTIPLE_PHONES) {
-                updateChipColor(cursor);
-            }
-        }
-
-        private void updateIndexer(Cursor cursor) {
-            if (cursor == null) {
-                mIndexer = 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);
-                mIndexer = new ContactsSectionIndexer(sections, counts);
-            } else {
-                mIndexer = null;
-            }
-        }
-
-        /**
-         * Run the query on a helper thread. Beware that this code does not run
-         * on the main UI thread!
-         */
-        @Override
-        public Cursor runQueryOnBackgroundThread(CharSequence constraint) {
-            return doFilter(constraint.toString());
-        }
-
-        public Object [] getSections() {
-            if (mIndexer == null) {
-                return new String[] { " " };
-            } else {
-                return mIndexer.getSections();
-            }
-        }
-
-        public int getPositionForSection(int sectionIndex) {
-            if (mIndexer == null) {
-                return -1;
-            }
-
-            return mIndexer.getPositionForSection(sectionIndex);
-        }
-
-        public int getSectionForPosition(int position) {
-            if (mIndexer == null) {
-                return -1;
-            }
-
-            return mIndexer.getSectionForPosition(position);
-        }
-
-        @Override
-        public boolean areAllItemsEnabled() {
-            return mMode != MODE_STARRED
-                && !mShowNumberOfContacts;
-        }
-
-        @Override
-        public boolean isEnabled(int position) {
-            if (mShowNumberOfContacts) {
-                if (position == 0) {
-                    return false;
-                }
-                position--;
-            }
-            return position != mFrequentSeparatorPos;
-        }
-
-        @Override
-        public int getCount() {
-            if (!mDataValid) {
-                return 0;
-            }
-            int superCount = super.getCount();
-
-            if (mShowNumberOfContacts && (mSearchMode || superCount > 0)) {
-                // We don't want to count this header if it's the only thing visible, so that
-                // the empty text will display.
-                superCount++;
-            }
-
-            if (mSearchMode && mMode != MODE_PICK_MULTIPLE_PHONES) {
-                // Last element in the list is the "Find
-                superCount++;
-            }
-
-            // We do not show the "Create New" button in Search mode
-            if ((mMode & MODE_MASK_CREATE_NEW) != 0 && !mSearchMode) {
-                // Count the "Create new contact" line
-                superCount++;
-            }
-
-            if (mMode == MODE_PICK_MULTIPLE_PHONES) {
-                superCount += mPhoneNumberAdapter.getCount();
-            }
-
-            if (mFrequentSeparatorPos != ListView.INVALID_POSITION) {
-                // When showing strequent list, we have an additional list item - the separator.
-                return superCount + 1;
-            } else {
-                return superCount;
-            }
-        }
-
-        /**
-         * Gets the actual count of contacts and excludes all the headers.
-         */
-        public int getRealCount() {
-            return super.getCount();
-        }
-
-        private int getRealPosition(int pos) {
-            if (mShowNumberOfContacts) {
-                pos--;
-            }
-
-            if ((mMode & MODE_MASK_CREATE_NEW) != 0 && !mSearchMode) {
-                return pos - 1;
-            }
-
-            if (mMode == MODE_PICK_MULTIPLE_PHONES) {
-                pos -= mPhoneNumberAdapter.getCount();
-            }
-
-            if (mFrequentSeparatorPos == ListView.INVALID_POSITION) {
-                // No separator, identity map
-                return pos;
-            } else if (pos <= mFrequentSeparatorPos) {
-                // Before or at the separator, identity map
-                return pos;
-            } else {
-                // After the separator, remove 1 from the pos to get the real underlying pos
-                return pos - 1;
-            }
-        }
-
-        @Override
-        public Object getItem(int pos) {
-            if (isSearchAllContactsItemPosition(pos)){
-                return null;
-            } else {
-                int realPosition = getRealPosition(pos);
-                if (realPosition < 0) {
-                    return null;
-                }
-                return super.getItem(realPosition);
-            }
-        }
-
-        @Override
-        public long getItemId(int pos) {
-            if (isSearchAllContactsItemPosition(pos)) {
-                return 0;
-            }
-            int realPosition = getRealPosition(pos);
-            if (realPosition < 0) {
-                return 0;
-            }
-            return super.getItemId(realPosition);
-        }
-
-        public void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount,
-                int totalItemCount) {
-            if (view instanceof PinnedHeaderListView) {
-                ((PinnedHeaderListView)view).configureHeaderView(firstVisibleItem);
-            }
-        }
-
-        public void onScrollStateChanged(AbsListView view, int scrollState) {
-            if (mHighlightWhenScrolling) {
-                if (scrollState != OnScrollListener.SCROLL_STATE_IDLE) {
-                    mHighlightingAnimation.startHighlighting();
-                } else {
-                    mHighlightingAnimation.stopHighlighting();
-                }
-            }
-
-            if (scrollState == OnScrollListener.SCROLL_STATE_FLING) {
-                mPhotoLoader.pause();
-            } else if (mDisplayPhotos) {
-                mPhotoLoader.resume();
-            }
-        }
-
-        /**
-         * Computes the state of the pinned header.  It can be invisible, fully
-         * visible or partially pushed up out of the view.
-         */
-        public int getPinnedHeaderState(int position) {
-            if (mIndexer == null || mCursor == null || mCursor.getCount() == 0) {
-                return PINNED_HEADER_GONE;
-            }
-
-            int realPosition = getRealPosition(position);
-            if (realPosition < 0) {
-                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) {
-                return PINNED_HEADER_PUSHED_UP;
-            }
-
-            return PINNED_HEADER_VISIBLE;
-        }
-
-        /**
-         * Configures the pinned header by setting the appropriate text label
-         * and also adjusting color if necessary.  The color needs to be
-         * adjusted when the pinned header is being pushed up from the view.
-         */
-        public void configurePinnedHeader(View header, int position, int alpha) {
-            PinnedHeaderCache cache = (PinnedHeaderCache)header.getTag();
-            if (cache == null) {
-                cache = new PinnedHeaderCache();
-                cache.titleView = (TextView)header.findViewById(R.id.header_text);
-                cache.textColor = cache.titleView.getTextColors();
-                cache.background = header.getBackground();
-                header.setTag(cache);
-            }
-
-            int realPosition = getRealPosition(position);
-            int section = getSectionForPosition(realPosition);
-
-            String title = (String)mIndexer.getSections()[section];
-            cache.titleView.setText(title);
-
-            if (alpha == 255) {
-                // Opaque: use the default background, and the original text color
-                header.setBackgroundDrawable(cache.background);
-                cache.titleView.setTextColor(cache.textColor);
-            } else {
-                // Faded: use a solid color approximation of the background, and
-                // a translucent text color
-                header.setBackgroundColor(Color.rgb(
-                        Color.red(mPinnedHeaderBackgroundColor) * alpha / 255,
-                        Color.green(mPinnedHeaderBackgroundColor) * alpha / 255,
-                        Color.blue(mPinnedHeaderBackgroundColor) * alpha / 255));
-
-                int textColor = cache.textColor.getDefaultColor();
-                cache.titleView.setTextColor(Color.argb(alpha,
-                        Color.red(textColor), Color.green(textColor), Color.blue(textColor)));
-            }
-        }
-    }
-
-    /**
-     * This class is the adapter for the phone numbers which may not be found in the contacts. It is
-     * called in ContactItemListAdapter in MODE_PICK_MULTIPLE_PHONES mode and shouldn't be a adapter
-     * for any View due to the missing implementation of getItem and getItemId.
-     */
-    private class PhoneNumberAdapter extends BaseAdapter {
-        public static final long INVALID_PHONE_ID = -1;
-
-        /** The initial phone numbers */
-        private List<String> mPhoneNumbers;
-
-        /** The phone numbers after the filtering */
-        private ArrayList<String> mFilteredPhoneNumbers = new ArrayList<String>();
-
-        private Context mContext;
-
-        /** The position where this Adapter Phone numbers start*/
-        private int mStartPos;
-
-        public PhoneNumberAdapter(Context context, final List<String> phoneNumbers) {
-            init(context, phoneNumbers);
-        }
-
-        private void init(Context context, final List<String> phoneNumbers) {
-            mStartPos = (mMode & MODE_MASK_SHOW_NUMBER_OF_CONTACTS) != 0 ? 1 : 0;
-            mContext = context;
-            if (phoneNumbers != null) {
-                mFilteredPhoneNumbers.addAll(phoneNumbers);
-                mPhoneNumbers = phoneNumbers;
-            } else {
-                mPhoneNumbers = new ArrayList<String>();
-            }
-        }
-
-        public int getCount() {
-            int filteredCount = mFilteredPhoneNumbers.size();
-            if (filteredCount == 0) {
-                return 0;
-            }
-            // Count on the separator
-            return 1 + filteredCount;
-        }
-
-        public Object getItem(int position) {
-            // This method is not used currently.
-            throw new RuntimeException("This method is not implemented");
-        }
-
-        public long getItemId(int position) {
-            // This method is not used currently.
-            throw new RuntimeException("This method is not implemented");
-        }
-
-        /**
-         * @return the initial phone numbers, the zero length array is returned when there is no
-         * initial numbers.
-         */
-        public final List<String> getPhoneNumbers() {
-            return mPhoneNumbers;
-        }
-
-        /**
-         * @return the filtered phone numbers, the zero size ArrayList is returned when there is no
-         * initial numbers.
-         */
-        public ArrayList<String> getFilteredPhoneNumbers() {
-            return mFilteredPhoneNumbers;
-        }
-
-        public View getView(int position, View convertView, ViewGroup parent) {
-            int viewCount = getCount();
-            if (viewCount == 0) {
-                return null;
-            }
-            // Separator
-            if (position == mStartPos) {
-                TextView view;
-                if (convertView != null && convertView instanceof TextView) {
-                    view = (TextView) convertView;
-                } else {
-                    LayoutInflater inflater =
-                        (LayoutInflater) mContext.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
-                    view = (TextView) inflater.inflate(R.layout.list_separator, parent, false);
-                }
-                view.setText(R.string.unknown_contacts_separator);
-                return view;
-            }
-            // PhoneNumbers start from position of startPos + 1
-            if (position >= mStartPos + 1 && position < mStartPos + viewCount) {
-                View view;
-                if (convertView != null && convertView.getTag() != null &&
-                        convertView.getTag() instanceof ContactListItemCache) {
-                    view = convertView;
-                } else {
-                    view = mAdapter.newView(mContext, null, parent);
-                }
-                bindView(view, mFilteredPhoneNumbers.get(position - 1 - mStartPos));
-                return view;
-            }
-            return null;
-        }
-
-        @Override
-        public int getItemViewType(int position) {
-            return position == mStartPos ? IGNORE_ITEM_VIEW_TYPE : super.getItemViewType(position);
-        }
-
-        private void bindView(View view, final String label) {
-            ContactListItemView itemView = (ContactListItemView) view;
-            final ContactListItemCache cache = (ContactListItemCache) view.getTag();
-            itemView.getNameTextView().setText(label);
-            CheckBox checkBox = itemView.getCheckBoxView();
-            checkBox.setChecked(mUserSelection.isSelected(label));
-            itemView.getChipView().setBackgroundResource(0);
-            cache.phoneId = INVALID_PHONE_ID;
-            cache.phoneNumber = label;
-            checkBox.setTag(cache);
-        }
-
-        public void doFilter(final String constraint, boolean selectedOnly) {
-            if (mPhoneNumbers == null) {
-                return;
-            }
-            mFilteredPhoneNumbers.clear();
-            for (String number : mPhoneNumbers) {
-                if (selectedOnly && !mUserSelection.isSelected(number) ||
-                        !TextUtils.isEmpty(constraint) && !number.startsWith(constraint)) {
-                    continue;
-                }
-                mFilteredPhoneNumbers.add(number);
-            }
-        }
-    }
-
-    /**
-     * This class is used to keep the user's selection in MODE_PICK_MULTIPLE_PHONES mode.
-     */
-    private class UserSelection {
-        public static final String EXTRA_SELECTION =
-            "com.android.contacts.ContactsListActivity.UserSelection.extra.SELECTION";
-        private static final String SELECTED_UNKNOWN_PHONES_KEY = "selected_unknown_phones";
-        private static final String SELECTED_PHONE_IDS_KEY = "selected_phone_id";
-
-        /** The PHONE_ID of selected number in user contacts*/
-        private HashSet<Long> mSelectedPhoneIds = new HashSet<Long>();
-
-        /** The selected phone numbers in the PhoneNumberAdapter */
-        private HashSet<String> mSelectedPhoneNumbers = new HashSet<String>();
-
-        /**
-         * @param phoneNumbers the phone numbers are selected.
-         */
-        public UserSelection(final List<String> phoneNumbers, final List<Long> phoneIds) {
-            init(phoneNumbers, phoneIds);
-        }
-
-        /**
-         * Creates from a instance state.
-         */
-        public UserSelection (Bundle icicle) {
-            init(icicle.getStringArray(SELECTED_UNKNOWN_PHONES_KEY),
-                    icicle.getLongArray(SELECTED_PHONE_IDS_KEY));
-        }
-
-        public void saveInstanceState(Bundle icicle) {
-            int selectedUnknownsCount = mSelectedPhoneNumbers.size();
-            if (selectedUnknownsCount > 0) {
-                String[] selectedUnknows = new String[selectedUnknownsCount];
-                icicle.putStringArray(SELECTED_UNKNOWN_PHONES_KEY,
-                        mSelectedPhoneNumbers.toArray(selectedUnknows));
-            }
-            int selectedKnownsCount = mSelectedPhoneIds.size();
-            if (selectedKnownsCount > 0) {
-                long[] selectedPhoneIds = new long [selectedKnownsCount];
-                int index = 0;
-                for (Long phoneId : mSelectedPhoneIds) {
-                    selectedPhoneIds[index++] = phoneId.longValue();
-                }
-                icicle.putLongArray(SELECTED_PHONE_IDS_KEY, selectedPhoneIds);
-
-            }
-        }
-
-        private void init(final String[] selecedUnknownNumbers, final long[] selectedPhoneIds) {
-            if (selecedUnknownNumbers != null) {
-                for (String number : selecedUnknownNumbers) {
-                    setPhoneSelected(number, true);
-                }
-            }
-            if (selectedPhoneIds != null) {
-                for (long id : selectedPhoneIds) {
-                    setPhoneSelected(id, true);
-                }
-            }
-        }
-
-        private void init(final List<String> selecedUnknownNumbers,
-                final List<Long> selectedPhoneIds) {
-            if (selecedUnknownNumbers != null) {
-                setPhoneNumbersSelected(selecedUnknownNumbers, true);
-            }
-            if (selectedPhoneIds != null) {
-                setPhoneIdsSelected(selectedPhoneIds, true);
-            }
-        }
-
-        private void setPhoneNumbersSelected(final List<String> phoneNumbers, boolean selected) {
-            if (selected) {
-                mSelectedPhoneNumbers.addAll(phoneNumbers);
-            } else {
-                mSelectedPhoneNumbers.removeAll(phoneNumbers);
-            }
-        }
-
-        private void setPhoneIdsSelected(final List<Long> phoneIds, boolean selected) {
-            if (selected) {
-                mSelectedPhoneIds.addAll(phoneIds);
-            } else {
-                mSelectedPhoneIds.removeAll(phoneIds);
-            }
-        }
-
-        public void setPhoneSelected(final String phoneNumber, boolean selected) {
-            if (!TextUtils.isEmpty(phoneNumber)) {
-                if (selected) {
-                    mSelectedPhoneNumbers.add(phoneNumber);
-                } else {
-                    mSelectedPhoneNumbers.remove(phoneNumber);
-                }
-            }
-        }
-
-        public void setPhoneSelected(long phoneId, boolean selected) {
-            if (selected) {
-                mSelectedPhoneIds.add(phoneId);
-            } else {
-                mSelectedPhoneIds.remove(phoneId);
-            }
-        }
-
-        public boolean isSelected(long phoneId) {
-            return mSelectedPhoneIds.contains(phoneId);
-        }
-
-        public boolean isSelected(final String phoneNumber) {
-            return mSelectedPhoneNumbers.contains(phoneNumber);
-        }
-
-        public void setAllPhonesSelected(boolean selected) {
-            if (selected) {
-                Cursor cursor = mAdapter.getCursor();
-                if (cursor != null) {
-                    int backupPos = cursor.getPosition();
-                    cursor.moveToPosition(-1);
-                    while (cursor.moveToNext()) {
-                        setPhoneSelected(cursor.getLong(PHONE_ID_COLUMN_INDEX), true);
-                    }
-                    cursor.moveToPosition(backupPos);
-                }
-                for (String number : mPhoneNumberAdapter.getFilteredPhoneNumbers()) {
-                    setPhoneSelected(number, true);
-                }
-            } else {
-                mSelectedPhoneIds.clear();
-                mSelectedPhoneNumbers.clear();
-            }
-        }
-
-        public boolean isAllSelected() {
-            return selectedCount() == mPhoneNumberAdapter.getFilteredPhoneNumbers().size()
-                    + mAdapter.getCount();
-        }
-
-        public int selectedCount() {
-            return mSelectedPhoneNumbers.size() + mSelectedPhoneIds.size();
-        }
-
-        public Iterator<Long> getSelectedPhonIds() {
-            return mSelectedPhoneIds.iterator();
-        }
-
-        private int fillSelectedNumbers(Uri[] uris, int from) {
-            int count = mSelectedPhoneNumbers.size();
-            if (count == 0)
-                return from;
-            // Below loop keeps phone numbers by initial order.
-            List<String> phoneNumbers = mPhoneNumberAdapter.getPhoneNumbers();
-            for (String phoneNumber : phoneNumbers) {
-                if (isSelected(phoneNumber)) {
-                    Uri.Builder ub = new Uri.Builder();
-                    ub.scheme(TEL_SCHEME);
-                    ub.encodedOpaquePart(phoneNumber);
-                    uris[from++] = ub.build();
-                }
-            }
-            return from;
-        }
-
-        private int fillSelectedPhoneIds(Uri[] uris, int from) {
-            int count = mSelectedPhoneIds.size();
-            if (count == 0)
-                return from;
-            Iterator<Long> it = mSelectedPhoneIds.iterator();
-            while (it.hasNext()) {
-                uris[from++] = ContentUris.withAppendedId(Phone.CONTENT_URI, it.next());
-            }
-            return from;
-        }
-
-        private Uri[] getSelected() {
-            Uri[] uris = new Uri[mSelectedPhoneNumbers.size() + mSelectedPhoneIds.size()];
-            int from  = fillSelectedNumbers(uris, 0);
-            fillSelectedPhoneIds(uris, from);
-            return uris;
-        }
-
-        public Intent createSelectionIntent() {
-            Intent intent = new Intent();
-            intent.putExtra(Intents.EXTRA_PHONE_URIS, getSelected());
-
-            return intent;
-        }
-
-        public void fillSelectionForSearchMode(Bundle bundle) {
-            bundle.putParcelableArray(EXTRA_SELECTION, getSelected());
-        }
-    }
 }
diff --git a/src/com/android/contacts/ContactsSearchActivity.java b/src/com/android/contacts/ContactsSearchActivity.java
new file mode 100644
index 0000000..3f1e62c
--- /dev/null
+++ b/src/com/android/contacts/ContactsSearchActivity.java
@@ -0,0 +1,21 @@
+/*
+ * 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;
+
+public class ContactsSearchActivity extends ContactsListActivity {
+
+}
\ No newline at end of file
diff --git a/src/com/android/contacts/DialtactsActivity.java b/src/com/android/contacts/DialtactsActivity.java
index 208fbf4..afb8606 100644
--- a/src/com/android/contacts/DialtactsActivity.java
+++ b/src/com/android/contacts/DialtactsActivity.java
@@ -44,6 +44,10 @@
     private static final String FAVORITES_ENTRY_COMPONENT =
             "com.android.contacts.DialtactsFavoritesEntryActivity";
 
+    /** Opens the Contacts app in the state the user has last set it to */
+    private static final String CONTACTS_LAUNCH_ACTIVITY =
+            "com.android.contacts.ContactsLaunchActivity";
+
     private static final int TAB_INDEX_DIALER = 0;
     private static final int TAB_INDEX_CALL_LOG = 1;
     private static final int TAB_INDEX_CONTACTS = 2;
@@ -57,10 +61,20 @@
     static final String PREF_FAVORITES_AS_CONTACTS = "favorites_as_contacts";
     static final boolean PREF_FAVORITES_AS_CONTACTS_DEFAULT = false;
 
+    /** Last manually selected tab index */
+    private static final String PREF_LAST_MANUALLY_SELECTED_TAB = "last_manually_selected_tab";
+    private static final int PREF_LAST_MANUALLY_SELECTED_TAB_DEFAULT = TAB_INDEX_DIALER;
+
     private TabHost mTabHost;
     private String mFilterText;
     private Uri mDialUri;
 
+    /**
+     * The index of the tab that has last been manually selected (the user clicked on a tab).
+     * This value does not keep track of programmatically set Tabs (e.g. Call Log after a Call)
+     */
+    private int mLastManuallySelectedTab;
+
     @Override
     protected void onCreate(Bundle icicle) {
         super.onCreate(icicle);
@@ -80,6 +94,11 @@
         setupContactsTab();
         setupFavoritesTab();
 
+        // Load the last manually loaded tab
+        final SharedPreferences prefs = getSharedPreferences(PREFS_DIALTACTS, MODE_PRIVATE);
+        mLastManuallySelectedTab = prefs.getInt(PREF_LAST_MANUALLY_SELECTED_TAB,
+                PREF_LAST_MANUALLY_SELECTED_TAB_DEFAULT);
+
         setCurrentTab(intent);
 
         if (intent.getAction().equals(UI.FILTER_CONTACTS_ACTION)
@@ -92,13 +111,15 @@
     protected void onPause() {
         super.onPause();
 
-        int currentTabIndex = mTabHost.getCurrentTab();
+        final int currentTabIndex = mTabHost.getCurrentTab();
+        final SharedPreferences.Editor editor =
+                getSharedPreferences(PREFS_DIALTACTS, MODE_PRIVATE).edit();
         if (currentTabIndex == TAB_INDEX_CONTACTS || currentTabIndex == TAB_INDEX_FAVORITES) {
-            SharedPreferences.Editor editor = getSharedPreferences(PREFS_DIALTACTS, MODE_PRIVATE)
-                    .edit();
             editor.putBoolean(PREF_FAVORITES_AS_CONTACTS, currentTabIndex == TAB_INDEX_FAVORITES);
-            editor.commit();
         }
+        editor.putInt(PREF_LAST_MANUALLY_SELECTED_TAB, mLastManuallySelectedTab);
+
+        editor.commit();
     }
 
     private void fixIntent(Intent intent) {
@@ -181,7 +202,7 @@
     /**
      * Sets the current tab based on the intent's request type
      *
-     * @param recentCallsRequest true is the recent calls tab is desired, false otherwise
+     * @param intent Intent that contains information about which tab should be selected
      */
     private void setCurrentTab(Intent intent) {
         // If we got here by hitting send and we're in call forward along to the in-call activity
@@ -202,6 +223,10 @@
         // state and instead reload their state from the parent's intent
         intent.putExtra(EXTRA_IGNORE_STATE, true);
 
+        // Remember the old manually selected tab index so that it can be restored if it is
+        // overwritten by one of the programmatic tab selections
+        final int savedTabIndex = mLastManuallySelectedTab;
+
         // Choose the tab based on the inbound intent
         String componentName = intent.getComponent().getClassName();
         if (getClass().getName().equals(componentName)) {
@@ -212,6 +237,8 @@
             }
         } else if (FAVORITES_ENTRY_COMPONENT.equals(componentName)) {
             mTabHost.setCurrentTab(TAB_INDEX_FAVORITES);
+        } else if (CONTACTS_LAUNCH_ACTIVITY.equals(componentName)) {
+            mTabHost.setCurrentTab(mLastManuallySelectedTab);
         } else {
             SharedPreferences prefs = getSharedPreferences(PREFS_DIALTACTS, MODE_PRIVATE);
             boolean favoritesAsContacts = prefs.getBoolean(PREF_FAVORITES_AS_CONTACTS,
@@ -223,6 +250,9 @@
             }
         }
 
+        // Restore to the previous manual selection
+        mLastManuallySelectedTab = savedTabIndex;
+
         // Tell the children activities that they should honor their saved states
         // instead of the state from the parent's intent
         intent.putExtra(EXTRA_IGNORE_STATE, false);
@@ -337,5 +367,9 @@
         if (activity != null) {
             activity.onWindowFocusChanged(true);
         }
+
+        // Remember this tab index. This function is also called, if the tab is set automatically
+        // in which case the setter (setCurrentTab) has to set this to its old value afterwards
+        mLastManuallySelectedTab = mTabHost.getCurrentTab();
     }
 }
diff --git a/src/com/android/contacts/ExportVCardActivity.java b/src/com/android/contacts/ExportVCardActivity.java
index 5bccc7a..a7a3ba2 100644
--- a/src/com/android/contacts/ExportVCardActivity.java
+++ b/src/com/android/contacts/ExportVCardActivity.java
@@ -26,6 +26,7 @@
 import android.os.Handler;
 import android.os.PowerManager;
 import android.pim.vcard.VCardComposer;
+import android.pim.vcard.VCardConfig;
 import android.text.TextUtils;
 import android.util.Log;
 
@@ -146,7 +147,8 @@
                     return;
                 }
 
-                composer = new VCardComposer(ExportVCardActivity.this, mVCardTypeStr, true);
+                final int vcardType = VCardConfig.getVCardTypeFromString(mVCardTypeStr);
+                composer = new VCardComposer(ExportVCardActivity.this, vcardType, true);
                 /*int vcardType = (VCardConfig.VCARD_TYPE_V21_GENERIC |
                         VCardConfig.FLAG_USE_QP_TO_PRIMARY_PROPERTIES);
                 composer = new VCardComposer(ExportVCardActivity.this, vcardType, true);*/
diff --git a/src/com/android/contacts/ImportVCardService.java b/src/com/android/contacts/ImportVCardService.java
index 40b0757..8da8651 100644
--- a/src/com/android/contacts/ImportVCardService.java
+++ b/src/com/android/contacts/ImportVCardService.java
@@ -13,7 +13,6 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-
 package com.android.contacts;
 
 import android.accounts.Account;
@@ -226,7 +225,7 @@
                 final String charset = detector.getEstimatedCharset();  // May be null.
 
                 final VCardEntryConstructor constructor =
-                        new VCardEntryConstructor(charset, vcardType, account);
+                        new VCardEntryConstructor(vcardType, account, charset);
                 final VCardEntryCommitter committer = new VCardEntryCommitter(mResolver);
                 constructor.addEntryHandler(committer);
                 constructor.addEntryHandler(new ProgressNotifier(id));
diff --git a/src/com/android/contacts/JoinContactActivity.java b/src/com/android/contacts/JoinContactActivity.java
index 47c7547..5e737a7 100644
--- a/src/com/android/contacts/JoinContactActivity.java
+++ b/src/com/android/contacts/JoinContactActivity.java
@@ -16,8 +16,12 @@
 
 package com.android.contacts;
 
+
+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;
@@ -28,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;
 
 /**
@@ -60,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;
@@ -75,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) {
@@ -83,7 +79,9 @@
                     + EXTRA_TARGET_CONTACT_ID);
             setResult(RESULT_CANCELED);
             finish();
+            return null;
         }
+        return new JoinContactListConfiguration(this, this);
     }
 
     @Override
@@ -94,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);
@@ -152,6 +151,7 @@
     }
 
     @Override
+    public
     Cursor doFilter(String filter) {
         throw new UnsupportedOperationException();
     }
@@ -184,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
@@ -199,184 +199,4 @@
 
         super.onQueryComplete(cursor);
     }
-
-    private class JoinContactListAdapter extends ContactItemListAdapter {
-        Cursor mSuggestionsCursor;
-        int mSuggestionsCursorCount;
-
-        public JoinContactListAdapter(Context 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;
-        }
-
-        private 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
new file mode 100644
index 0000000..6c27c3e
--- /dev/null
+++ b/src/com/android/contacts/MultiplePhonePickerActivity.java
@@ -0,0 +1,452 @@
+/*
+ * 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;
+
+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;
+import android.content.ContentResolver;
+import android.content.ContentUris;
+import android.content.Intent;
+import android.database.Cursor;
+import android.database.MatrixCursor;
+import android.net.Uri;
+import android.os.Bundle;
+import android.os.Handler;
+import android.os.Parcelable;
+import android.provider.ContactsContract.Intents;
+import android.provider.ContactsContract.ProviderStatus;
+import android.provider.ContactsContract.CommonDataKinds.Phone;
+import android.text.TextUtils;
+import android.util.SparseIntArray;
+import android.view.Menu;
+import android.view.MenuInflater;
+import android.view.MenuItem;
+import android.view.View;
+import android.view.ViewStub;
+import android.view.View.OnClickListener;
+import android.view.animation.AnimationUtils;
+import android.widget.Button;
+import android.widget.CheckBox;
+
+import java.util.ArrayList;
+import java.util.Iterator;
+import java.util.List;
+
+/**
+ * Displays of phone numbers and allows selection of multiple numbers.
+ */
+public class MultiplePhonePickerActivity extends ContactsListActivity {
+    /**
+     * User selected phone number and id in MODE_PICK_MULTIPLE_PHONES mode.
+     */
+    public final MultiplePhoneSelection mUserSelection = new MultiplePhoneSelection(this);
+
+    /**
+     * The adapter for the phone numbers, used in MODE_PICK_MULTIPLE_PHONES mode.
+     */
+    public final MultiplePhoneExtraAdapter mPhoneNumberAdapter =
+            new MultiplePhoneExtraAdapter(this, this, mUserSelection);
+
+    private static int[] CHIP_COLOR_ARRAY = {
+        R.drawable.appointment_indicator_leftside_1,
+        R.drawable.appointment_indicator_leftside_2,
+        R.drawable.appointment_indicator_leftside_3,
+        R.drawable.appointment_indicator_leftside_4,
+        R.drawable.appointment_indicator_leftside_5,
+        R.drawable.appointment_indicator_leftside_6,
+        R.drawable.appointment_indicator_leftside_7,
+        R.drawable.appointment_indicator_leftside_8,
+        R.drawable.appointment_indicator_leftside_9,
+        R.drawable.appointment_indicator_leftside_10,
+        R.drawable.appointment_indicator_leftside_11,
+        R.drawable.appointment_indicator_leftside_12,
+        R.drawable.appointment_indicator_leftside_13,
+        R.drawable.appointment_indicator_leftside_14,
+        R.drawable.appointment_indicator_leftside_15,
+        R.drawable.appointment_indicator_leftside_16,
+        R.drawable.appointment_indicator_leftside_17,
+        R.drawable.appointment_indicator_leftside_18,
+        R.drawable.appointment_indicator_leftside_19,
+        R.drawable.appointment_indicator_leftside_20,
+        R.drawable.appointment_indicator_leftside_21,
+    };
+
+    /**
+     * This is the map from contact to color index.
+     * A colored chip in MODE_PICK_MULTIPLE_PHONES mode is used to indicate the number of phone
+     * numbers belong to one contact
+     */
+    SparseIntArray mContactColor = new SparseIntArray();
+
+    /**
+     * UI control of action panel in MODE_PICK_MULTIPLE_PHONES mode.
+     */
+    private View mFooterView;
+
+    /**
+     * Display only selected recipients or not in MODE_PICK_MULTIPLE_PHONES mode
+     */
+    public boolean mShowSelectedOnly = false;
+
+    public OnClickListener mCheckBoxClickerListener = new OnClickListener () {
+        public void onClick(View v) {
+            final ContactListItemCache cache = (ContactListItemCache) v.getTag();
+            if (cache.phoneId != MultiplePhoneExtraAdapter.INVALID_PHONE_ID) {
+                mUserSelection.setPhoneSelected(cache.phoneId, ((CheckBox) v).isChecked());
+            } else {
+                mUserSelection.setPhoneSelected(cache.phoneNumber,
+                        ((CheckBox) v).isChecked());
+            }
+            updateWidgets(true);
+        }
+    };
+
+    @Override
+    protected void onCreate(Bundle icicle) {
+        super.onCreate(icicle);
+        initMultiPicker(getIntent());
+    }
+
+    @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();
+            mFooterView = stubView.findViewById(R.id.footer);
+            mFooterView.setVisibility(View.GONE);
+            Button doneButton = (Button) stubView.findViewById(R.id.done);
+            doneButton.setOnClickListener(this);
+            Button revertButton = (Button) stubView.findViewById(R.id.revert);
+            revertButton.setOnClickListener(this);
+        }
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public void onClick(View v) {
+        int id = v.getId();
+        switch (id) {
+            case R.id.done:
+                setMultiPickerResult();
+                finish();
+                break;
+            case R.id.revert:
+                finish();
+                break;
+        }
+    }
+
+    @Override
+    protected void onSaveInstanceState(Bundle icicle) {
+        super.onSaveInstanceState(icicle);
+        if (mList != null) {
+            if (mUserSelection != null) {
+                mUserSelection.saveInstanceState(icicle);
+            }
+        }
+    }
+
+    @Override
+    protected void onRestoreInstanceState(Bundle icicle) {
+        super.onRestoreInstanceState(icicle);
+        // Retrieve list state. This will be applied after the QueryHandler has run
+        mUserSelection.restoreInstanceState(icicle);
+    }
+
+    @Override
+    public boolean onCreateOptionsMenu(Menu menu) {
+        super.onCreateOptionsMenu(menu);
+
+        final MenuInflater inflater = getMenuInflater();
+        inflater.inflate(R.menu.pick, menu);
+        return true;
+    }
+
+    @Override
+    public boolean onPrepareOptionsMenu(Menu menu) {
+        if (mShowSelectedOnly) {
+            menu.findItem(R.id.menu_display_selected).setVisible(false);
+            menu.findItem(R.id.menu_display_all).setVisible(true);
+            menu.findItem(R.id.menu_select_all).setVisible(false);
+            menu.findItem(R.id.menu_select_none).setVisible(false);
+            return true;
+        }
+        menu.findItem(R.id.menu_display_all).setVisible(false);
+        menu.findItem(R.id.menu_display_selected).setVisible(true);
+        if (mUserSelection.isAllSelected()) {
+            menu.findItem(R.id.menu_select_all).setVisible(false);
+            menu.findItem(R.id.menu_select_none).setVisible(true);
+        } else {
+            menu.findItem(R.id.menu_select_all).setVisible(true);
+            menu.findItem(R.id.menu_select_none).setVisible(false);
+        }
+        return true;
+    }
+
+    @Override
+    public boolean onOptionsItemSelected(MenuItem item) {
+        switch (item.getItemId()) {
+            case R.id.menu_select_all: {
+                mUserSelection.setAllPhonesSelected(true);
+                checkAll(true);
+                updateWidgets(true);
+                return true;
+            }
+            case R.id.menu_select_none: {
+                mUserSelection.setAllPhonesSelected(false);
+                checkAll(false);
+                updateWidgets(true);
+                return true;
+            }
+            case R.id.menu_display_selected: {
+                mShowSelectedOnly = true;
+                startQuery();
+                return true;
+            }
+            case R.id.menu_display_all: {
+                mShowSelectedOnly = false;
+                startQuery();
+                return true;
+            }
+        }
+        return super.onOptionsItemSelected(item);
+    }
+
+    @Override
+    public void startSearch(String initialQuery, boolean selectInitialQuery, Bundle appSearchData,
+            boolean globalSearch) {
+        if (mProviderStatus != ProviderStatus.STATUS_NORMAL) {
+            return;
+        }
+
+        if (globalSearch) {
+            super.startSearch(initialQuery, selectInitialQuery, appSearchData, globalSearch);
+        } else {
+            if (!mSearchMode && (mMode & MODE_MASK_NO_FILTER) == 0) {
+                if ((mMode & MODE_MASK_PICKER) != 0) {
+                    Bundle extras = getIntent().getExtras();
+                    if (extras == null) {
+                        extras = new Bundle();
+                    }
+                    mUserSelection.fillSelectionForSearchMode(extras);
+                    ContactsSearchManager.startSearchForResult(this, initialQuery,
+                            SUBACTIVITY_FILTER, extras);
+                } else {
+                    ContactsSearchManager.startSearch(this, initialQuery);
+                }
+            }
+        }
+    }
+
+    @Override
+    public void onBackPressed() {
+        setMultiPickerResult();
+        super.onBackPressed();
+    }
+
+    @Override
+    protected void startQuery(Uri uri, String[] projection) {
+        // Filter unknown phone numbers first.
+        mPhoneNumberAdapter.doFilter(null, mShowSelectedOnly);
+        if (mShowSelectedOnly) {
+            StringBuilder idSetBuilder = new StringBuilder();
+            Iterator<Long> itr = mUserSelection.getSelectedPhonIds();
+            if (itr.hasNext()) {
+                idSetBuilder.append(Long.toString(itr.next()));
+            }
+            while (itr.hasNext()) {
+                idSetBuilder.append(',');
+                idSetBuilder.append(Long.toString(itr.next()));
+            }
+            String whereClause = Phone._ID + " IN (" + idSetBuilder.toString() + ")";
+            mQueryHandler.startQuery(QUERY_TOKEN, null, uri, projection, whereClause, null,
+                    getSortOrder(projection));
+        } else {
+            mQueryHandler.startQuery(QUERY_TOKEN, null, uri,
+                    projection, CLAUSE_ONLY_VISIBLE, null, getSortOrder(projection));
+        }
+    }
+
+    @Override
+    public Cursor doFilter(String filter) {
+        String[] projection = getProjectionForQuery();
+        if (mSearchMode && TextUtils.isEmpty(getTextFilter())) {
+            return new MatrixCursor(projection);
+        }
+
+        final ContentResolver resolver = getContentResolver();
+        // Filter phone numbers as well.
+        mPhoneNumberAdapter.doFilter(filter, mShowSelectedOnly);
+
+        Uri uri = getUriToQuery();
+        if (!TextUtils.isEmpty(filter)) {
+            uri = Uri.withAppendedPath(Phone.CONTENT_FILTER_URI, Uri.encode(filter));
+        }
+        return resolver.query(uri, projection, CLAUSE_ONLY_VISIBLE, null, getSortOrder(projection));
+    }
+
+    private void initMultiPicker(final Intent intent) {
+        final Handler handler = new Handler();
+        // TODO : Shall we still show the progressDialog in search mode.
+        final ProgressDialog progressDialog = new ProgressDialog(this);
+        progressDialog.setMessage(getText(R.string.adding_recipients));
+        progressDialog.setIndeterminate(true);
+        progressDialog.setCancelable(false);
+
+        final Runnable showProgress = new Runnable() {
+            public void run() {
+                progressDialog.show();
+            }
+        };
+        handler.postDelayed(showProgress, 1);
+
+        new Thread(new Runnable() {
+            public void run() {
+                try {
+                    loadSelectionFromIntent(intent);
+                } finally {
+                    handler.removeCallbacks(showProgress);
+                    progressDialog.dismiss();
+                }
+                final Runnable populateWorker = new Runnable() {
+                    public void run() {
+                        if (mAdapter != null) {
+                            mAdapter.notifyDataSetChanged();
+                        }
+                        updateWidgets(false);
+                    }
+                };
+                handler.post(populateWorker);
+            }
+        }).start();
+    }
+
+    private void getPhoneNumbersOrIdsFromURIs(final Parcelable[] uris,
+            final List<String> phoneNumbers, final List<Long> phoneIds) {
+        if (uris != null) {
+            for (Parcelable paracelable : uris) {
+                Uri uri = (Uri) paracelable;
+                if (uri == null) continue;
+                String scheme = uri.getScheme();
+                if (phoneNumbers != null && "tel".equals(scheme)) {
+                    phoneNumbers.add(uri.getSchemeSpecificPart());
+                } else if (phoneIds != null && "content".equals(scheme)) {
+                    phoneIds.add(ContentUris.parseId(uri));
+                }
+            }
+        }
+    }
+
+    private void loadSelectionFromIntent(Intent intent) {
+        Parcelable[] uris = intent.getParcelableArrayExtra(Intents.EXTRA_PHONE_URIS);
+        ArrayList<String> phoneNumbers = new ArrayList<String>();
+        ArrayList<Long> phoneIds = new ArrayList<Long>();
+        ArrayList<String> selectedPhoneNumbers = null;
+        if (mSearchMode) {
+            // All selection will be read from EXTRA_SELECTION
+            getPhoneNumbersOrIdsFromURIs(uris, phoneNumbers, null);
+            uris = intent.getParcelableArrayExtra(MultiplePhoneSelection.EXTRA_SELECTION);
+            if (uris != null) {
+                selectedPhoneNumbers = new ArrayList<String>();
+                getPhoneNumbersOrIdsFromURIs(uris, selectedPhoneNumbers, phoneIds);
+            }
+        } else {
+            getPhoneNumbersOrIdsFromURIs(uris, phoneNumbers, phoneIds);
+            selectedPhoneNumbers = phoneNumbers;
+        }
+        mPhoneNumberAdapter.setPhoneNumbers(phoneNumbers);
+        mUserSelection.setSelection(selectedPhoneNumbers, phoneIds);
+    }
+
+    private void setMultiPickerResult() {
+        setResult(RESULT_OK, mUserSelection.createSelectionIntent());
+    }
+
+    /**
+     * Go through the cursor and assign the chip color to contact who has more than one phone
+     * numbers.
+     * Assume the cursor is sorted by CONTACT_ID.
+     */
+    public void updateChipColor(Cursor cursor) {
+        if (cursor == null || cursor.getCount() == 0) {
+            return;
+        }
+        mContactColor.clear();
+        int backupPos = cursor.getPosition();
+        cursor.moveToFirst();
+        int color = 0;
+        long prevContactId = cursor.getLong(PHONE_CONTACT_ID_COLUMN_INDEX);
+        while (cursor.moveToNext()) {
+            long contactId = cursor.getLong(PHONE_CONTACT_ID_COLUMN_INDEX);
+            if (prevContactId == contactId) {
+                if (mContactColor.indexOfKey(Long.valueOf(contactId).hashCode()) < 0) {
+                    mContactColor.put(Long.valueOf(contactId).hashCode(), CHIP_COLOR_ARRAY[color]);
+                    color++;
+                    if (color >= CHIP_COLOR_ARRAY.length) {
+                        color = 0;
+                    }
+                }
+            }
+            prevContactId = contactId;
+        }
+        cursor.moveToPosition(backupPos);
+    }
+
+    /**
+     * Get assigned chip color resource id for a given contact, 0 is returned if there is no mapped
+     * resource.
+     */
+    public int getChipColor(long contactId) {
+        return mContactColor.get(Long.valueOf(contactId).hashCode());
+    }
+
+    private void updateWidgets(boolean changed) {
+        int selected = mUserSelection.selectedCount();
+
+        if (selected >= 1) {
+            final String format =
+                getResources().getQuantityString(R.plurals.multiple_picker_title, selected);
+            setTitle(String.format(format, selected));
+        } else {
+            setTitle(getString(R.string.contactsList));
+        }
+
+        if (changed && mFooterView.getVisibility() == View.GONE) {
+            mFooterView.setVisibility(View.VISIBLE);
+            mFooterView.startAnimation(AnimationUtils.loadAnimation(this, R.anim.footer_appear));
+        }
+    }
+
+    private void checkAll(boolean checked) {
+        // TODO fix this. It should iterate over the cursor rather than the views in the list.
+        /*
+        final ListView listView = getListView();
+        int childCount = listView.getChildCount();
+        for (int i = 0; i < childCount; i++) {
+            final ContactListItemView child = (ContactListItemView)listView.getChildAt(i);
+            child.getCheckBoxView().setChecked(checked);
+        }
+        */
+    }
+}
diff --git a/src/com/android/contacts/ViewContactActivity.java b/src/com/android/contacts/ViewContactActivity.java
deleted file mode 100644
index c15a40d..0000000
--- a/src/com/android/contacts/ViewContactActivity.java
+++ /dev/null
@@ -1,1360 +0,0 @@
-/*
- * Copyright (C) 2007 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;
-import com.android.contacts.Collapser.Collapsible;
-import com.android.contacts.model.ContactsSource;
-import com.android.contacts.model.Sources;
-import com.android.contacts.model.ContactsSource.DataKind;
-import com.android.contacts.ui.EditContactActivity;
-import com.android.contacts.util.Constants;
-import com.android.contacts.util.DataStatus;
-import com.android.contacts.util.NotifyingAsyncQueryHandler;
-import com.android.internal.telephony.ITelephony;
-import com.android.internal.widget.ContactHeaderWidget;
-import com.google.android.collect.Lists;
-import com.google.android.collect.Maps;
-
-import android.app.Activity;
-import android.app.AlertDialog;
-import android.app.Dialog;
-import android.content.ActivityNotFoundException;
-import android.content.ContentResolver;
-import android.content.ContentUris;
-import android.content.ContentValues;
-import android.content.Context;
-import android.content.DialogInterface;
-import android.content.Entity;
-import android.content.EntityIterator;
-import android.content.Intent;
-import android.content.Entity.NamedContentValues;
-import android.content.res.Resources;
-import android.database.ContentObserver;
-import android.database.Cursor;
-import android.graphics.drawable.Drawable;
-import android.net.ParseException;
-import android.net.Uri;
-import android.net.WebAddress;
-import android.os.AsyncTask;
-import android.os.Bundle;
-import android.os.Handler;
-import android.os.RemoteException;
-import android.os.ServiceManager;
-import android.provider.ContactsContract;
-import android.provider.ContactsContract.AggregationExceptions;
-import android.provider.ContactsContract.CommonDataKinds;
-import android.provider.ContactsContract.Contacts;
-import android.provider.ContactsContract.Data;
-import android.provider.ContactsContract.DisplayNameSources;
-import android.provider.ContactsContract.RawContacts;
-import android.provider.ContactsContract.RawContactsEntity;
-import android.provider.ContactsContract.StatusUpdates;
-import android.provider.ContactsContract.CommonDataKinds.Email;
-import android.provider.ContactsContract.CommonDataKinds.Im;
-import android.provider.ContactsContract.CommonDataKinds.Nickname;
-import android.provider.ContactsContract.CommonDataKinds.Note;
-import android.provider.ContactsContract.CommonDataKinds.Organization;
-import android.provider.ContactsContract.CommonDataKinds.Phone;
-import android.provider.ContactsContract.CommonDataKinds.StructuredPostal;
-import android.provider.ContactsContract.CommonDataKinds.Website;
-import android.telephony.PhoneNumberUtils;
-import android.text.TextUtils;
-import android.util.Log;
-import android.view.ContextMenu;
-import android.view.KeyEvent;
-import android.view.LayoutInflater;
-import android.view.Menu;
-import android.view.MenuInflater;
-import android.view.MenuItem;
-import android.view.View;
-import android.view.ViewGroup;
-import android.view.Window;
-import android.view.ContextMenu.ContextMenuInfo;
-import android.widget.AdapterView;
-import android.widget.ImageView;
-import android.widget.ListView;
-import android.widget.TextView;
-import android.widget.Toast;
-
-import java.util.ArrayList;
-import java.util.HashMap;
-import java.util.List;
-
-/**
- * Displays the details of a specific contact.
- */
-public class ViewContactActivity extends Activity
-        implements View.OnCreateContextMenuListener, DialogInterface.OnClickListener,
-        AdapterView.OnItemClickListener, NotifyingAsyncQueryHandler.AsyncQueryListener {
-    private static final String TAG = "ViewContact";
-
-    private static final boolean SHOW_SEPARATORS = false;
-
-    private static final int DIALOG_CONFIRM_DELETE = 1;
-    private static final int DIALOG_CONFIRM_READONLY_DELETE = 2;
-    private static final int DIALOG_CONFIRM_MULTIPLE_DELETE = 3;
-    private static final int DIALOG_CONFIRM_READONLY_HIDE = 4;
-
-    private static final int REQUEST_JOIN_CONTACT = 1;
-    private static final int REQUEST_EDIT_CONTACT = 2;
-
-    public static final int MENU_ITEM_MAKE_DEFAULT = 3;
-
-    protected Uri mLookupUri;
-    private ContentResolver mResolver;
-    private ViewAdapter mAdapter;
-    private int mNumPhoneNumbers = 0;
-
-    /**
-     * A list of distinct contact IDs included in the current contact.
-     */
-    private ArrayList<Long> mRawContactIds = new ArrayList<Long>();
-
-    /* package */ ArrayList<ViewEntry> mPhoneEntries = new ArrayList<ViewEntry>();
-    /* package */ ArrayList<ViewEntry> mSmsEntries = new ArrayList<ViewEntry>();
-    /* package */ ArrayList<ViewEntry> mEmailEntries = new ArrayList<ViewEntry>();
-    /* package */ ArrayList<ViewEntry> mPostalEntries = new ArrayList<ViewEntry>();
-    /* package */ ArrayList<ViewEntry> mImEntries = new ArrayList<ViewEntry>();
-    /* package */ ArrayList<ViewEntry> mNicknameEntries = new ArrayList<ViewEntry>();
-    /* package */ ArrayList<ViewEntry> mOrganizationEntries = new ArrayList<ViewEntry>();
-    /* package */ ArrayList<ViewEntry> mGroupEntries = new ArrayList<ViewEntry>();
-    /* package */ ArrayList<ViewEntry> mOtherEntries = new ArrayList<ViewEntry>();
-    /* package */ ArrayList<ArrayList<ViewEntry>> mSections = new ArrayList<ArrayList<ViewEntry>>();
-
-    private Cursor mCursor;
-
-    protected ContactHeaderWidget mContactHeaderWidget;
-    private NotifyingAsyncQueryHandler mHandler;
-
-    protected LayoutInflater mInflater;
-
-    protected int mReadOnlySourcesCnt;
-    protected int mWritableSourcesCnt;
-    protected boolean mAllRestricted;
-
-    protected Uri mPrimaryPhoneUri = null;
-
-    protected ArrayList<Long> mWritableRawContactIds = new ArrayList<Long>();
-
-    private static final int TOKEN_ENTITIES = 0;
-    private static final int TOKEN_STATUSES = 1;
-
-    private boolean mHasEntities = false;
-    private boolean mHasStatuses = false;
-
-    private long mNameRawContactId = -1;
-    private int mDisplayNameSource = DisplayNameSources.UNDEFINED;
-
-    private ArrayList<Entity> mEntities = Lists.newArrayList();
-    private HashMap<Long, DataStatus> mStatuses = Maps.newHashMap();
-
-    /**
-     * The view shown if the detail list is empty.
-     * We set this to the list view when first bind the adapter, so that it won't be shown while
-     * we're loading data.
-     */
-    private View mEmptyView;
-
-    private ContentObserver mObserver = new ContentObserver(new Handler()) {
-        @Override
-        public boolean deliverSelfNotifications() {
-            return true;
-        }
-
-        @Override
-        public void onChange(boolean selfChange) {
-            if (mCursor != null && !mCursor.isClosed()) {
-                startEntityQuery();
-            }
-        }
-    };
-
-    public void onClick(DialogInterface dialog, int which) {
-        closeCursor();
-        getContentResolver().delete(mLookupUri, null, null);
-        finish();
-    }
-
-    private ListView mListView;
-    private boolean mShowSmsLinksForAllPhones;
-
-    @Override
-    protected void onCreate(Bundle icicle) {
-        super.onCreate(icicle);
-
-        final Intent intent = getIntent();
-        Uri data = intent.getData();
-        String authority = data.getAuthority();
-        if (ContactsContract.AUTHORITY.equals(authority)) {
-            mLookupUri = data;
-        } else if (android.provider.Contacts.AUTHORITY.equals(authority)) {
-            final long rawContactId = ContentUris.parseId(data);
-            mLookupUri = RawContacts.getContactLookupUri(getContentResolver(),
-                    ContentUris.withAppendedId(RawContacts.CONTENT_URI, rawContactId));
-
-        }
-        mInflater = (LayoutInflater) getSystemService(Context.LAYOUT_INFLATER_SERVICE);
-
-        requestWindowFeature(Window.FEATURE_NO_TITLE);
-        setContentView(R.layout.contact_card_layout);
-
-        mContactHeaderWidget = (ContactHeaderWidget) findViewById(R.id.contact_header_widget);
-        mContactHeaderWidget.showStar(true);
-        mContactHeaderWidget.setExcludeMimes(new String[] {
-            Contacts.CONTENT_ITEM_TYPE
-        });
-
-        mHandler = new NotifyingAsyncQueryHandler(this, this);
-
-        mListView = (ListView) findViewById(R.id.contact_data);
-        mListView.setOnCreateContextMenuListener(this);
-        mListView.setScrollBarStyle(ListView.SCROLLBARS_OUTSIDE_OVERLAY);
-        mListView.setOnItemClickListener(this);
-        // Don't set it to mListView yet.  We do so later when we bind the adapter.
-        mEmptyView = findViewById(android.R.id.empty);
-
-        mResolver = getContentResolver();
-
-        // Build the list of sections. The order they're added to mSections dictates the
-        // order they are displayed in the list.
-        mSections.add(mPhoneEntries);
-        mSections.add(mSmsEntries);
-        mSections.add(mEmailEntries);
-        mSections.add(mImEntries);
-        mSections.add(mPostalEntries);
-        mSections.add(mNicknameEntries);
-        mSections.add(mOrganizationEntries);
-        mSections.add(mGroupEntries);
-        mSections.add(mOtherEntries);
-
-        //TODO Read this value from a preference
-        mShowSmsLinksForAllPhones = true;
-    }
-
-    @Override
-    protected void onResume() {
-        super.onResume();
-        startEntityQuery();
-    }
-
-    @Override
-    protected void onPause() {
-        super.onPause();
-        closeCursor();
-    }
-
-    @Override
-    protected void onDestroy() {
-        super.onDestroy();
-        closeCursor();
-    }
-
-    @Override
-    protected Dialog onCreateDialog(int id) {
-        switch (id) {
-            case DIALOG_CONFIRM_DELETE:
-                return new AlertDialog.Builder(this)
-                        .setTitle(R.string.deleteConfirmation_title)
-                        .setIcon(android.R.drawable.ic_dialog_alert)
-                        .setMessage(R.string.deleteConfirmation)
-                        .setNegativeButton(android.R.string.cancel, null)
-                        .setPositiveButton(android.R.string.ok, this)
-                        .setCancelable(false)
-                        .create();
-            case DIALOG_CONFIRM_READONLY_DELETE:
-                return new AlertDialog.Builder(this)
-                        .setTitle(R.string.deleteConfirmation_title)
-                        .setIcon(android.R.drawable.ic_dialog_alert)
-                        .setMessage(R.string.readOnlyContactDeleteConfirmation)
-                        .setNegativeButton(android.R.string.cancel, null)
-                        .setPositiveButton(android.R.string.ok, this)
-                        .setCancelable(false)
-                        .create();
-            case DIALOG_CONFIRM_MULTIPLE_DELETE:
-                return new AlertDialog.Builder(this)
-                        .setTitle(R.string.deleteConfirmation_title)
-                        .setIcon(android.R.drawable.ic_dialog_alert)
-                        .setMessage(R.string.multipleContactDeleteConfirmation)
-                        .setNegativeButton(android.R.string.cancel, null)
-                        .setPositiveButton(android.R.string.ok, this)
-                        .setCancelable(false)
-                        .create();
-            case DIALOG_CONFIRM_READONLY_HIDE: {
-                return new AlertDialog.Builder(this)
-                        .setTitle(R.string.deleteConfirmation_title)
-                        .setIcon(android.R.drawable.ic_dialog_alert)
-                        .setMessage(R.string.readOnlyContactWarning)
-                        .setPositiveButton(android.R.string.ok, this)
-                        .create();
-            }
-
-        }
-        return null;
-    }
-
-    /** {@inheritDoc} */
-    public void onQueryComplete(int token, Object cookie, final Cursor cursor) {
-        if (token == TOKEN_STATUSES) {
-            try {
-                // Read available social rows and consider binding
-                readStatuses(cursor);
-            } finally {
-                if (cursor != null) {
-                    cursor.close();
-                }
-            }
-            considerBindData();
-            return;
-        }
-
-        // One would think we could just iterate over the Cursor
-        // directly here, as the result set should be small, and we've
-        // already run the query in an AsyncTask, but a lot of ANRs
-        // were being reported in this code nonetheless.  See bug
-        // 2539603 for details.  The real bug which makes this result
-        // set huge and CPU-heavy may be elsewhere.
-        // TODO: if we keep this async, perhaps the entity iteration
-        // should also be original AsyncTask, rather than ping-ponging
-        // between threads like this.
-        final ArrayList<Entity> oldEntities = mEntities;
-        (new AsyncTask<Void, Void, ArrayList<Entity>>() {
-            @Override
-            protected ArrayList<Entity> doInBackground(Void... params) {
-                ArrayList<Entity> newEntities = new ArrayList<Entity>(cursor.getCount());
-                EntityIterator iterator = RawContacts.newEntityIterator(cursor);
-                try {
-                    while (iterator.hasNext()) {
-                        Entity entity = iterator.next();
-                        newEntities.add(entity);
-                    }
-                } finally {
-                    iterator.close();
-                }
-                return newEntities;
-            }
-
-            @Override
-            protected void onPostExecute(ArrayList<Entity> newEntities) {
-                if (newEntities == null) {
-                    // There was an error loading.
-                    return;
-                }
-                synchronized (ViewContactActivity.this) {
-                    if (mEntities != oldEntities) {
-                        // Multiple async tasks were in flight and we
-                        // lost the race.
-                        return;
-                    }
-                    mEntities = newEntities;
-                    mHasEntities = true;
-                }
-                considerBindData();
-            }
-        }).execute();
-    }
-
-    private long getRefreshedContactId() {
-        Uri freshContactUri = Contacts.lookupContact(getContentResolver(), mLookupUri);
-        if (freshContactUri != null) {
-            return ContentUris.parseId(freshContactUri);
-        }
-        return -1;
-    }
-
-    /**
-     * Read from the given {@link Cursor} and build a set of {@link DataStatus}
-     * objects to match any valid statuses found.
-     */
-    private synchronized void readStatuses(Cursor cursor) {
-        mStatuses.clear();
-
-        // Walk found statuses, creating internal row for each
-        while (cursor.moveToNext()) {
-            final DataStatus status = new DataStatus(cursor);
-            final long dataId = cursor.getLong(StatusQuery._ID);
-            mStatuses.put(dataId, status);
-        }
-
-        mHasStatuses = true;
-    }
-
-    private static Cursor setupContactCursor(ContentResolver resolver, Uri lookupUri) {
-        if (lookupUri == null) {
-            return null;
-        }
-        final List<String> segments = lookupUri.getPathSegments();
-        if (segments.size() != 4) {
-            return null;
-        }
-
-        // Contains an Id.
-        final long uriContactId = Long.parseLong(segments.get(3));
-        final String uriLookupKey = Uri.encode(segments.get(2));
-        final Uri dataUri = Uri.withAppendedPath(
-                ContentUris.withAppendedId(Contacts.CONTENT_URI, uriContactId),
-                Contacts.Data.CONTENT_DIRECTORY);
-
-        // This cursor has several purposes:
-        // - Fetch NAME_RAW_CONTACT_ID and DISPLAY_NAME_SOURCE
-        // - Fetch the lookup-key to ensure we are looking at the right record
-        // - Watcher for change events
-        Cursor cursor = resolver.query(dataUri,
-                new String[] {
-                    Contacts.NAME_RAW_CONTACT_ID,
-                    Contacts.DISPLAY_NAME_SOURCE,
-                    Contacts.LOOKUP_KEY
-                }, null, null, null);
-
-        if (cursor.moveToFirst()) {
-            String lookupKey =
-                    cursor.getString(cursor.getColumnIndex(Contacts.LOOKUP_KEY));
-            if (!lookupKey.equals(uriLookupKey)) {
-                // ID and lookup key do not match
-                cursor.close();
-                return null;
-            }
-            return cursor;
-        } else {
-            cursor.close();
-            return null;
-        }
-    }
-
-    private synchronized void startEntityQuery() {
-        closeCursor();
-
-        // Interprete mLookupUri
-        mCursor = setupContactCursor(mResolver, mLookupUri);
-
-        // If mCursor is null now we did not succeed in using the Uri's Id (or it didn't contain
-        // a Uri). Instead we now have to use the lookup key to find the record
-        if (mCursor == null) {
-            mLookupUri = Contacts.getLookupUri(getContentResolver(), mLookupUri);
-            mCursor = setupContactCursor(mResolver, mLookupUri);
-        }
-
-        // If mCursor is still null, we were unsuccessful in finding the record
-        if (mCursor == null) {
-            mNameRawContactId = -1;
-            mDisplayNameSource = DisplayNameSources.UNDEFINED;
-            // TODO either figure out a way to prevent a flash of black background or
-            // use some other UI than a toast
-            Toast.makeText(this, R.string.invalidContactMessage, Toast.LENGTH_SHORT).show();
-            Log.e(TAG, "invalid contact uri: " + mLookupUri);
-            finish();
-            return;
-        }
-
-        final long contactId = ContentUris.parseId(mLookupUri);
-
-        mNameRawContactId =
-                mCursor.getLong(mCursor.getColumnIndex(Contacts.NAME_RAW_CONTACT_ID));
-        mDisplayNameSource =
-                mCursor.getInt(mCursor.getColumnIndex(Contacts.DISPLAY_NAME_SOURCE));
-
-        mCursor.registerContentObserver(mObserver);
-
-        // Clear flags and start queries to data and status
-        mHasEntities = false;
-        mHasStatuses = false;
-
-        mHandler.startQuery(TOKEN_ENTITIES, null, RawContactsEntity.CONTENT_URI, null,
-                RawContacts.CONTACT_ID + "=?", new String[] {
-                    String.valueOf(contactId)
-                }, null);
-        final Uri dataUri = Uri.withAppendedPath(
-                ContentUris.withAppendedId(Contacts.CONTENT_URI, contactId),
-                Contacts.Data.CONTENT_DIRECTORY);
-        mHandler.startQuery(TOKEN_STATUSES, null, dataUri, StatusQuery.PROJECTION,
-                        StatusUpdates.PRESENCE + " IS NOT NULL OR " + StatusUpdates.STATUS
-                                + " IS NOT NULL", null, null);
-
-        mContactHeaderWidget.bindFromContactLookupUri(mLookupUri);
-    }
-
-    private void closeCursor() {
-        if (mCursor != null) {
-            mCursor.unregisterContentObserver(mObserver);
-            mCursor.close();
-            mCursor = null;
-        }
-    }
-
-    /**
-     * Consider binding views after any of several background queries has
-     * completed. We check internal flags and only bind when all data has
-     * arrived.
-     */
-    private void considerBindData() {
-        if (mHasEntities && mHasStatuses) {
-            bindData();
-        }
-    }
-
-    private void bindData() {
-
-        // Build up the contact entries
-        buildEntries();
-
-        // Collapse similar data items in select sections.
-        Collapser.collapseList(mPhoneEntries);
-        Collapser.collapseList(mSmsEntries);
-        Collapser.collapseList(mEmailEntries);
-        Collapser.collapseList(mPostalEntries);
-        Collapser.collapseList(mImEntries);
-
-        if (mAdapter == null) {
-            mAdapter = new ViewAdapter(this, mSections);
-            mListView.setAdapter(mAdapter);
-        } else {
-            mAdapter.setSections(mSections, SHOW_SEPARATORS);
-        }
-        mListView.setEmptyView(mEmptyView);
-    }
-
-    @Override
-    public boolean onCreateOptionsMenu(Menu menu) {
-        super.onCreateOptionsMenu(menu);
-
-        final MenuInflater inflater = getMenuInflater();
-        inflater.inflate(R.menu.view, menu);
-        return true;
-    }
-
-    @Override
-    public boolean onPrepareOptionsMenu(Menu menu) {
-        super.onPrepareOptionsMenu(menu);
-
-        // Only allow edit when we have at least one raw_contact id
-        final boolean hasRawContact = (mRawContactIds.size() > 0);
-        menu.findItem(R.id.menu_edit).setEnabled(hasRawContact);
-
-        // Only allow share when unrestricted contacts available
-        menu.findItem(R.id.menu_share).setEnabled(!mAllRestricted);
-
-        return true;
-    }
-
-    @Override
-    public void onCreateContextMenu(ContextMenu menu, View view, ContextMenuInfo menuInfo) {
-        AdapterView.AdapterContextMenuInfo info;
-        try {
-             info = (AdapterView.AdapterContextMenuInfo) menuInfo;
-        } catch (ClassCastException e) {
-            Log.e(TAG, "bad menuInfo", e);
-            return;
-        }
-
-        // This can be null sometimes, don't crash...
-        if (info == null) {
-            Log.e(TAG, "bad menuInfo");
-            return;
-        }
-
-        ViewEntry entry = ContactEntryAdapter.getEntry(mSections, info.position, SHOW_SEPARATORS);
-        menu.setHeaderTitle(R.string.contactOptionsTitle);
-        if (entry.mimetype.equals(CommonDataKinds.Phone.CONTENT_ITEM_TYPE)) {
-            menu.add(0, 0, 0, R.string.menu_call).setIntent(entry.intent);
-            menu.add(0, 0, 0, R.string.menu_sendSMS).setIntent(entry.secondaryIntent);
-            if (!entry.isPrimary) {
-                menu.add(0, MENU_ITEM_MAKE_DEFAULT, 0, R.string.menu_makeDefaultNumber);
-            }
-        } else if (entry.mimetype.equals(CommonDataKinds.Email.CONTENT_ITEM_TYPE)) {
-            menu.add(0, 0, 0, R.string.menu_sendEmail).setIntent(entry.intent);
-            if (!entry.isPrimary) {
-                menu.add(0, MENU_ITEM_MAKE_DEFAULT, 0, R.string.menu_makeDefaultEmail);
-            }
-        } else if (entry.mimetype.equals(CommonDataKinds.StructuredPostal.CONTENT_ITEM_TYPE)) {
-            menu.add(0, 0, 0, R.string.menu_viewAddress).setIntent(entry.intent);
-        }
-    }
-
-    @Override
-    public boolean onOptionsItemSelected(MenuItem item) {
-        switch (item.getItemId()) {
-            case R.id.menu_edit: {
-                Long rawContactIdToEdit = null;
-                if (mRawContactIds.size() > 0) {
-                    rawContactIdToEdit = mRawContactIds.get(0);
-                } else {
-                    // There is no rawContact to edit.
-                    break;
-                }
-                Uri rawContactUri = ContentUris.withAppendedId(RawContacts.CONTENT_URI,
-                        rawContactIdToEdit);
-                startActivityForResult(new Intent(Intent.ACTION_EDIT, rawContactUri),
-                        REQUEST_EDIT_CONTACT);
-                break;
-            }
-            case R.id.menu_delete: {
-                // Get confirmation
-                if (mReadOnlySourcesCnt > 0 & mWritableSourcesCnt > 0) {
-                    showDialog(DIALOG_CONFIRM_READONLY_DELETE);
-                } else if (mReadOnlySourcesCnt > 0 && mWritableSourcesCnt == 0) {
-                    showDialog(DIALOG_CONFIRM_READONLY_HIDE);
-                } else if (mReadOnlySourcesCnt == 0 && mWritableSourcesCnt > 1) {
-                    showDialog(DIALOG_CONFIRM_MULTIPLE_DELETE);
-                } else {
-                    showDialog(DIALOG_CONFIRM_DELETE);
-                }
-                return true;
-            }
-            case R.id.menu_join: {
-                showJoinAggregateActivity();
-                return true;
-            }
-            case R.id.menu_options: {
-                showOptionsActivity();
-                return true;
-            }
-            case R.id.menu_share: {
-                if (mAllRestricted) return false;
-
-                // TODO: Keep around actual LOOKUP_KEY, or formalize method of extracting
-                final String lookupKey = Uri.encode(mLookupUri.getPathSegments().get(2));
-                final Uri shareUri = Uri.withAppendedPath(Contacts.CONTENT_VCARD_URI, lookupKey);
-
-                final Intent intent = new Intent(Intent.ACTION_SEND);
-                intent.setType(Contacts.CONTENT_VCARD_TYPE);
-                intent.putExtra(Intent.EXTRA_STREAM, shareUri);
-
-                // Launch chooser to share contact via
-                final CharSequence chooseTitle = getText(R.string.share_via);
-                final Intent chooseIntent = Intent.createChooser(intent, chooseTitle);
-
-                try {
-                    startActivity(chooseIntent);
-                } catch (ActivityNotFoundException ex) {
-                    Toast.makeText(this, R.string.share_error, Toast.LENGTH_SHORT).show();
-                }
-                return true;
-            }
-        }
-        return super.onOptionsItemSelected(item);
-    }
-
-    @Override
-    public boolean onContextItemSelected(MenuItem item) {
-        switch (item.getItemId()) {
-            case MENU_ITEM_MAKE_DEFAULT: {
-                if (makeItemDefault(item)) {
-                    return true;
-                }
-                break;
-            }
-        }
-
-        return super.onContextItemSelected(item);
-    }
-
-    private boolean makeItemDefault(MenuItem item) {
-        ViewEntry entry = getViewEntryForMenuItem(item);
-        if (entry == null) {
-            return false;
-        }
-
-        // Update the primary values in the data record.
-        ContentValues values = new ContentValues(1);
-        values.put(Data.IS_SUPER_PRIMARY, 1);
-        getContentResolver().update(ContentUris.withAppendedId(Data.CONTENT_URI, entry.id),
-                values, null, null);
-        startEntityQuery();
-        return true;
-    }
-
-    /**
-     * Shows a list of aggregates that can be joined into the currently viewed aggregate.
-     */
-    public void showJoinAggregateActivity() {
-        long freshId = getRefreshedContactId();
-        if (freshId > 0) {
-            String displayName = null;
-            if (mCursor.moveToFirst()) {
-                displayName = mCursor.getString(0);
-            }
-            Intent intent = new Intent(JoinContactActivity.JOIN_CONTACT);
-            intent.putExtra(JoinContactActivity.EXTRA_TARGET_CONTACT_ID, freshId);
-            startActivityForResult(intent, REQUEST_JOIN_CONTACT);
-        }
-    }
-
-    @Override
-    protected void onActivityResult(int requestCode, int resultCode, Intent intent) {
-        if (requestCode == REQUEST_JOIN_CONTACT) {
-            if (resultCode == RESULT_OK && intent != null) {
-                final long contactId = ContentUris.parseId(intent.getData());
-                joinAggregate(contactId);
-            }
-        } else if (requestCode == REQUEST_EDIT_CONTACT) {
-            if (resultCode == EditContactActivity.RESULT_CLOSE_VIEW_ACTIVITY) {
-                finish();
-            } else if (resultCode == Activity.RESULT_OK) {
-                mLookupUri = intent.getData();
-                if (mLookupUri == null) {
-                    finish();
-                }
-            }
-        }
-    }
-
-    private void joinAggregate(final long contactId) {
-        Cursor c = mResolver.query(RawContacts.CONTENT_URI, new String[] {RawContacts._ID},
-                RawContacts.CONTACT_ID + "=" + contactId, null, null);
-
-        try {
-            while(c.moveToNext()) {
-                long rawContactId = c.getLong(0);
-                setAggregationException(rawContactId, AggregationExceptions.TYPE_KEEP_TOGETHER);
-            }
-        } finally {
-            c.close();
-        }
-
-        Toast.makeText(this, R.string.contactsJoinedMessage, Toast.LENGTH_LONG).show();
-        startEntityQuery();
-    }
-
-    /**
-     * Given a contact ID sets an aggregation exception to either join the contact with the
-     * current aggregate or split off.
-     */
-    protected void setAggregationException(long rawContactId, int exceptionType) {
-        ContentValues values = new ContentValues(3);
-        for (long aRawContactId : mRawContactIds) {
-            if (aRawContactId != rawContactId) {
-                values.put(AggregationExceptions.RAW_CONTACT_ID1, aRawContactId);
-                values.put(AggregationExceptions.RAW_CONTACT_ID2, rawContactId);
-                values.put(AggregationExceptions.TYPE, exceptionType);
-                mResolver.update(AggregationExceptions.CONTENT_URI, values, null, null);
-            }
-        }
-    }
-
-    private void showOptionsActivity() {
-        final Intent intent = new Intent(this, ContactOptionsActivity.class);
-        intent.setData(mLookupUri);
-        startActivity(intent);
-    }
-
-    private ViewEntry getViewEntryForMenuItem(MenuItem item) {
-        AdapterView.AdapterContextMenuInfo info;
-        try {
-             info = (AdapterView.AdapterContextMenuInfo) item.getMenuInfo();
-        } catch (ClassCastException e) {
-            Log.e(TAG, "bad menuInfo", e);
-            return null;
-        }
-
-        return ContactEntryAdapter.getEntry(mSections, info.position, SHOW_SEPARATORS);
-    }
-
-    @Override
-    public boolean onKeyDown(int keyCode, KeyEvent event) {
-        switch (keyCode) {
-            case KeyEvent.KEYCODE_CALL: {
-                try {
-                    ITelephony phone = ITelephony.Stub.asInterface(
-                            ServiceManager.checkService("phone"));
-                    if (phone != null && !phone.isIdle()) {
-                        // Skip out and let the key be handled at a higher level
-                        break;
-                    }
-                } catch (RemoteException re) {
-                    // Fall through and try to call the contact
-                }
-
-                int index = mListView.getSelectedItemPosition();
-                if (index != -1) {
-                    ViewEntry entry = ViewAdapter.getEntry(mSections, index, SHOW_SEPARATORS);
-                    if (entry != null &&
-                            entry.intent.getAction() == Intent.ACTION_CALL_PRIVILEGED) {
-                        startActivity(entry.intent);
-                        return true;
-                    }
-                } else if (mPrimaryPhoneUri != null) {
-                    // There isn't anything selected, call the default number
-                    final Intent intent = new Intent(Intent.ACTION_CALL_PRIVILEGED,
-                            mPrimaryPhoneUri);
-                    startActivity(intent);
-                    return true;
-                }
-                return false;
-            }
-
-            case KeyEvent.KEYCODE_DEL: {
-                if (mReadOnlySourcesCnt > 0 & mWritableSourcesCnt > 0) {
-                    showDialog(DIALOG_CONFIRM_READONLY_DELETE);
-                } else if (mReadOnlySourcesCnt > 0 && mWritableSourcesCnt == 0) {
-                    showDialog(DIALOG_CONFIRM_READONLY_HIDE);
-                } else if (mReadOnlySourcesCnt == 0 && mWritableSourcesCnt > 1) {
-                    showDialog(DIALOG_CONFIRM_MULTIPLE_DELETE);
-                } else {
-                    showDialog(DIALOG_CONFIRM_DELETE);
-                }
-                return true;
-            }
-        }
-
-        return super.onKeyDown(keyCode, event);
-    }
-
-    public void onItemClick(AdapterView parent, View v, int position, long id) {
-        ViewEntry entry = ViewAdapter.getEntry(mSections, position, SHOW_SEPARATORS);
-        if (entry != null) {
-            Intent intent = entry.intent;
-            if (intent != null) {
-                try {
-                    startActivity(intent);
-                } catch (ActivityNotFoundException e) {
-                    Log.e(TAG, "No activity found for intent: " + intent);
-                    signalError();
-                }
-            } else {
-                signalError();
-            }
-        } else {
-            signalError();
-        }
-    }
-
-    /**
-     * Signal an error to the user via a beep, or some other method.
-     */
-    private void signalError() {
-        //TODO: implement this when we have the sonification APIs
-    }
-
-    /**
-     * Build up the entries to display on the screen.
-     *
-     * @param personCursor the URI for the contact being displayed
-     */
-    private final void buildEntries() {
-        // Clear out the old entries
-        final int numSections = mSections.size();
-        for (int i = 0; i < numSections; i++) {
-            mSections.get(i).clear();
-        }
-
-        mRawContactIds.clear();
-
-        mReadOnlySourcesCnt = 0;
-        mWritableSourcesCnt = 0;
-        mAllRestricted = true;
-        mPrimaryPhoneUri = null;
-
-        mWritableRawContactIds.clear();
-
-        final Context context = this;
-        final Sources sources = Sources.getInstance(context);
-
-        // Build up method entries
-        if (mLookupUri != null) {
-            for (Entity entity: mEntities) {
-                final ContentValues entValues = entity.getEntityValues();
-                final String accountType = entValues.getAsString(RawContacts.ACCOUNT_TYPE);
-                final long rawContactId = entValues.getAsLong(RawContacts._ID);
-
-                // Mark when this contact has any unrestricted components
-                final boolean isRestricted = entValues.getAsInteger(RawContacts.IS_RESTRICTED) != 0;
-                if (!isRestricted) mAllRestricted = false;
-
-                if (!mRawContactIds.contains(rawContactId)) {
-                    mRawContactIds.add(rawContactId);
-                }
-                ContactsSource contactsSource = sources.getInflatedSource(accountType,
-                        ContactsSource.LEVEL_SUMMARY);
-                if (contactsSource != null && contactsSource.readOnly) {
-                    mReadOnlySourcesCnt += 1;
-                } else {
-                    mWritableSourcesCnt += 1;
-                    mWritableRawContactIds.add(rawContactId);
-                }
-
-
-                for (NamedContentValues subValue : entity.getSubValues()) {
-                    final ContentValues entryValues = subValue.values;
-                    entryValues.put(Data.RAW_CONTACT_ID, rawContactId);
-
-                    final long dataId = entryValues.getAsLong(Data._ID);
-                    final String mimeType = entryValues.getAsString(Data.MIMETYPE);
-                    if (mimeType == null) continue;
-
-                    final DataKind kind = sources.getKindOrFallback(accountType, mimeType, this,
-                            ContactsSource.LEVEL_MIMETYPES);
-                    if (kind == null) continue;
-
-                    final ViewEntry entry = ViewEntry.fromValues(context, mimeType, kind,
-                            rawContactId, dataId, entryValues);
-
-                    final boolean hasData = !TextUtils.isEmpty(entry.data);
-                    final boolean isSuperPrimary = entryValues.getAsInteger(
-                            Data.IS_SUPER_PRIMARY) != 0;
-
-                    if (Phone.CONTENT_ITEM_TYPE.equals(mimeType) && hasData) {
-                        // Build phone entries
-                        mNumPhoneNumbers++;
-
-                        entry.intent = new Intent(Intent.ACTION_CALL_PRIVILEGED,
-                                Uri.fromParts(Constants.SCHEME_TEL, entry.data, null));
-                        entry.secondaryIntent = new Intent(Intent.ACTION_SENDTO,
-                                Uri.fromParts(Constants.SCHEME_SMSTO, entry.data, null));
-
-                        // Remember super-primary phone
-                        if (isSuperPrimary) mPrimaryPhoneUri = entry.uri;
-
-                        entry.isPrimary = isSuperPrimary;
-                        mPhoneEntries.add(entry);
-
-                        if (entry.type == CommonDataKinds.Phone.TYPE_MOBILE
-                                || mShowSmsLinksForAllPhones) {
-                            // Add an SMS entry
-                            if (kind.iconAltRes > 0) {
-                                entry.secondaryActionIcon = kind.iconAltRes;
-                            }
-                        }
-                    } else if (Email.CONTENT_ITEM_TYPE.equals(mimeType) && hasData) {
-                        // Build email entries
-                        entry.intent = new Intent(Intent.ACTION_SENDTO,
-                                Uri.fromParts(Constants.SCHEME_MAILTO, entry.data, null));
-                        entry.isPrimary = isSuperPrimary;
-                        mEmailEntries.add(entry);
-
-                        // When Email rows have status, create additional Im row
-                        final DataStatus status = mStatuses.get(entry.id);
-                        if (status != null) {
-                            final String imMime = Im.CONTENT_ITEM_TYPE;
-                            final DataKind imKind = sources.getKindOrFallback(accountType,
-                                    imMime, this, ContactsSource.LEVEL_MIMETYPES);
-                            final ViewEntry imEntry = ViewEntry.fromValues(context,
-                                    imMime, imKind, rawContactId, dataId, entryValues);
-                            imEntry.intent = ContactsUtils.buildImIntent(entryValues);
-                            imEntry.applyStatus(status, false);
-                            mImEntries.add(imEntry);
-                        }
-                    } else if (StructuredPostal.CONTENT_ITEM_TYPE.equals(mimeType) && hasData) {
-                        // Build postal entries
-                        entry.maxLines = 4;
-                        entry.intent = new Intent(Intent.ACTION_VIEW, entry.uri);
-                        mPostalEntries.add(entry);
-                    } else if (Im.CONTENT_ITEM_TYPE.equals(mimeType) && hasData) {
-                        // Build IM entries
-                        entry.intent = ContactsUtils.buildImIntent(entryValues);
-                        if (TextUtils.isEmpty(entry.label)) {
-                            entry.label = getString(R.string.chat).toLowerCase();
-                        }
-
-                        // Apply presence and status details when available
-                        final DataStatus status = mStatuses.get(entry.id);
-                        if (status != null) {
-                            entry.applyStatus(status, false);
-                        }
-                        mImEntries.add(entry);
-                    } else if (Organization.CONTENT_ITEM_TYPE.equals(mimeType) &&
-                            (hasData || !TextUtils.isEmpty(entry.label))) {
-                        // Build organization entries
-                        final boolean isNameRawContact = (mNameRawContactId == rawContactId);
-
-                        final boolean duplicatesTitle =
-                            isNameRawContact
-                            && mDisplayNameSource == DisplayNameSources.ORGANIZATION
-                            && (!hasData || TextUtils.isEmpty(entry.label));
-
-                        if (!duplicatesTitle) {
-                            entry.uri = null;
-
-                            if (TextUtils.isEmpty(entry.label)) {
-                                entry.label = entry.data;
-                                entry.data = "";
-                            }
-
-                            mOrganizationEntries.add(entry);
-                        }
-                    } else if (Nickname.CONTENT_ITEM_TYPE.equals(mimeType) && hasData) {
-                        // Build nickname entries
-                        final boolean isNameRawContact = (mNameRawContactId == rawContactId);
-
-                        final boolean duplicatesTitle =
-                            isNameRawContact
-                            && mDisplayNameSource == DisplayNameSources.NICKNAME;
-
-                        if (!duplicatesTitle) {
-                            entry.uri = null;
-                            mNicknameEntries.add(entry);
-                        }
-                    } else if (Note.CONTENT_ITEM_TYPE.equals(mimeType) && hasData) {
-                        // Build note entries
-                        entry.uri = null;
-                        entry.maxLines = 100;
-                        mOtherEntries.add(entry);
-                    } else if (Website.CONTENT_ITEM_TYPE.equals(mimeType) && hasData) {
-                        // Build note entries
-                        entry.uri = null;
-                        entry.maxLines = 10;
-                        try {
-                            WebAddress webAddress = new WebAddress(entry.data);
-                            entry.intent = new Intent(Intent.ACTION_VIEW,
-                                    Uri.parse(webAddress.toString()));
-                        } catch (ParseException e) {
-                            Log.e(TAG, "Couldn't parse website: " + entry.data);
-                        }
-                        mOtherEntries.add(entry);
-                    } else {
-                        // Handle showing custom rows
-                        entry.intent = new Intent(Intent.ACTION_VIEW, entry.uri);
-
-                        // Use social summary when requested by external source
-                        final DataStatus status = mStatuses.get(entry.id);
-                        final boolean hasSocial = kind.actionBodySocial && status != null;
-                        if (hasSocial) {
-                            entry.applyStatus(status, true);
-                        }
-
-                        if (hasSocial || hasData) {
-                            mOtherEntries.add(entry);
-                        }
-                    }
-                }
-            }
-        }
-    }
-
-    static String buildActionString(DataKind kind, ContentValues values, boolean lowerCase,
-            Context context) {
-        if (kind.actionHeader == null) {
-            return null;
-        }
-        CharSequence actionHeader = kind.actionHeader.inflateUsing(context, values);
-        if (actionHeader == null) {
-            return null;
-        }
-        return lowerCase ? actionHeader.toString().toLowerCase() : actionHeader.toString();
-    }
-
-    static String buildDataString(DataKind kind, ContentValues values, Context context) {
-        if (kind.actionBody == null) {
-            return null;
-        }
-        CharSequence actionBody = kind.actionBody.inflateUsing(context, values);
-        return actionBody == null ? null : actionBody.toString();
-    }
-
-    /**
-     * A basic structure with the data for a contact entry in the list.
-     */
-    static class ViewEntry extends ContactEntryAdapter.Entry implements Collapsible<ViewEntry> {
-        public Context context = null;
-        public String resPackageName = null;
-        public int actionIcon = -1;
-        public boolean isPrimary = false;
-        public int secondaryActionIcon = -1;
-        public Intent intent;
-        public Intent secondaryIntent = null;
-        public int maxLabelLines = 1;
-        public ArrayList<Long> ids = new ArrayList<Long>();
-        public int collapseCount = 0;
-
-        public int presence = -1;
-
-        public CharSequence footerLine = null;
-
-        private ViewEntry() {
-        }
-
-        /**
-         * Build new {@link ViewEntry} and populate from the given values.
-         */
-        public static ViewEntry fromValues(Context context, String mimeType, DataKind kind,
-                long rawContactId, long dataId, ContentValues values) {
-            final ViewEntry entry = new ViewEntry();
-            entry.context = context;
-            entry.contactId = rawContactId;
-            entry.id = dataId;
-            entry.uri = ContentUris.withAppendedId(Data.CONTENT_URI, entry.id);
-            entry.mimetype = mimeType;
-            entry.label = buildActionString(kind, values, false, context);
-            entry.data = buildDataString(kind, values, context);
-
-            if (kind.typeColumn != null && values.containsKey(kind.typeColumn)) {
-                entry.type = values.getAsInteger(kind.typeColumn);
-            }
-            if (kind.iconRes > 0) {
-                entry.resPackageName = kind.resPackageName;
-                entry.actionIcon = kind.iconRes;
-            }
-
-            return entry;
-        }
-
-        /**
-         * Apply given {@link DataStatus} values over this {@link ViewEntry}
-         *
-         * @param fillData When true, the given status replaces {@link #data}
-         *            and {@link #footerLine}. Otherwise only {@link #presence}
-         *            is updated.
-         */
-        public ViewEntry applyStatus(DataStatus status, boolean fillData) {
-            presence = status.getPresence();
-            if (fillData && status.isValid()) {
-                this.data = status.getStatus().toString();
-                this.footerLine = status.getTimestampLabel(context);
-            }
-
-            return this;
-        }
-
-        public boolean collapseWith(ViewEntry entry) {
-            // assert equal collapse keys
-            if (!shouldCollapseWith(entry)) {
-                return false;
-            }
-
-            // Choose the label associated with the highest type precedence.
-            if (TypePrecedence.getTypePrecedence(mimetype, type)
-                    > TypePrecedence.getTypePrecedence(entry.mimetype, entry.type)) {
-                type = entry.type;
-                label = entry.label;
-            }
-
-            // Choose the max of the maxLines and maxLabelLines values.
-            maxLines = Math.max(maxLines, entry.maxLines);
-            maxLabelLines = Math.max(maxLabelLines, entry.maxLabelLines);
-
-            // Choose the presence with the highest precedence.
-            if (StatusUpdates.getPresencePrecedence(presence)
-                    < StatusUpdates.getPresencePrecedence(entry.presence)) {
-                presence = entry.presence;
-            }
-
-            // If any of the collapsed entries are primary make the whole thing primary.
-            isPrimary = entry.isPrimary ? true : isPrimary;
-
-            // uri, and contactdId, shouldn't make a difference. Just keep the original.
-
-            // Keep track of all the ids that have been collapsed with this one.
-            ids.add(entry.id);
-            collapseCount++;
-            return true;
-        }
-
-        public boolean shouldCollapseWith(ViewEntry entry) {
-            if (entry == null) {
-                return false;
-            }
-
-            if (!ContactsUtils.shouldCollapse(context, mimetype, data, entry.mimetype,
-                    entry.data)) {
-                return false;
-            }
-
-            if (!TextUtils.equals(mimetype, entry.mimetype)
-                    || !ContactsUtils.areIntentActionEqual(intent, entry.intent)
-                    || !ContactsUtils.areIntentActionEqual(secondaryIntent, entry.secondaryIntent)
-                    || actionIcon != entry.actionIcon) {
-                return false;
-            }
-
-            return true;
-        }
-    }
-
-    /** Cache of the children views of a row */
-    static class ViewCache {
-        public TextView label;
-        public TextView data;
-        public TextView footer;
-        public ImageView actionIcon;
-        public ImageView presenceIcon;
-        public ImageView primaryIcon;
-        public ImageView secondaryActionButton;
-        public View secondaryActionDivider;
-
-        // Need to keep track of this too
-        ViewEntry entry;
-    }
-
-    private final class ViewAdapter extends ContactEntryAdapter<ViewEntry>
-            implements View.OnClickListener {
-
-
-        ViewAdapter(Context context, ArrayList<ArrayList<ViewEntry>> sections) {
-            super(context, sections, SHOW_SEPARATORS);
-        }
-
-        public void onClick(View v) {
-            Intent intent = (Intent) v.getTag();
-            startActivity(intent);
-        }
-
-        @Override
-        public View getView(int position, View convertView, ViewGroup parent) {
-            ViewEntry entry = getEntry(mSections, position, false);
-            View v;
-
-            ViewCache views;
-
-            // Check to see if we can reuse convertView
-            if (convertView != null) {
-                v = convertView;
-                views = (ViewCache) v.getTag();
-            } else {
-                // Create a new view if needed
-                v = mInflater.inflate(R.layout.list_item_text_icons, parent, false);
-
-                // Cache the children
-                views = new ViewCache();
-                views.label = (TextView) v.findViewById(android.R.id.text1);
-                views.data = (TextView) v.findViewById(android.R.id.text2);
-                views.footer = (TextView) v.findViewById(R.id.footer);
-                views.actionIcon = (ImageView) v.findViewById(R.id.action_icon);
-                views.primaryIcon = (ImageView) v.findViewById(R.id.primary_icon);
-                views.presenceIcon = (ImageView) v.findViewById(R.id.presence_icon);
-                views.secondaryActionButton = (ImageView) v.findViewById(
-                        R.id.secondary_action_button);
-                views.secondaryActionButton.setOnClickListener(this);
-                views.secondaryActionDivider = v.findViewById(R.id.divider);
-                v.setTag(views);
-            }
-
-            // Update the entry in the view cache
-            views.entry = entry;
-
-            // Bind the data to the view
-            bindView(v, entry);
-            return v;
-        }
-
-        @Override
-        protected View newView(int position, ViewGroup parent) {
-            // getView() handles this
-            throw new UnsupportedOperationException();
-        }
-
-        @Override
-        protected void bindView(View view, ViewEntry entry) {
-            final Resources resources = mContext.getResources();
-            ViewCache views = (ViewCache) view.getTag();
-
-            // Set the label
-            TextView label = views.label;
-            setMaxLines(label, entry.maxLabelLines);
-            label.setText(entry.label);
-
-            // Set the data
-            TextView data = views.data;
-            if (data != null) {
-                if (entry.mimetype.equals(Phone.CONTENT_ITEM_TYPE)
-                        || entry.mimetype.equals(Constants.MIME_SMS_ADDRESS)) {
-                    data.setText(PhoneNumberUtils.formatNumber(entry.data));
-                } else {
-                    data.setText(entry.data);
-                }
-                setMaxLines(data, entry.maxLines);
-            }
-
-            // Set the footer
-            if (!TextUtils.isEmpty(entry.footerLine)) {
-                views.footer.setText(entry.footerLine);
-                views.footer.setVisibility(View.VISIBLE);
-            } else {
-                views.footer.setVisibility(View.GONE);
-            }
-
-            // Set the primary icon
-            views.primaryIcon.setVisibility(entry.isPrimary ? View.VISIBLE : View.GONE);
-
-            // Set the action icon
-            ImageView action = views.actionIcon;
-            if (entry.actionIcon != -1) {
-                Drawable actionIcon;
-                if (entry.resPackageName != null) {
-                    // Load external resources through PackageManager
-                    actionIcon = mContext.getPackageManager().getDrawable(entry.resPackageName,
-                            entry.actionIcon, null);
-                } else {
-                    actionIcon = resources.getDrawable(entry.actionIcon);
-                }
-                action.setImageDrawable(actionIcon);
-                action.setVisibility(View.VISIBLE);
-            } else {
-                // Things should still line up as if there was an icon, so make it invisible
-                action.setVisibility(View.INVISIBLE);
-            }
-
-            // Set the presence icon
-            Drawable presenceIcon = ContactPresenceIconUtil.getPresenceIcon(
-                    mContext, entry.presence);
-            ImageView presenceIconView = views.presenceIcon;
-            if (presenceIcon != null) {
-                presenceIconView.setImageDrawable(presenceIcon);
-                presenceIconView.setVisibility(View.VISIBLE);
-            } else {
-                presenceIconView.setVisibility(View.GONE);
-            }
-
-            // Set the secondary action button
-            ImageView secondaryActionView = views.secondaryActionButton;
-            Drawable secondaryActionIcon = null;
-            if (entry.secondaryActionIcon != -1) {
-                secondaryActionIcon = resources.getDrawable(entry.secondaryActionIcon);
-            }
-            if (entry.secondaryIntent != null && secondaryActionIcon != null) {
-                secondaryActionView.setImageDrawable(secondaryActionIcon);
-                secondaryActionView.setTag(entry.secondaryIntent);
-                secondaryActionView.setVisibility(View.VISIBLE);
-                views.secondaryActionDivider.setVisibility(View.VISIBLE);
-            } else {
-                secondaryActionView.setVisibility(View.GONE);
-                views.secondaryActionDivider.setVisibility(View.GONE);
-            }
-        }
-
-        private void setMaxLines(TextView textView, int maxLines) {
-            if (maxLines == 1) {
-                textView.setSingleLine(true);
-                textView.setEllipsize(TextUtils.TruncateAt.END);
-            } else {
-                textView.setSingleLine(false);
-                textView.setMaxLines(maxLines);
-                textView.setEllipsize(null);
-            }
-        }
-    }
-
-    private interface StatusQuery {
-        final String[] PROJECTION = new String[] {
-                Data._ID,
-                Data.STATUS,
-                Data.STATUS_RES_PACKAGE,
-                Data.STATUS_ICON,
-                Data.STATUS_LABEL,
-                Data.STATUS_TIMESTAMP,
-                Data.PRESENCE,
-        };
-
-        final int _ID = 0;
-    }
-
-    @Override
-    public void startSearch(String initialQuery, boolean selectInitialQuery, Bundle appSearchData,
-            boolean globalSearch) {
-        if (globalSearch) {
-            super.startSearch(initialQuery, selectInitialQuery, appSearchData, globalSearch);
-        } else {
-            ContactsSearchManager.startSearch(this, initialQuery);
-        }
-    }
-}
diff --git a/src/com/android/contacts/activities/ContactDetailActivity.java b/src/com/android/contacts/activities/ContactDetailActivity.java
index dc14770..d413a25 100644
--- a/src/com/android/contacts/activities/ContactDetailActivity.java
+++ b/src/com/android/contacts/activities/ContactDetailActivity.java
@@ -18,12 +18,14 @@
 
 import com.android.contacts.ContactsSearchManager;
 import com.android.contacts.R;
-import com.android.contacts.mvcframework.DialogManager;
-import com.android.contacts.mvcframework.LoaderActivity;
-import com.android.contacts.views.detail.ContactDetailView;
+import com.android.contacts.util.DialogManager;
+import com.android.contacts.views.detail.ContactPresenter;
 import com.android.contacts.views.detail.ContactLoader;
 
 import android.app.Dialog;
+import android.app.patterns.Loader;
+import android.app.patterns.LoaderActivity;
+import android.net.Uri;
 import android.os.Bundle;
 import android.util.Log;
 import android.view.KeyEvent;
@@ -33,7 +35,7 @@
 public class ContactDetailActivity extends LoaderActivity<ContactLoader.Result> implements
         DialogManager.DialogShowingViewActivity {
     private static final int LOADER_DETAILS = 1;
-    private ContactDetailView mDetails;
+    private ContactPresenter mCoupler;
     private DialogManager mDialogManager;
 
     private static final String TAG = "ContactDetailActivity";
@@ -49,8 +51,8 @@
 
         mDialogManager = new DialogManager(this, DIALOG_VIEW_DIALOGS_ID1, DIALOG_VIEW_DIALOGS_ID2);
 
-        mDetails = (ContactDetailView) findViewById(R.id.contact_details);
-        mDetails.setCallbacks(new ContactDetailView.DefaultCallbacks(this));
+        mCoupler = new ContactPresenter(this, findViewById(R.id.contact_details));
+        mCoupler.setController(new ContactPresenter.DefaultController(this));
     }
 
     @Override
@@ -64,13 +66,16 @@
             case LOADER_DETAILS: {
                 return new ContactLoader(this, getIntent().getData());
             }
+            default: {
+                Log.wtf(TAG, "Unknown ID in onCreateLoader: " + id);
+            }
         }
         return null;
     }
 
-
     @Override
-    public void onLoadComplete(int id, ContactLoader.Result data) {
+    public void onLoadFinished(Loader loader, ContactLoader.Result data) {
+        final int id = loader.getId();
         switch (id) {
             case LOADER_DETAILS:
                 if (data == ContactLoader.Result.NOT_FOUND) {
@@ -79,15 +84,18 @@
                     finish();
                     return;
                 }
-                mDetails.setData(data);
+                mCoupler.setData(data);
                 break;
+            default: {
+                Log.wtf(TAG, "Unknown ID in onLoadFinished: " + id);
+            }
         }
     }
 
     @Override
     public boolean onCreateOptionsMenu(Menu menu) {
         // TODO: This is too hardwired.
-        if (mDetails.onCreateOptionsMenu(menu, getMenuInflater())) return true;
+        if (mCoupler.onCreateOptionsMenu(menu, getMenuInflater())) return true;
 
         return super.onCreateOptionsMenu(menu);
     }
@@ -95,7 +103,7 @@
     @Override
     public boolean onPrepareOptionsMenu(Menu menu) {
         // TODO: This is too hardwired.
-        if (mDetails.onPrepareOptionsMenu(menu)) return true;
+        if (mCoupler.onPrepareOptionsMenu(menu)) return true;
 
         return super.onPrepareOptionsMenu(menu);
     }
@@ -103,7 +111,7 @@
     @Override
     public boolean onOptionsItemSelected(MenuItem item) {
         // TODO: This is too hardwired.
-        if (mDetails.onOptionsItemSelected(item)) return true;
+        if (mCoupler.onOptionsItemSelected(item)) return true;
 
         return super.onOptionsItemSelected(item);
     }
@@ -120,7 +128,7 @@
     @Override
     public boolean onContextItemSelected(MenuItem item) {
         // TODO: This is too hardwired.
-        if (mDetails.onContextItemSelected(item)) return true;
+        if (mCoupler.onContextItemSelected(item)) return true;
 
         return super.onContextItemSelected(item);
     }
@@ -138,7 +146,7 @@
     @Override
     public boolean onKeyDown(int keyCode, KeyEvent event) {
         // TODO: This is too hardwired.
-        if (mDetails.onKeyDown(keyCode, event)) return true;
+        if (mCoupler.onKeyDown(keyCode, event)) return true;
 
         return super.onKeyDown(keyCode, event);
     }
diff --git a/src/com/android/contacts/list/ContactEntryListAdapter.java b/src/com/android/contacts/list/ContactEntryListAdapter.java
new file mode 100644
index 0000000..2cf12c5
--- /dev/null
+++ b/src/com/android/contacts/list/ContactEntryListAdapter.java
@@ -0,0 +1,42 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.contacts.list;
+
+import android.content.Context;
+
+/**
+ * Common base class for various contact-related lists, e.g. contact list, phone number list
+ * etc.
+ */
+public abstract class ContactEntryListAdapter extends PinnedHeaderListAdapter {
+
+    public ContactEntryListAdapter(Context context) {
+        super(context);
+    }
+
+    public Context getContext() {
+        return mContext;
+    }
+
+    /*
+     * TODO change this method when loaders are introduced.
+     */
+    @Override
+    @Deprecated
+    public void onContentChanged() {
+        super.onContentChanged();
+    }
+}
diff --git a/src/com/android/contacts/list/ContactEntryListConfiguration.java b/src/com/android/contacts/list/ContactEntryListConfiguration.java
new file mode 100644
index 0000000..26eeb72
--- /dev/null
+++ b/src/com/android/contacts/list/ContactEntryListConfiguration.java
@@ -0,0 +1,98 @@
+/*
+ * 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.ContactsListActivity;
+import com.android.contacts.widget.PinnedHeaderListView;
+
+import android.content.Context;
+import android.view.View;
+import android.widget.ListAdapter;
+import android.widget.ListView;
+
+/**
+ * Common base class for configurations of various contact-related lists, e.g.
+ * contact list, phone number list etc.
+ */
+public abstract class ContactEntryListConfiguration {
+
+    private final Context mContext;
+    private final ContactsApplicationController mApplicationController;
+    private boolean mSectionHeaderDisplayEnabled;
+    private boolean mPhotoLoaderEnabled;
+
+    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);
+
+        configurePinnedHeader(listView, adapter);
+    }
+
+    private void configurePinnedHeader(ListView listView, ListAdapter adapter) {
+        if (!mSectionHeaderDisplayEnabled) {
+            return;
+        }
+
+        if (listView instanceof PinnedHeaderListView
+                && adapter instanceof PinnedHeaderListAdapter) {
+            PinnedHeaderListView pinnedHeaderList = (PinnedHeaderListView)listView;
+            PinnedHeaderListAdapter pinnedHeaderListAdapter = (PinnedHeaderListAdapter)adapter;
+            View headerView = pinnedHeaderListAdapter.createPinnedHeaderView(pinnedHeaderList);
+            pinnedHeaderList.setPinnedHeaderView(headerView);
+        }
+    }
+
+    public void setSectionHeaderDisplayEnabled(boolean flag) {
+        mSectionHeaderDisplayEnabled = flag;
+    }
+
+    public boolean isSectionHeaderDisplayEnabled() {
+        return mSectionHeaderDisplayEnabled;
+    }
+
+    public void setPhotoLoaderEnabled(boolean flag) {
+        mPhotoLoaderEnabled = flag;
+    }
+
+    public boolean isPhotoLoaderEnabled() {
+        return mPhotoLoaderEnabled;
+    }
+}
diff --git a/src/com/android/contacts/list/ContactEntryListController.java b/src/com/android/contacts/list/ContactEntryListController.java
new file mode 100644
index 0000000..a190b4e
--- /dev/null
+++ b/src/com/android/contacts/list/ContactEntryListController.java
@@ -0,0 +1,82 @@
+/*
+ * 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;
+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 list controllers.
+ */
+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
new file mode 100644
index 0000000..6b228c6
--- /dev/null
+++ b/src/com/android/contacts/list/ContactItemListAdapter.java
@@ -0,0 +1,717 @@
+/*
+ * 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.ContactListItemView;
+import com.android.contacts.ContactPresenceIconUtil;
+import com.android.contacts.ContactsListActivity;
+import com.android.contacts.ContactsSectionIndexer;
+import com.android.contacts.R;
+import com.android.contacts.ContactsListActivity.ContactListItemCache;
+import com.android.contacts.TextHighlightingAnimation.TextWithHighlighting;
+
+import android.content.Context;
+import android.database.CharArrayBuffer;
+import android.database.Cursor;
+import android.graphics.drawable.Drawable;
+import android.os.Bundle;
+import android.provider.ContactsContract;
+import android.provider.ContactsContract.ContactCounts;
+import android.provider.ContactsContract.Contacts;
+import android.provider.ContactsContract.ProviderStatus;
+import android.provider.ContactsContract.CommonDataKinds.Email;
+import android.provider.ContactsContract.CommonDataKinds.Nickname;
+import android.provider.ContactsContract.CommonDataKinds.Organization;
+import android.provider.ContactsContract.CommonDataKinds.Phone;
+import android.provider.ContactsContract.CommonDataKinds.StructuredPostal;
+import android.text.TextUtils;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.Filter;
+import android.widget.ImageView;
+import android.widget.ListView;
+import android.widget.QuickContactBadge;
+import android.widget.TextView;
+
+public class ContactItemListAdapter extends ContactEntryListAdapter {
+
+    private final ContactsListActivity contactsListActivity;
+    private boolean mLoading = true;
+    protected CharSequence mUnknownNameText;
+    protected boolean mDisplayPhotos = false;
+    private boolean mDisplayCallButton = false;
+    protected boolean mDisplayAdditionalData = true;
+    private int mFrequentSeparatorPos = ListView.INVALID_POSITION;
+    private boolean mSectionHeaderDisplayEnabled;
+
+    public ContactItemListAdapter(ContactsListActivity contactsListActivity) {
+        super(contactsListActivity);
+        this.contactsListActivity = contactsListActivity;
+
+        mUnknownNameText = contactsListActivity.getText(android.R.string.unknownName);
+
+        // Do not display the second line of text if in a specific SEARCH query mode, usually for
+        // matching a specific E-mail or phone number. Any contact details
+        // shown would be identical, and columns might not even be present
+        // in the returned cursor.
+        if (contactsListActivity.mMode != ContactsListActivity.MODE_QUERY_PICK_PHONE
+                && contactsListActivity.mQueryMode != ContactsListActivity.QUERY_MODE_NONE) {
+            mDisplayAdditionalData = false;
+        }
+
+        if ((contactsListActivity.mMode & ContactsListActivity.MODE_MASK_NO_DATA) ==
+                ContactsListActivity.MODE_MASK_NO_DATA) {
+            mDisplayAdditionalData = false;
+        }
+
+        if ((contactsListActivity.mMode & ContactsListActivity.MODE_MASK_SHOW_CALL_BUTTON) ==
+                ContactsListActivity.MODE_MASK_SHOW_CALL_BUTTON) {
+            mDisplayCallButton = true;
+        }
+    }
+
+    public void setSectionHeaderDisplayEnabled(boolean flag) {
+        mSectionHeaderDisplayEnabled = flag;
+    }
+
+    public boolean isSectionHeaderDisplayEnabled() {
+        return mSectionHeaderDisplayEnabled;
+    }
+
+    public void setDisplayPhotos(boolean flag) {
+        mDisplayPhotos = flag;
+    }
+
+    /**
+     * Callback on the UI thread when the content observer on the backing cursor fires.
+     * Instead of calling requery we need to do an async query so that the requery doesn't
+     * block the UI thread for a long time.
+     */
+    @Override
+    public void onContentChanged() {
+        CharSequence constraint = contactsListActivity.getTextFilter();
+        if (!TextUtils.isEmpty(constraint)) {
+            // Reset the filter state then start an async filter operation
+            Filter filter = getFilter();
+            filter.filter(constraint);
+        } else {
+            // Start an async query
+            contactsListActivity.startQuery();
+        }
+    }
+
+    public void setLoading(boolean loading) {
+        mLoading = loading;
+    }
+
+    @Override
+    public boolean isEmpty() {
+        if (contactsListActivity.mProviderStatus != ProviderStatus.STATUS_NORMAL) {
+            return true;
+        }
+
+        if (contactsListActivity.mSearchMode) {
+            return TextUtils.isEmpty(contactsListActivity.getTextFilter());
+        } else if ((contactsListActivity.mMode & ContactsListActivity.MODE_MASK_CREATE_NEW) ==
+                ContactsListActivity.MODE_MASK_CREATE_NEW) {
+            // This mode mask adds a header and we always want it to show up, even
+            // if the list is empty, so always claim the list is not empty.
+            return false;
+        } else {
+            if (mCursor == null || mLoading) {
+                // We don't want the empty state to show when loading.
+                return false;
+            } else {
+                return super.isEmpty();
+            }
+        }
+    }
+
+    @Override
+    public int getItemViewType(int position) {
+        if (position == 0 && (contactsListActivity.mShowNumberOfContacts ||
+                (contactsListActivity.mMode & ContactsListActivity.MODE_MASK_CREATE_NEW) != 0)) {
+            return IGNORE_ITEM_VIEW_TYPE;
+        }
+
+        if (isSearchAllContactsItemPosition(position)) {
+            return IGNORE_ITEM_VIEW_TYPE;
+        }
+
+        if (getSeparatorId(position) != 0) {
+            // We don't want the separator view to be recycled.
+            return IGNORE_ITEM_VIEW_TYPE;
+        }
+        return super.getItemViewType(position);
+    }
+
+    @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");
+        }
+
+        // handle the total contacts item
+        if (position == 0 && contactsListActivity.mShowNumberOfContacts) {
+            return getTotalContactCountView(parent);
+        }
+
+        if (position == 0
+                && (contactsListActivity.mMode & ContactsListActivity.MODE_MASK_CREATE_NEW) != 0) {
+            // Add the header for creating a new contact
+            return contactsListActivity.getLayoutInflater().inflate(R.layout.create_new_contact,
+                    parent, false);
+        }
+
+        if (isSearchAllContactsItemPosition(position)) {
+            return contactsListActivity.getLayoutInflater().
+                    inflate(R.layout.contacts_list_search_all_item, parent, false);
+        }
+
+        // Handle the separator specially
+        int separatorId = getSeparatorId(position);
+        if (separatorId != 0) {
+            TextView view = (TextView) contactsListActivity.getLayoutInflater().
+                    inflate(R.layout.list_separator, parent, false);
+            view.setText(separatorId);
+            return view;
+        }
+
+        int realPosition = getRealPosition(position);
+        if (!mCursor.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(), mCursor, parent);
+        } else {
+            newView = false;
+            v = convertView;
+        }
+        bindView(v, getContext(), mCursor);
+        bindSectionHeader(v, realPosition, mSectionHeaderDisplayEnabled);
+        return v;
+    }
+
+    private View getTotalContactCountView(ViewGroup parent) {
+        final LayoutInflater inflater = contactsListActivity.getLayoutInflater();
+        View view = inflater.inflate(R.layout.total_contacts, parent, false);
+
+        TextView totalContacts = (TextView) view.findViewById(R.id.totalContactsText);
+
+        String text;
+        int count = getRealCount();
+
+        if (contactsListActivity.mSearchMode
+                && !TextUtils.isEmpty(contactsListActivity.getTextFilter())) {
+            text = contactsListActivity.getQuantityText(count, R.string.listFoundAllContactsZero,
+                    R.plurals.searchFoundContacts);
+        } else {
+            if (contactsListActivity.mDisplayOnlyPhones) {
+                text = contactsListActivity.getQuantityText(count,
+                        R.string.listTotalPhoneContactsZero, R.plurals.listTotalPhoneContacts);
+            } else {
+                text = contactsListActivity.getQuantityText(count,
+                        R.string.listTotalAllContactsZero, R.plurals.listTotalAllContacts);
+            }
+        }
+        totalContacts.setText(text);
+        return view;
+    }
+
+    public boolean isSearchAllContactsItemPosition(int position) {
+        return contactsListActivity.mSearchMode && contactsListActivity.mMode != ContactsListActivity.MODE_PICK_MULTIPLE_PHONES && position == getCount() - 1;
+    }
+
+    private int getSeparatorId(int position) {
+        int separatorId = 0;
+        if (position == mFrequentSeparatorPos) {
+            separatorId = R.string.favoritesFrquentSeparator;
+        }
+        return separatorId;
+    }
+
+    @Override
+    public View newView(Context context, Cursor cursor, ViewGroup parent) {
+        final ContactListItemView view = new ContactListItemView(context, null);
+        view.setOnCallButtonClickListener(contactsListActivity);
+        view.setTag(new ContactsListActivity.ContactListItemCache());
+        return view;
+    }
+
+    @Override
+    public void bindView(View itemView, Context context, Cursor cursor) {
+        final ContactListItemView view = (ContactListItemView)itemView;
+        final ContactListItemCache cache = (ContactListItemCache) view.getTag();
+
+        int typeColumnIndex;
+        int dataColumnIndex;
+        int labelColumnIndex;
+        int defaultType;
+        int nameColumnIndex;
+        int phoneticNameColumnIndex;
+        int photoColumnIndex = ContactsListActivity.SUMMARY_PHOTO_ID_COLUMN_INDEX;
+        boolean displayAdditionalData = mDisplayAdditionalData;
+        boolean highlightingEnabled = false;
+        switch(contactsListActivity.mMode) {
+            case ContactsListActivity.MODE_PICK_MULTIPLE_PHONES:
+            case ContactsListActivity.MODE_PICK_PHONE:
+            case ContactsListActivity.MODE_LEGACY_PICK_PHONE:
+            case ContactsListActivity.MODE_QUERY_PICK_PHONE: {
+                nameColumnIndex = ContactsListActivity.PHONE_DISPLAY_NAME_COLUMN_INDEX;
+                phoneticNameColumnIndex = -1;
+                dataColumnIndex = ContactsListActivity.PHONE_NUMBER_COLUMN_INDEX;
+                typeColumnIndex = ContactsListActivity.PHONE_TYPE_COLUMN_INDEX;
+                labelColumnIndex = ContactsListActivity.PHONE_LABEL_COLUMN_INDEX;
+                defaultType = Phone.TYPE_HOME;
+                photoColumnIndex = ContactsListActivity.PHONE_PHOTO_ID_COLUMN_INDEX;
+                break;
+            }
+            case ContactsListActivity.MODE_PICK_POSTAL:
+            case ContactsListActivity.MODE_LEGACY_PICK_POSTAL: {
+                nameColumnIndex = ContactsListActivity.POSTAL_DISPLAY_NAME_COLUMN_INDEX;
+                phoneticNameColumnIndex = -1;
+                dataColumnIndex = ContactsListActivity.POSTAL_ADDRESS_COLUMN_INDEX;
+                typeColumnIndex = ContactsListActivity.POSTAL_TYPE_COLUMN_INDEX;
+                labelColumnIndex = ContactsListActivity.POSTAL_LABEL_COLUMN_INDEX;
+                defaultType = StructuredPostal.TYPE_HOME;
+                break;
+            }
+            default: {
+                nameColumnIndex = contactsListActivity.getSummaryDisplayNameColumnIndex();
+                if (contactsListActivity.mMode == ContactsListActivity.MODE_LEGACY_PICK_PERSON
+                        || contactsListActivity.mMode ==
+                            ContactsListActivity.MODE_LEGACY_PICK_OR_CREATE_PERSON) {
+                    phoneticNameColumnIndex = -1;
+                } else {
+                    phoneticNameColumnIndex =
+                        ContactsListActivity.SUMMARY_PHONETIC_NAME_COLUMN_INDEX;
+                }
+                dataColumnIndex = -1;
+                typeColumnIndex = -1;
+                labelColumnIndex = -1;
+                defaultType = Phone.TYPE_HOME;
+                displayAdditionalData = false;
+                highlightingEnabled = contactsListActivity.mHighlightWhenScrolling
+                        && contactsListActivity.mMode != ContactsListActivity.MODE_STREQUENT;
+            }
+        }
+
+        // Set the name
+        cursor.copyStringToBuffer(nameColumnIndex, cache.nameBuffer);
+        TextView nameView = view.getNameTextView();
+        int size = cache.nameBuffer.sizeCopied;
+        if (size != 0) {
+            if (highlightingEnabled) {
+                if (cache.textWithHighlighting == null) {
+                    cache.textWithHighlighting =
+                            contactsListActivity.mHighlightingAnimation.createTextWithHighlighting();
+                }
+                buildDisplayNameWithHighlighting(nameView, cursor, cache.nameBuffer,
+                        cache.highlightedTextBuffer, cache.textWithHighlighting);
+            } else {
+                nameView.setText(cache.nameBuffer.data, 0, size);
+            }
+        } else {
+            nameView.setText(mUnknownNameText);
+        }
+
+        // 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 requested
+        if (mDisplayPhotos) {
+            boolean useQuickContact = (contactsListActivity.mMode
+                    & ContactsListActivity.MODE_MASK_DISABLE_QUIKCCONTACT) == 0;
+
+            long photoId = 0;
+            if (!cursor.isNull(photoColumnIndex)) {
+                photoId = cursor.getLong(photoColumnIndex);
+            }
+
+            ImageView viewToUse;
+            if (useQuickContact) {
+                // Build soft lookup reference
+                final long contactId =
+                        cursor.getLong(ContactsListActivity.SUMMARY_ID_COLUMN_INDEX);
+                final String lookupKey =
+                        cursor.getString(ContactsListActivity.SUMMARY_LOOKUP_KEY_COLUMN_INDEX);
+                QuickContactBadge quickContact = view.getQuickContact();
+                quickContact.assignContactUri(Contacts.getLookupUri(contactId, lookupKey));
+                viewToUse = quickContact;
+            } else {
+                viewToUse = view.getPhotoView();
+            }
+
+            final int position = cursor.getPosition();
+            contactsListActivity.mPhotoLoader.loadPhoto(viewToUse, photoId);
+        }
+
+        if ((contactsListActivity.mMode & ContactsListActivity.MODE_MASK_NO_PRESENCE) == 0) {
+            // Set the proper icon (star or presence or nothing)
+            int serverStatus;
+            if (!cursor.isNull(ContactsListActivity.SUMMARY_PRESENCE_STATUS_COLUMN_INDEX)) {
+                serverStatus =
+                        cursor.getInt(ContactsListActivity.SUMMARY_PRESENCE_STATUS_COLUMN_INDEX);
+                Drawable icon = ContactPresenceIconUtil.getPresenceIcon(getContext(), serverStatus);
+                if (icon != null) {
+                    view.setPresence(icon);
+                } else {
+                    view.setPresence(null);
+                }
+            } else {
+                view.setPresence(null);
+            }
+        } else {
+            view.setPresence(null);
+        }
+
+        if (contactsListActivity.mShowSearchSnippets) {
+            boolean showSnippet = false;
+            String snippetMimeType =
+                    cursor.getString(ContactsListActivity.SUMMARY_SNIPPET_MIMETYPE_COLUMN_INDEX);
+            if (Email.CONTENT_ITEM_TYPE.equals(snippetMimeType)) {
+                String email =
+                        cursor.getString(ContactsListActivity.SUMMARY_SNIPPET_DATA1_COLUMN_INDEX);
+                if (!TextUtils.isEmpty(email)) {
+                    view.setSnippet(email);
+                    showSnippet = true;
+                }
+            } else if (Organization.CONTENT_ITEM_TYPE.equals(snippetMimeType)) {
+                String company =
+                        cursor.getString(ContactsListActivity.SUMMARY_SNIPPET_DATA1_COLUMN_INDEX);
+                String title =
+                        cursor.getString(ContactsListActivity.SUMMARY_SNIPPET_DATA4_COLUMN_INDEX);
+                if (!TextUtils.isEmpty(company)) {
+                    if (!TextUtils.isEmpty(title)) {
+                        view.setSnippet(company + " / " + title);
+                    } else {
+                        view.setSnippet(company);
+                    }
+                    showSnippet = true;
+                } else if (!TextUtils.isEmpty(title)) {
+                    view.setSnippet(title);
+                    showSnippet = true;
+                }
+            } else if (Nickname.CONTENT_ITEM_TYPE.equals(snippetMimeType)) {
+                String nickname =
+                        cursor.getString(ContactsListActivity.SUMMARY_SNIPPET_DATA1_COLUMN_INDEX);
+                if (!TextUtils.isEmpty(nickname)) {
+                    view.setSnippet(nickname);
+                    showSnippet = true;
+                }
+            }
+
+            if (!showSnippet) {
+                view.setSnippet(null);
+            }
+        }
+
+        if (!displayAdditionalData) {
+            if (phoneticNameColumnIndex != -1) {
+
+                // Set the name
+                cursor.copyStringToBuffer(phoneticNameColumnIndex, cache.phoneticNameBuffer);
+                int phoneticNameSize = cache.phoneticNameBuffer.sizeCopied;
+                if (phoneticNameSize != 0) {
+                    view.setLabel(cache.phoneticNameBuffer.data, phoneticNameSize);
+                } else {
+                    view.setLabel(null);
+                }
+            } else {
+                view.setLabel(null);
+            }
+            return;
+        }
+
+        // Set the data.
+        cursor.copyStringToBuffer(dataColumnIndex, cache.dataBuffer);
+
+        size = cache.dataBuffer.sizeCopied;
+        view.setData(cache.dataBuffer.data, size);
+
+        // Set the label.
+        if (!cursor.isNull(typeColumnIndex)) {
+            final int type = cursor.getInt(typeColumnIndex);
+            final String label = cursor.getString(labelColumnIndex);
+
+            if (contactsListActivity.mMode == ContactsListActivity.MODE_LEGACY_PICK_POSTAL
+                    || contactsListActivity.mMode == ContactsListActivity.MODE_PICK_POSTAL) {
+                // TODO cache
+                view.setLabel(StructuredPostal.getTypeLabel(context.getResources(), type,
+                        label));
+            } else {
+                // TODO cache
+                view.setLabel(Phone.getTypeLabel(context.getResources(), type, label));
+            }
+        } else {
+            view.setLabel(null);
+        }
+    }
+
+    /**
+     * Computes the span of the display name that has highlighted parts and configures
+     * the display name text view accordingly.
+     */
+    protected void buildDisplayNameWithHighlighting(TextView textView, Cursor cursor,
+            CharArrayBuffer buffer1, CharArrayBuffer buffer2,
+            TextWithHighlighting textWithHighlighting) {
+        int oppositeDisplayOrderColumnIndex;
+        if (contactsListActivity.mDisplayOrder ==
+                ContactsContract.Preferences.DISPLAY_ORDER_PRIMARY) {
+            oppositeDisplayOrderColumnIndex =
+                    ContactsListActivity.SUMMARY_DISPLAY_NAME_ALTERNATIVE_COLUMN_INDEX;
+        } else {
+            oppositeDisplayOrderColumnIndex =
+                    ContactsListActivity.SUMMARY_DISPLAY_NAME_PRIMARY_COLUMN_INDEX;
+        }
+        cursor.copyStringToBuffer(oppositeDisplayOrderColumnIndex, buffer2);
+
+        textWithHighlighting.setText(buffer1, buffer2);
+        textView.setText(textWithHighlighting);
+    }
+
+    protected void bindSectionHeader(View itemView, int position, boolean displaySectionHeaders) {
+        final ContactListItemView view = (ContactListItemView)itemView;
+        final ContactListItemCache cache = (ContactListItemCache) view.getTag();
+        if (!displaySectionHeaders) {
+            view.setSectionHeader(null);
+            view.setDividerVisible(true);
+        } else {
+            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);
+            }
+        }
+    }
+
+    @Override
+    public void changeCursor(Cursor cursor) {
+        if (cursor != null) {
+            setLoading(false);
+        }
+
+        // Get the split between starred and frequent items, if the mode is strequent
+        mFrequentSeparatorPos = ListView.INVALID_POSITION;
+        int cursorCount = 0;
+        if (cursor != null && (cursorCount = cursor.getCount()) > 0
+                && contactsListActivity.mMode == ContactsListActivity.MODE_STREQUENT) {
+            cursor.move(-1);
+            for (int i = 0; cursor.moveToNext(); i++) {
+                int starred = cursor.getInt(ContactsListActivity.SUMMARY_STARRED_COLUMN_INDEX);
+                if (starred == 0) {
+                    if (i > 0) {
+                        // Only add the separator when there are starred items present
+                        mFrequentSeparatorPos = i;
+                    }
+                    break;
+                }
+            }
+        }
+
+        if (cursor != null && contactsListActivity.mSearchResultsMode) {
+            TextView foundContactsText = (TextView)contactsListActivity
+                    .findViewById(R.id.search_results_found);
+            String text = contactsListActivity.getQuantityText(cursor.getCount(),
+                    R.string.listFoundAllContactsZero, R.plurals.listFoundAllContacts);
+            foundContactsText.setText(text);
+        }
+
+        if (contactsListActivity.mEmptyView != null && (cursor == null || cursor.getCount() == 0)) {
+            prepareEmptyView();
+        }
+
+        super.changeCursor(cursor);
+
+        // Update the indexer for the fast scroll widget
+        updateIndexer(cursor);
+    }
+
+    protected void prepareEmptyView() {
+        contactsListActivity.mEmptyView.show(contactsListActivity.mSearchMode,
+                contactsListActivity.mDisplayOnlyPhones,
+                contactsListActivity.mMode == ContactsListActivity.MODE_STREQUENT
+                || contactsListActivity.mMode == ContactsListActivity.MODE_STARRED,
+                contactsListActivity.mMode == ContactsListActivity.MODE_QUERY
+                || contactsListActivity.mMode == ContactsListActivity.MODE_QUERY_PICK
+                || contactsListActivity.mMode == ContactsListActivity.MODE_QUERY_PICK_PHONE
+                || contactsListActivity.mMode == ContactsListActivity.MODE_QUERY_PICK_TO_VIEW
+                || contactsListActivity.mMode == ContactsListActivity.MODE_QUERY_PICK_TO_EDIT,
+                contactsListActivity.mShortcutAction != null,
+                false,
+                false);
+    }
+
+    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);
+        }
+    }
+
+    /**
+     * Run the query on a helper thread. Beware that this code does not run
+     * on the main UI thread!
+     */
+    @Override
+    public Cursor runQueryOnBackgroundThread(CharSequence constraint) {
+        return contactsListActivity.doFilter(constraint.toString());
+    }
+
+    @Override
+    public boolean areAllItemsEnabled() {
+        return contactsListActivity.mMode != ContactsListActivity.MODE_STARRED
+            && !contactsListActivity.mShowNumberOfContacts;
+    }
+
+    @Override
+    public boolean isEnabled(int position) {
+        if (contactsListActivity.mShowNumberOfContacts) {
+            if (position == 0) {
+                return false;
+            }
+            position--;
+        }
+        return position != mFrequentSeparatorPos;
+    }
+
+    @Override
+    public int getCount() {
+        if (!mDataValid) {
+            return 0;
+        }
+        int superCount = super.getCount();
+
+        if (contactsListActivity.mShowNumberOfContacts
+                && (contactsListActivity.mSearchMode || superCount > 0)) {
+            // We don't want to count this header if it's the only thing visible, so that
+            // the empty text will display.
+            superCount++;
+        }
+
+        if (contactsListActivity.mSearchMode
+                && contactsListActivity.mMode != ContactsListActivity.MODE_PICK_MULTIPLE_PHONES) {
+            // Last element in the list is the "Find
+            superCount++;
+        }
+
+        // We do not show the "Create New" button in Search mode
+        if ((contactsListActivity.mMode & ContactsListActivity.MODE_MASK_CREATE_NEW) != 0
+                && !contactsListActivity.mSearchMode) {
+            // Count the "Create new contact" line
+            superCount++;
+        }
+
+        if (mFrequentSeparatorPos != ListView.INVALID_POSITION) {
+            // When showing strequent list, we have an additional list item - the separator.
+            return superCount + 1;
+        } else {
+            return superCount;
+        }
+    }
+
+    /**
+     * Gets the actual count of contacts and excludes all the headers.
+     */
+    public int getRealCount() {
+        return super.getCount();
+    }
+
+    @Override
+    protected int getCursorPosition(int position) {
+        return getRealPosition(position);
+    }
+
+    protected int getRealPosition(int pos) {
+        if (contactsListActivity.mShowNumberOfContacts) {
+            pos--;
+        }
+
+        if ((contactsListActivity.mMode & ContactsListActivity.MODE_MASK_CREATE_NEW) != 0
+                && !contactsListActivity.mSearchMode) {
+            return pos - 1;
+        }
+
+        if (mFrequentSeparatorPos == ListView.INVALID_POSITION) {
+            // No separator, identity map
+            return pos;
+        } else if (pos <= mFrequentSeparatorPos) {
+            // Before or at the separator, identity map
+            return pos;
+        } else {
+            // After the separator, remove 1 from the pos to get the real underlying pos
+            return pos - 1;
+        }
+    }
+
+    @Override
+    public Object getItem(int pos) {
+        if (isSearchAllContactsItemPosition(pos)){
+            return null;
+        } else {
+            int realPosition = getRealPosition(pos);
+            if (realPosition < 0) {
+                return null;
+            }
+            return super.getItem(realPosition);
+        }
+    }
+
+    @Override
+    public long getItemId(int pos) {
+        if (isSearchAllContactsItemPosition(pos)) {
+            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/ContactsIntentResolver.java b/src/com/android/contacts/list/ContactsIntentResolver.java
new file mode 100644
index 0000000..034e64f
--- /dev/null
+++ b/src/com/android/contacts/list/ContactsIntentResolver.java
@@ -0,0 +1,515 @@
+/*
+ * 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.ContactsSearchManager;
+import com.android.contacts.JoinContactActivity;
+import com.android.contacts.R;
+
+import android.app.Activity;
+import android.app.SearchManager;
+import android.content.ContentUris;
+import android.content.Intent;
+import android.content.UriMatcher;
+import android.database.Cursor;
+import android.net.Uri;
+import android.os.Bundle;
+import android.provider.ContactsContract;
+import android.provider.Contacts.ContactMethods;
+import android.provider.Contacts.People;
+import android.provider.Contacts.Phones;
+import android.provider.ContactsContract.Contacts;
+import android.provider.ContactsContract.Data;
+import android.provider.ContactsContract.Intents;
+import android.provider.ContactsContract.RawContacts;
+import android.provider.ContactsContract.CommonDataKinds.Phone;
+import android.provider.ContactsContract.CommonDataKinds.StructuredPostal;
+import android.provider.ContactsContract.Intents.Insert;
+import android.provider.ContactsContract.Intents.UI;
+import android.text.TextUtils;
+import android.util.Log;
+
+/**
+ * Maintains contact list configuration, which is a transient object that
+ * deals with intents, saved instance configuration etc.
+ */
+@SuppressWarnings("deprecation")
+public class ContactsIntentResolver {
+
+    private static final String TAG = "ContactsListActivity";
+
+    private static final String SHORTCUT_ACTION_KEY = "shortcutAction";
+
+    /**
+     * The action for the join contact activity.
+     * <p>
+     * Input: extra field {@link #EXTRA_AGGREGATE_ID} is the aggregate ID.
+     *
+     * TODO: move to {@link ContactsContract}.
+     */
+    public static final String JOIN_AGGREGATE =
+            "com.android.contacts.action.JOIN_AGGREGATE";
+
+    /**
+     * Used with {@link #JOIN_AGGREGATE} to give it the target for aggregation.
+     * <p>
+     * Type: LONG
+     */
+    public static final String EXTRA_AGGREGATE_ID =
+            "com.android.contacts.action.AGGREGATE_ID";
+
+    /** Mask for picker mode */
+    static final int MODE_MASK_PICKER = 0x80000000;
+    /** Mask for no presence mode */
+    static final int MODE_MASK_NO_PRESENCE = 0x40000000;
+    /** Mask for enabling list filtering */
+    static final int MODE_MASK_NO_FILTER = 0x20000000;
+    /** Mask for having a "create new contact" header in the list */
+    static final int MODE_MASK_CREATE_NEW = 0x10000000;
+    /** Mask for showing photos in the list */
+    static final int MODE_MASK_SHOW_PHOTOS = 0x08000000;
+    /** Mask for hiding additional information e.g. primary phone number in the list */
+    static final int MODE_MASK_NO_DATA = 0x04000000;
+    /** Mask for showing a call button in the list */
+    static final int MODE_MASK_SHOW_CALL_BUTTON = 0x02000000;
+    /** Mask to disable quickcontact (images will show as normal images) */
+    static final int MODE_MASK_DISABLE_QUIKCCONTACT = 0x01000000;
+    /** Mask to show the total number of contacts at the top */
+    static final int MODE_MASK_SHOW_NUMBER_OF_CONTACTS = 0x00800000;
+
+    /** Unknown mode */
+    static final int MODE_UNKNOWN = 0;
+    /** Default mode */
+    static final int MODE_DEFAULT = 4 | MODE_MASK_SHOW_PHOTOS | MODE_MASK_SHOW_NUMBER_OF_CONTACTS;
+    /** Custom mode */
+    static final int MODE_CUSTOM = 8;
+    /** Show all starred contacts */
+    static final int MODE_STARRED = 20 | MODE_MASK_SHOW_PHOTOS;
+    /** Show frequently contacted contacts */
+    static final int MODE_FREQUENT = 30 | MODE_MASK_SHOW_PHOTOS;
+    /** Show starred and the frequent */
+    static final int MODE_STREQUENT = 35 | MODE_MASK_SHOW_PHOTOS | MODE_MASK_SHOW_CALL_BUTTON;
+    /** Show all contacts and pick them when clicking */
+    static final int MODE_PICK_CONTACT = 40 | MODE_MASK_PICKER | MODE_MASK_SHOW_PHOTOS
+            | MODE_MASK_DISABLE_QUIKCCONTACT;
+    /** Show all contacts as well as the option to create a new one */
+    static final int MODE_PICK_OR_CREATE_CONTACT = 42 | MODE_MASK_PICKER | MODE_MASK_CREATE_NEW
+            | MODE_MASK_SHOW_PHOTOS | MODE_MASK_DISABLE_QUIKCCONTACT;
+    /** Show all people through the legacy provider and pick them when clicking */
+    static final int MODE_LEGACY_PICK_PERSON = 43 | MODE_MASK_PICKER
+            | MODE_MASK_DISABLE_QUIKCCONTACT;
+    /** Show all people through the legacy provider as well as the option to create a new one */
+    static final int MODE_LEGACY_PICK_OR_CREATE_PERSON = 44 | MODE_MASK_PICKER
+            | MODE_MASK_CREATE_NEW | MODE_MASK_DISABLE_QUIKCCONTACT;
+    /** Show all contacts and pick them when clicking, and allow creating a new contact */
+    static final int MODE_INSERT_OR_EDIT_CONTACT = 45 | MODE_MASK_PICKER | MODE_MASK_CREATE_NEW
+            | MODE_MASK_SHOW_PHOTOS | MODE_MASK_DISABLE_QUIKCCONTACT;
+    /** Show all phone numbers and pick them when clicking */
+    static final int MODE_PICK_PHONE = 50 | MODE_MASK_PICKER | MODE_MASK_NO_PRESENCE;
+    /** Show all phone numbers through the legacy provider and pick them when clicking */
+    static final int MODE_LEGACY_PICK_PHONE =
+            51 | MODE_MASK_PICKER | MODE_MASK_NO_PRESENCE | MODE_MASK_NO_FILTER;
+    /** Show all postal addresses and pick them when clicking */
+    static final int MODE_PICK_POSTAL =
+            55 | MODE_MASK_PICKER | MODE_MASK_NO_PRESENCE | MODE_MASK_NO_FILTER;
+    /** Show all postal addresses and pick them when clicking */
+    static final int MODE_LEGACY_PICK_POSTAL =
+            56 | MODE_MASK_PICKER | MODE_MASK_NO_PRESENCE | MODE_MASK_NO_FILTER;
+    static final int MODE_GROUP = 57 | MODE_MASK_SHOW_PHOTOS;
+    /** Run a search query */
+    static final int MODE_QUERY = 60 | MODE_MASK_SHOW_PHOTOS | MODE_MASK_NO_FILTER
+            | MODE_MASK_SHOW_NUMBER_OF_CONTACTS;
+    /** Run a search query in PICK mode, but that still launches to VIEW */
+    static final int MODE_QUERY_PICK_TO_VIEW = 65 | MODE_MASK_SHOW_PHOTOS | MODE_MASK_PICKER
+            | MODE_MASK_SHOW_NUMBER_OF_CONTACTS;
+
+    /** Show join suggestions followed by an A-Z list */
+    static final int MODE_JOIN_CONTACT = 70 | MODE_MASK_PICKER | MODE_MASK_NO_PRESENCE
+            | MODE_MASK_NO_DATA | MODE_MASK_SHOW_PHOTOS | MODE_MASK_DISABLE_QUIKCCONTACT;
+
+    /** Run a search query in a PICK mode */
+    static final int MODE_QUERY_PICK = 75 | MODE_MASK_SHOW_PHOTOS | MODE_MASK_NO_FILTER
+            | MODE_MASK_PICKER | MODE_MASK_DISABLE_QUIKCCONTACT | MODE_MASK_SHOW_NUMBER_OF_CONTACTS;
+
+    /** Run a search query in a PICK_PHONE mode */
+    static final int MODE_QUERY_PICK_PHONE = 80 | MODE_MASK_NO_FILTER | MODE_MASK_PICKER
+            | MODE_MASK_SHOW_NUMBER_OF_CONTACTS;
+
+    /** Run a search query in PICK mode, but that still launches to EDIT */
+    static final int MODE_QUERY_PICK_TO_EDIT = 85 | MODE_MASK_NO_FILTER | MODE_MASK_SHOW_PHOTOS
+            | MODE_MASK_PICKER | MODE_MASK_SHOW_NUMBER_OF_CONTACTS;
+
+    /**
+     * Show all phone numbers and do multiple pick when clicking. This mode has phone filtering
+     * feature, but doesn't support 'search for all contacts'.
+     */
+    static final int MODE_PICK_MULTIPLE_PHONES = 80 | MODE_MASK_PICKER
+            | MODE_MASK_NO_PRESENCE | MODE_MASK_SHOW_PHOTOS | MODE_MASK_DISABLE_QUIKCCONTACT;
+
+    /**
+     * An action used to do perform search while in a contact picker.  It is initiated
+     * by the ContactListActivity itself.
+     */
+    private static final String ACTION_SEARCH_INTERNAL = "com.android.contacts.INTERNAL_SEARCH";
+
+    // Uri matcher for contact id
+    private static final int CONTACTS_ID = 1001;
+    private static final UriMatcher sContactsIdMatcher;
+    static {
+        sContactsIdMatcher = new UriMatcher(UriMatcher.NO_MATCH);
+        sContactsIdMatcher.addURI(ContactsContract.AUTHORITY, "contacts/#", CONTACTS_ID);
+    }
+
+    private final Activity mContext;
+    private boolean mValid = true;
+    private Intent mRedirectIntent;
+    private CharSequence mTitle;
+
+    // TODO: make all these fields private.  They should only remain public while we
+    // are refactoring ContactListActivity.
+    public int mMode = MODE_DEFAULT;
+    public int mQueryMode = QUERY_MODE_NONE;
+    public boolean mSearchMode;
+    public boolean mShowSearchSnippets;
+    public String mInitialFilter;
+    public boolean mDisplayOnlyPhones;
+    public String mShortcutAction;
+    public boolean mSearchResultsMode;
+    public boolean mShowNumberOfContacts;
+    public String mGroupName;
+
+    /**
+     * Internal query type when in mode {@link #MODE_QUERY_PICK_TO_VIEW}.
+     */
+    private static final int QUERY_MODE_NONE = -1;
+    private static final int QUERY_MODE_MAILTO = 1;
+    private static final int QUERY_MODE_TEL = 2;
+
+    private final ContactsApplicationController mAppController;
+
+    public ContactsIntentResolver(Activity context, ContactsApplicationController appController) {
+        this.mContext = context;
+        this.mAppController = appController;
+    }
+
+    public void setIntent(Intent intent) {
+        String action = intent.getAction();
+        String component = intent.getComponent().getClassName();
+
+        // Allow the title to be set to a custom String using an extra on the intent
+        String title = intent.getStringExtra(UI.TITLE_EXTRA_KEY);
+        if (title != null) {
+            mTitle = title;
+        }
+
+        String type = intent.getType();
+
+        // When we get a FILTER_CONTACTS_ACTION, it represents search in the context
+        // of some other action. Let's retrieve the original action to provide proper
+        // context for the search queries.
+        if (UI.FILTER_CONTACTS_ACTION.equals(action)) {
+            mSearchMode = true;
+            mShowSearchSnippets = true;
+            Bundle extras = intent.getExtras();
+            if (extras != null) {
+                mInitialFilter = extras.getString(UI.FILTER_TEXT_EXTRA_KEY);
+                String originalAction =
+                        extras.getString(ContactsSearchManager.ORIGINAL_ACTION_EXTRA_KEY);
+                if (originalAction != null) {
+                    action = originalAction;
+                }
+                String originalComponent =
+                        extras.getString(ContactsSearchManager.ORIGINAL_COMPONENT_EXTRA_KEY);
+                if (originalComponent != null) {
+                    component = originalComponent;
+                }
+                String originalType =
+                    extras.getString(ContactsSearchManager.ORIGINAL_TYPE_EXTRA_KEY);
+                if (originalType != null) {
+                    type = originalType;
+                }
+            } else {
+                mInitialFilter = null;
+            }
+        }
+
+        Log.i(TAG, "Called with action: " + action);
+        mMode = MODE_UNKNOWN;
+        if (UI.LIST_DEFAULT.equals(action) || UI.FILTER_CONTACTS_ACTION.equals(action)) {
+            mMode = MODE_DEFAULT;
+            // When mDefaultMode is true the mode is set in onResume(), since the preferneces
+            // activity may change it whenever this activity isn't running
+        } else if (UI.LIST_GROUP_ACTION.equals(action)) {
+            mMode = MODE_GROUP;
+            mGroupName = intent.getStringExtra(UI.GROUP_NAME_EXTRA_KEY);
+            if (TextUtils.isEmpty(mGroupName)) {
+                mValid = false;
+                return;
+            }
+        } else if (UI.LIST_ALL_CONTACTS_ACTION.equals(action)) {
+            mMode = MODE_CUSTOM;
+            mDisplayOnlyPhones = false;
+        } else if (UI.LIST_STARRED_ACTION.equals(action)) {
+            mMode = mSearchMode ? MODE_DEFAULT : MODE_STARRED;
+        } else if (UI.LIST_FREQUENT_ACTION.equals(action)) {
+            mMode = mSearchMode ? MODE_DEFAULT : MODE_FREQUENT;
+        } else if (UI.LIST_STREQUENT_ACTION.equals(action)) {
+            mMode = mSearchMode ? MODE_DEFAULT : MODE_STREQUENT;
+        } else if (UI.LIST_CONTACTS_WITH_PHONES_ACTION.equals(action)) {
+            mMode = MODE_CUSTOM;
+            mDisplayOnlyPhones = true;
+        } else if (Intent.ACTION_PICK.equals(action)) {
+            // XXX These should be showing the data from the URI given in
+            // the Intent.
+           // TODO : Does it work in mSearchMode?
+            final String resolvedType = intent.resolveType(mContext);
+            if (Contacts.CONTENT_TYPE.equals(resolvedType)) {
+                mMode = MODE_PICK_CONTACT;
+            } else if (People.CONTENT_TYPE.equals(resolvedType)) {
+                mMode = MODE_LEGACY_PICK_PERSON;
+            } else if (Phone.CONTENT_TYPE.equals(resolvedType)) {
+                mMode = MODE_PICK_PHONE;
+            } else if (Phones.CONTENT_TYPE.equals(resolvedType)) {
+                mMode = MODE_LEGACY_PICK_PHONE;
+            } else if (StructuredPostal.CONTENT_TYPE.equals(resolvedType)) {
+                mMode = MODE_PICK_POSTAL;
+            } else if (ContactMethods.CONTENT_POSTAL_TYPE.equals(resolvedType)) {
+                mMode = MODE_LEGACY_PICK_POSTAL;
+            }
+        } else if (Intent.ACTION_CREATE_SHORTCUT.equals(action)) {
+            if (component.equals("alias.DialShortcut")) {
+                mMode = MODE_PICK_PHONE;
+                mShortcutAction = Intent.ACTION_CALL;
+                mShowSearchSnippets = false;
+                mTitle = mContext.getString(R.string.callShortcutActivityTitle);
+            } else if (component.equals("alias.MessageShortcut")) {
+                mMode = MODE_PICK_PHONE;
+                mShortcutAction = Intent.ACTION_SENDTO;
+                mShowSearchSnippets = false;
+                mTitle = mContext.getString(R.string.messageShortcutActivityTitle);
+            } else if (mSearchMode) {
+                mMode = MODE_PICK_CONTACT;
+                mShortcutAction = Intent.ACTION_VIEW;
+                mTitle = mContext.getString(R.string.shortcutActivityTitle);
+            } else {
+                mMode = MODE_PICK_OR_CREATE_CONTACT;
+                mShortcutAction = Intent.ACTION_VIEW;
+                mTitle = mContext.getString(R.string.shortcutActivityTitle);
+            }
+        } else if (Intent.ACTION_GET_CONTENT.equals(action)) {
+            if (Contacts.CONTENT_ITEM_TYPE.equals(type)) {
+                if (mSearchMode) {
+                    mMode = MODE_PICK_CONTACT;
+                } else {
+                    mMode = MODE_PICK_OR_CREATE_CONTACT;
+                }
+            } else if (Phone.CONTENT_ITEM_TYPE.equals(type)) {
+                mMode = MODE_PICK_PHONE;
+            } else if (Phones.CONTENT_ITEM_TYPE.equals(type)) {
+                mMode = MODE_LEGACY_PICK_PHONE;
+            } else if (StructuredPostal.CONTENT_ITEM_TYPE.equals(type)) {
+                mMode = MODE_PICK_POSTAL;
+            } else if (ContactMethods.CONTENT_POSTAL_ITEM_TYPE.equals(type)) {
+                mMode = MODE_LEGACY_PICK_POSTAL;
+            }  else if (People.CONTENT_ITEM_TYPE.equals(type)) {
+                if (mSearchMode) {
+                    mMode = MODE_LEGACY_PICK_PERSON;
+                } else {
+                    mMode = MODE_LEGACY_PICK_OR_CREATE_PERSON;
+                }
+            }
+
+        } else if (Intent.ACTION_INSERT_OR_EDIT.equals(action)) {
+            mMode = MODE_INSERT_OR_EDIT_CONTACT;
+        } else if (Intent.ACTION_SEARCH.equals(action)) {
+            // See if the suggestion was clicked with a search action key (call button)
+            if ("call".equals(intent.getStringExtra(SearchManager.ACTION_MSG))) {
+                String query = intent.getStringExtra(SearchManager.QUERY);
+                if (!TextUtils.isEmpty(query)) {
+                    Intent newIntent = new Intent(Intent.ACTION_CALL_PRIVILEGED,
+                            Uri.fromParts("tel", query, null));
+                    mRedirectIntent = newIntent;
+                }
+                return;
+            }
+
+            // See if search request has extras to specify query
+            if (intent.hasExtra(Insert.EMAIL)) {
+                mMode = MODE_QUERY_PICK_TO_VIEW;
+                mQueryMode = QUERY_MODE_MAILTO;
+                mInitialFilter = intent.getStringExtra(Insert.EMAIL);
+            } else if (intent.hasExtra(Insert.PHONE)) {
+                mMode = MODE_QUERY_PICK_TO_VIEW;
+                mQueryMode = QUERY_MODE_TEL;
+                mInitialFilter = intent.getStringExtra(Insert.PHONE);
+            } else {
+                // Otherwise handle the more normal search case
+                mMode = MODE_QUERY;
+                mShowSearchSnippets = true;
+                mInitialFilter = intent.getStringExtra(SearchManager.QUERY);
+            }
+            mSearchResultsMode = true;
+        } else if (ACTION_SEARCH_INTERNAL.equals(action)) {
+            String originalAction = null;
+            Bundle extras = intent.getExtras();
+            if (extras != null) {
+                originalAction = extras.getString(ContactsSearchManager.ORIGINAL_ACTION_EXTRA_KEY);
+            }
+            mShortcutAction = intent.getStringExtra(SHORTCUT_ACTION_KEY);
+
+            if (Intent.ACTION_INSERT_OR_EDIT.equals(originalAction)) {
+                mMode = MODE_QUERY_PICK_TO_EDIT;
+                mShowSearchSnippets = true;
+                mInitialFilter = intent.getStringExtra(SearchManager.QUERY);
+            } else if (mShortcutAction != null && intent.hasExtra(Insert.PHONE)) {
+                mMode = MODE_QUERY_PICK_PHONE;
+                mQueryMode = QUERY_MODE_TEL;
+                mInitialFilter = intent.getStringExtra(Insert.PHONE);
+            } else {
+                mMode = MODE_QUERY_PICK;
+                mQueryMode = QUERY_MODE_NONE;
+                mShowSearchSnippets = true;
+                mInitialFilter = intent.getStringExtra(SearchManager.QUERY);
+            }
+            mSearchResultsMode = true;
+        // Since this is the filter activity it receives all intents
+        // dispatched from the SearchManager for security reasons
+        // so we need to re-dispatch from here to the intended target.
+        } else if (Intents.SEARCH_SUGGESTION_CLICKED.equals(action)) {
+            Uri data = intent.getData();
+            Uri telUri = null;
+            if (sContactsIdMatcher.match(data) == CONTACTS_ID) {
+                long contactId = Long.valueOf(data.getLastPathSegment());
+                final Cursor cursor = queryPhoneNumbers(contactId);
+                if (cursor != null) {
+                    if (cursor.getCount() == 1 && cursor.moveToFirst()) {
+                        int phoneNumberIndex = cursor.getColumnIndex(Phone.NUMBER);
+                        String phoneNumber = cursor.getString(phoneNumberIndex);
+                        telUri = Uri.parse("tel:" + phoneNumber);
+                    }
+                    cursor.close();
+                }
+            }
+            // See if the suggestion was clicked with a search action key (call button)
+            Intent newIntent;
+            if ("call".equals(intent.getStringExtra(SearchManager.ACTION_MSG)) && telUri != null) {
+                newIntent = new Intent(Intent.ACTION_CALL_PRIVILEGED, telUri);
+            } else {
+                newIntent = new Intent(Intent.ACTION_VIEW, data);
+            }
+            mRedirectIntent = newIntent;
+            return;
+        } else if (Intents.SEARCH_SUGGESTION_DIAL_NUMBER_CLICKED.equals(action)) {
+            Intent newIntent = new Intent(Intent.ACTION_CALL_PRIVILEGED, intent.getData());
+            mRedirectIntent = newIntent;
+            return;
+        } else if (Intents.SEARCH_SUGGESTION_CREATE_CONTACT_CLICKED.equals(action)) {
+            // TODO actually support this in EditContactActivity.
+            String number = intent.getData().getSchemeSpecificPart();
+            Intent newIntent = new Intent(Intent.ACTION_INSERT, Contacts.CONTENT_URI);
+            newIntent.putExtra(Intents.Insert.PHONE, number);
+            mRedirectIntent = newIntent;
+            return;
+        } else if (JoinContactActivity.JOIN_CONTACT.equals(action)) {
+            mMode = MODE_PICK_CONTACT;
+        } else if (Intents.ACTION_GET_MULTIPLE_PHONES.equals(action)) {
+            if (mSearchMode) {
+                mShowSearchSnippets = false;
+            }
+            if (Phone.CONTENT_TYPE.equals(type)) {
+                mMode = MODE_PICK_MULTIPLE_PHONES;
+            } else {
+                // TODO support other content types
+                Log.e(TAG, "Intent " + action + " is not supported for type " + type);
+                mValid = false;
+            }
+        }
+        if (mMode == MODE_UNKNOWN) {
+            mMode = MODE_DEFAULT;
+        }
+
+        if (((mMode & MODE_MASK_SHOW_NUMBER_OF_CONTACTS) != 0 || mSearchMode)
+                && !mSearchResultsMode) {
+            mShowNumberOfContacts = true;
+        }
+    }
+
+    public boolean isValid() {
+        return mValid;
+    }
+
+    public Intent getRedirectIntent() {
+        return mRedirectIntent;
+    }
+
+    public CharSequence getActivityTitle() {
+        return mTitle;
+    }
+
+    private Cursor queryPhoneNumbers(long contactId) {
+        Uri baseUri = ContentUris.withAppendedId(Contacts.CONTENT_URI, contactId);
+        Uri dataUri = Uri.withAppendedPath(baseUri, Contacts.Data.CONTENT_DIRECTORY);
+
+        Cursor c = mContext.getContentResolver().query(dataUri,
+                new String[] {Phone._ID, Phone.NUMBER, Phone.IS_SUPER_PRIMARY,
+                RawContacts.ACCOUNT_TYPE, Phone.TYPE, Phone.LABEL},
+                Data.MIMETYPE + "=?", new String[] {Phone.CONTENT_ITEM_TYPE}, null);
+        if (c != null && c.moveToFirst()) {
+            return c;
+        }
+        return null;
+    }
+
+    public ContactEntryListConfiguration getConfiguration() {
+        ContactEntryListConfiguration config;
+        switch (mMode) {
+            case MODE_DEFAULT: {
+                config = new MainContactListConfiguration(mContext, mAppController);
+                if (!mSearchMode) {
+                    config.setSectionHeaderDisplayEnabled(true);
+                }
+                break;
+            }
+            case MODE_LEGACY_PICK_POSTAL:
+            case MODE_PICK_POSTAL:
+            case MODE_LEGACY_PICK_PHONE:
+            case MODE_PICK_PHONE:
+            case MODE_STREQUENT:
+            case MODE_FREQUENT: {
+                config = new DefaultContactListConfiguration(mContext, mAppController);
+                break;
+            }
+            case MODE_PICK_MULTIPLE_PHONES: {
+                config = new MultiplePhonePickerConfiguration(mContext, mAppController);
+                break;
+            }
+            default: {
+                config = new DefaultContactListConfiguration(mContext, mAppController);
+                if (!mSearchMode) {
+                    config.setSectionHeaderDisplayEnabled(true);
+                }
+            }
+        }
+
+        if ((mMode & MODE_MASK_SHOW_PHOTOS) == MODE_MASK_SHOW_PHOTOS) {
+            config.setPhotoLoaderEnabled(true);
+        }
+
+        return config;
+    }
+}
diff --git a/src/com/android/contacts/list/DefaultContactListConfiguration.java b/src/com/android/contacts/list/DefaultContactListConfiguration.java
new file mode 100644
index 0000000..a681f3a
--- /dev/null
+++ b/src/com/android/contacts/list/DefaultContactListConfiguration.java
@@ -0,0 +1,47 @@
+/*
+ * 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.ContactsListActivity;
+
+import android.content.Context;
+import android.widget.ListAdapter;
+
+/**
+ * Configuration for the default contact list.
+ */
+public class DefaultContactListConfiguration extends ContactEntryListConfiguration {
+
+    public DefaultContactListConfiguration(Context context,
+            ContactsApplicationController applicationController) {
+        super(context, applicationController);
+    }
+
+    @Override
+    public ListAdapter createListAdapter() {
+        ContactItemListAdapter adapter =
+                new ContactItemListAdapter((ContactsListActivity)getContext());
+        adapter.setSectionHeaderDisplayEnabled(isSectionHeaderDisplayEnabled());
+        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/MainContactListConfiguration.java b/src/com/android/contacts/list/MainContactListConfiguration.java
new file mode 100644
index 0000000..565f7e1
--- /dev/null
+++ b/src/com/android/contacts/list/MainContactListConfiguration.java
@@ -0,0 +1,47 @@
+/*
+ * 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.ContactsListActivity;
+
+import android.content.Context;
+import android.widget.ListAdapter;
+
+/**
+ * Configuration for the default contact list.
+ */
+public class MainContactListConfiguration extends ContactEntryListConfiguration {
+
+    public MainContactListConfiguration(Context context,
+            ContactsApplicationController applicationController) {
+        super(context, applicationController);
+    }
+
+    @Override
+    public ListAdapter createListAdapter() {
+        ContactItemListAdapter adapter =
+                new ContactItemListAdapter((ContactsListActivity)getContext());
+        adapter.setSectionHeaderDisplayEnabled(isSectionHeaderDisplayEnabled());
+        adapter.setDisplayPhotos(true);
+        return adapter;
+    }
+
+    @Override
+    public ContactEntryListController createController() {
+        return new MainContactListController(getContext(), getApplicationController());
+    }
+}
diff --git a/src/com/android/contacts/list/MainContactListController.java b/src/com/android/contacts/list/MainContactListController.java
new file mode 100644
index 0000000..7aae6b4
--- /dev/null
+++ b/src/com/android/contacts/list/MainContactListController.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 MainContactListController extends ContactEntryListController {
+
+    public MainContactListController(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/MultiplePhoneExtraAdapter.java b/src/com/android/contacts/list/MultiplePhoneExtraAdapter.java
new file mode 100644
index 0000000..09fc0db
--- /dev/null
+++ b/src/com/android/contacts/list/MultiplePhoneExtraAdapter.java
@@ -0,0 +1,183 @@
+/*
+ * 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.ContactListItemView;
+import com.android.contacts.MultiplePhonePickerActivity;
+import com.android.contacts.R;
+import com.android.contacts.ContactsListActivity.ContactListItemCache;
+
+import android.content.Context;
+import android.text.TextUtils;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.BaseAdapter;
+import android.widget.CheckBox;
+import android.widget.TextView;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * This class is the adapter for the phone numbers which may not be found in the contacts. It is
+ * called in ContactItemListAdapter in MODE_PICK_MULTIPLE_PHONES mode and shouldn't be a adapter
+ * for any View due to the missing implementation of getItem and getItemId.
+ */
+public class MultiplePhoneExtraAdapter extends BaseAdapter {
+
+    private final MultiplePhonePickerActivity mMultiplePhonePickerActivity;
+
+    public static final long INVALID_PHONE_ID = -1;
+
+    /** The initial phone numbers */
+    private List<String> mPhoneNumbers;
+
+    /** The phone numbers after the filtering */
+    private ArrayList<String> mFilteredPhoneNumbers = new ArrayList<String>();
+
+    private final Context mContext;
+
+    private final MultiplePhoneSelection mSelection;
+
+    public MultiplePhoneExtraAdapter(MultiplePhonePickerActivity multiplePhonePickerActivity,
+            Context context, MultiplePhoneSelection selection) {
+        mContext = context;
+        mMultiplePhonePickerActivity = multiplePhonePickerActivity;
+        mSelection = selection;
+    }
+
+    public void setPhoneNumbers(ArrayList<String> phoneNumbers) {
+        if (phoneNumbers != null) {
+            mFilteredPhoneNumbers.addAll(phoneNumbers);
+            mPhoneNumbers = phoneNumbers;
+        } else {
+            mPhoneNumbers = new ArrayList<String>();
+        }
+    }
+
+    public int getCount() {
+        int filteredCount = mFilteredPhoneNumbers.size();
+        if (filteredCount == 0) {
+            return 0;
+        }
+        // Count on the separator
+        return 1 + filteredCount;
+    }
+
+    public Object getItem(int position) {
+        // This method is not used currently.
+        throw new RuntimeException("This method is not implemented");
+    }
+
+    public long getItemId(int position) {
+        // This method is not used currently.
+        throw new RuntimeException("This method is not implemented");
+    }
+
+    /**
+     * @return the initial phone numbers, the zero length array is returned when there is no
+     * initial numbers.
+     */
+    public final List<String> getPhoneNumbers() {
+        return mPhoneNumbers;
+    }
+
+    /**
+     * @return the filtered phone numbers, the zero size ArrayList is returned when there is no
+     * initial numbers.
+     */
+    public ArrayList<String> getFilteredPhoneNumbers() {
+        return mFilteredPhoneNumbers;
+    }
+
+    public View getView(int position, View convertView, ViewGroup parent) {
+        int viewCount = getCount();
+        if (viewCount == 0) {
+            return null;
+        }
+
+        int startPos = (this.mMultiplePhonePickerActivity.mMode &
+                MultiplePhonePickerActivity.MODE_MASK_SHOW_NUMBER_OF_CONTACTS) != 0 ? 1 : 0;
+
+        // Separator
+        if (position == startPos) {
+            TextView view;
+            if (convertView != null && convertView instanceof TextView) {
+                view = (TextView) convertView;
+            } else {
+                LayoutInflater inflater =
+                    (LayoutInflater) mContext.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
+                view = (TextView) inflater.inflate(R.layout.list_separator, parent, false);
+            }
+            view.setText(R.string.unknown_contacts_separator);
+            return view;
+        }
+        // PhoneNumbers start from position of startPos + 1
+        if (position >= startPos + 1 && position < startPos + viewCount) {
+            View view;
+            if (convertView != null && convertView.getTag() != null &&
+                    convertView.getTag() instanceof ContactListItemCache) {
+                view = convertView;
+            } else {
+                view = this.mMultiplePhonePickerActivity.mAdapter.newView(mContext, null, parent);
+            }
+            bindView(view, mFilteredPhoneNumbers.get(position - 1 - startPos));
+            return view;
+        }
+        return null;
+    }
+
+    @Override
+    public int getItemViewType(int position) {
+        int startPos = (this.mMultiplePhonePickerActivity.mMode &
+                MultiplePhonePickerActivity.MODE_MASK_SHOW_NUMBER_OF_CONTACTS) != 0 ? 1 : 0;
+
+        return position == startPos ? IGNORE_ITEM_VIEW_TYPE : super.getItemViewType(position);
+    }
+
+    private void bindView(View view, final String label) {
+        ContactListItemView itemView = (ContactListItemView) view;
+        itemView.setDividerVisible(true);
+        itemView.setSectionHeader(null);
+        itemView.setLabel(null);
+        itemView.setData(null, 0);
+        itemView.removePhotoView();
+
+        final ContactListItemCache cache = (ContactListItemCache) view.getTag();
+        itemView.getNameTextView().setText(label);
+        CheckBox checkBox = itemView.getCheckBoxView();
+        checkBox.setChecked(mSelection.isSelected(label));
+        itemView.getChipView().setBackgroundResource(0);
+        cache.phoneId = INVALID_PHONE_ID;
+        cache.phoneNumber = label;
+        checkBox.setTag(cache);
+    }
+
+    public void doFilter(final String constraint, boolean selectedOnly) {
+        if (mPhoneNumbers == null) {
+            return;
+        }
+        mFilteredPhoneNumbers.clear();
+        for (String number : mPhoneNumbers) {
+            if (selectedOnly && !mSelection.isSelected(number) ||
+                    !TextUtils.isEmpty(constraint) && !number.startsWith(constraint)) {
+                continue;
+            }
+            mFilteredPhoneNumbers.add(number);
+        }
+    }
+}
\ No newline at end of file
diff --git a/src/com/android/contacts/list/MultiplePhonePickerAdapter.java b/src/com/android/contacts/list/MultiplePhonePickerAdapter.java
new file mode 100644
index 0000000..fd0feeb
--- /dev/null
+++ b/src/com/android/contacts/list/MultiplePhonePickerAdapter.java
@@ -0,0 +1,220 @@
+/*
+ * 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.ContactListItemView;
+import com.android.contacts.ContactsListActivity;
+import com.android.contacts.MultiplePhonePickerActivity;
+import com.android.contacts.ContactsListActivity.ContactListItemCache;
+
+import android.content.Context;
+import android.database.Cursor;
+import android.provider.ContactsContract.Contacts;
+import android.provider.ContactsContract.CommonDataKinds.Phone;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.CheckBox;
+import android.widget.ImageView;
+import android.widget.QuickContactBadge;
+import android.widget.TextView;
+
+public class MultiplePhonePickerAdapter extends ContactItemListAdapter {
+
+    private final MultiplePhonePickerActivity mMultiplePhonePickerActivity;
+    private MultiplePhoneExtraAdapter mExtraAdapter;
+
+    public MultiplePhonePickerAdapter(MultiplePhonePickerActivity multiplePhonePickerActivity) {
+        super(multiplePhonePickerActivity);
+        this.mMultiplePhonePickerActivity = multiplePhonePickerActivity;
+    }
+
+    public void setExtraAdapter(MultiplePhoneExtraAdapter extraAdapter) {
+        mExtraAdapter = extraAdapter;
+    }
+
+    @Override
+    public int getItemViewType(int position) {
+        if (position == 0 && mMultiplePhonePickerActivity.mShowNumberOfContacts) {
+            return IGNORE_ITEM_VIEW_TYPE;
+        }
+
+        if (position < mExtraAdapter.getCount()) {
+            return mExtraAdapter.getItemViewType(position);
+        }
+
+        return super.getItemViewType(position);
+    }
+
+    @Override
+    public View getView(int position, View convertView, ViewGroup parent) {
+        if (position == 0 && mMultiplePhonePickerActivity.mShowNumberOfContacts) {
+            return super.getView(position, convertView, parent);
+        }
+
+        if (position < mExtraAdapter.getCount()) {
+            return mExtraAdapter.getView(position,
+                    convertView, parent);
+        }
+        return super.getView(position, convertView, parent);
+    }
+
+    @Override
+    public View newView(Context context, Cursor cursor, ViewGroup parent) {
+        final ContactListItemView view = new ContactListItemView(context, null);
+        view.setOnCallButtonClickListener(mMultiplePhonePickerActivity);
+        view.setOnCheckBoxClickListener(mMultiplePhonePickerActivity.mCheckBoxClickerListener);
+        view.setTag(new MultiplePhonePickerActivity.ContactListItemCache());
+        return view;
+    }
+
+    @Override
+    public void bindView(View itemView, Context context, Cursor cursor) {
+        final ContactListItemView view = (ContactListItemView)itemView;
+        final ContactListItemCache cache = (ContactListItemCache)view.getTag();
+
+        int typeColumnIndex;
+        int dataColumnIndex;
+        int labelColumnIndex;
+        int defaultType;
+        int nameColumnIndex;
+        int phoneticNameColumnIndex;
+        int photoColumnIndex = ContactsListActivity.SUMMARY_PHOTO_ID_COLUMN_INDEX;
+        boolean displayAdditionalData = mDisplayAdditionalData;
+        boolean highlightingEnabled = false;
+        nameColumnIndex = ContactsListActivity.PHONE_DISPLAY_NAME_COLUMN_INDEX;
+        phoneticNameColumnIndex = -1;
+        dataColumnIndex = ContactsListActivity.PHONE_NUMBER_COLUMN_INDEX;
+        typeColumnIndex = ContactsListActivity.PHONE_TYPE_COLUMN_INDEX;
+        labelColumnIndex = ContactsListActivity.PHONE_LABEL_COLUMN_INDEX;
+        defaultType = Phone.TYPE_HOME;
+        photoColumnIndex = ContactsListActivity.PHONE_PHOTO_ID_COLUMN_INDEX;
+
+        cache.phoneId = Long.valueOf(cursor.getLong(ContactsListActivity.PHONE_ID_COLUMN_INDEX));
+        CheckBox checkBox = view.getCheckBoxView();
+        checkBox.setChecked(mMultiplePhonePickerActivity.mUserSelection
+                .isSelected(cache.phoneId));
+        checkBox.setTag(cache);
+        int color = mMultiplePhonePickerActivity.getChipColor(cursor
+                .getLong(ContactsListActivity.PHONE_CONTACT_ID_COLUMN_INDEX));
+        view.getChipView().setBackgroundResource(color);
+
+        // Set the name
+        cursor.copyStringToBuffer(nameColumnIndex, cache.nameBuffer);
+        TextView nameView = view.getNameTextView();
+        int size = cache.nameBuffer.sizeCopied;
+        if (size != 0) {
+            if (highlightingEnabled) {
+                if (cache.textWithHighlighting == null) {
+                    cache.textWithHighlighting =
+                            mMultiplePhonePickerActivity.mHighlightingAnimation
+                                    .createTextWithHighlighting();
+                }
+                buildDisplayNameWithHighlighting(nameView, cursor, cache.nameBuffer,
+                        cache.highlightedTextBuffer, cache.textWithHighlighting);
+            } else {
+                nameView.setText(cache.nameBuffer.data, 0, size);
+            }
+        } else {
+            nameView.setText(mUnknownNameText);
+        }
+
+        // Set the photo, if requested
+        if (mDisplayPhotos) {
+            boolean useQuickContact = false;
+
+            long photoId = 0;
+            if (!cursor.isNull(photoColumnIndex)) {
+                photoId = cursor.getLong(photoColumnIndex);
+            }
+
+            ImageView viewToUse;
+            if (useQuickContact) {
+                // Build soft lookup reference
+                final long contactId = cursor.getLong(ContactsListActivity.SUMMARY_ID_COLUMN_INDEX);
+                final String lookupKey = cursor
+                        .getString(ContactsListActivity.SUMMARY_LOOKUP_KEY_COLUMN_INDEX);
+                QuickContactBadge quickContact = view.getQuickContact();
+                quickContact.assignContactUri(Contacts.getLookupUri(contactId, lookupKey));
+                viewToUse = quickContact;
+            } else {
+                viewToUse = view.getPhotoView();
+            }
+
+            final int position = cursor.getPosition();
+            mMultiplePhonePickerActivity.mPhotoLoader.loadPhoto(viewToUse, photoId);
+        }
+
+        if (!displayAdditionalData) {
+            if (phoneticNameColumnIndex != -1) {
+
+                // Set the name
+                cursor.copyStringToBuffer(phoneticNameColumnIndex, cache.phoneticNameBuffer);
+                int phoneticNameSize = cache.phoneticNameBuffer.sizeCopied;
+                if (phoneticNameSize != 0) {
+                    view.setLabel(cache.phoneticNameBuffer.data, phoneticNameSize);
+                } else {
+                    view.setLabel(null);
+                }
+            } else {
+                view.setLabel(null);
+            }
+            return;
+        }
+
+        // Set the data.
+        cursor.copyStringToBuffer(dataColumnIndex, cache.dataBuffer);
+
+        size = cache.dataBuffer.sizeCopied;
+        view.setData(cache.dataBuffer.data, size);
+
+        // Set the label.
+        if (!cursor.isNull(typeColumnIndex)) {
+            final int type = cursor.getInt(typeColumnIndex);
+            final String label = cursor.getString(labelColumnIndex);
+            view.setLabel(Phone.getTypeLabel(context.getResources(), type, label));
+        } else {
+            view.setLabel(null);
+        }
+    }
+
+    @Override
+    public void changeCursor(Cursor cursor) {
+        super.changeCursor(cursor);
+        mMultiplePhonePickerActivity.updateChipColor(cursor);
+    }
+
+    @Override
+    protected void prepareEmptyView() {
+        mMultiplePhonePickerActivity.mEmptyView.show(mMultiplePhonePickerActivity.mSearchMode,
+                true, false, false, false, true, mMultiplePhonePickerActivity.mShowSelectedOnly);
+    }
+
+    @Override
+    public int getCount() {
+        if (!mDataValid) {
+            return 0;
+        }
+
+        int count = super.getCount();
+        count += mExtraAdapter.getCount();
+        return count;
+    }
+
+    @Override
+    protected int getRealPosition(int pos) {
+        return pos - mExtraAdapter.getCount();
+    }
+}
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());
+    }
+}
diff --git a/src/com/android/contacts/list/MultiplePhoneSelection.java b/src/com/android/contacts/list/MultiplePhoneSelection.java
new file mode 100644
index 0000000..e9249e4
--- /dev/null
+++ b/src/com/android/contacts/list/MultiplePhoneSelection.java
@@ -0,0 +1,230 @@
+/*
+ * 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.MultiplePhonePickerActivity;
+
+import android.content.ContentUris;
+import android.content.Intent;
+import android.database.Cursor;
+import android.net.Uri;
+import android.os.Bundle;
+import android.provider.ContactsContract.Intents;
+import android.provider.ContactsContract.CommonDataKinds.Phone;
+import android.text.TextUtils;
+
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.List;
+
+/**
+ * This class is used to keep the user's selection in MODE_PICK_MULTIPLE_PHONES mode.
+ */
+public class MultiplePhoneSelection {
+
+    private final MultiplePhonePickerActivity mMultiplePhoneSelectionActivity;
+
+    private static final String TEL_SCHEME = "tel";
+
+    public static final String EXTRA_SELECTION =
+        "com.android.contacts.MultiplePhoneSelectionActivity.UserSelection.extra.SELECTION";
+    private static final String SELECTED_UNKNOWN_PHONES_KEY = "selected_unknown_phones";
+    private static final String SELECTED_PHONE_IDS_KEY = "selected_phone_id";
+
+    /** The PHONE_ID of selected number in user contacts*/
+    private HashSet<Long> mSelectedPhoneIds = new HashSet<Long>();
+
+    /** The selected phone numbers in the PhoneNumberAdapter */
+    private HashSet<String> mSelectedPhoneNumbers = new HashSet<String>();
+
+    public MultiplePhoneSelection(MultiplePhonePickerActivity multiplePhonePickerActivity) {
+        this.mMultiplePhoneSelectionActivity = multiplePhonePickerActivity;
+    }
+
+    public void saveInstanceState(Bundle icicle) {
+        int selectedUnknownsCount = mSelectedPhoneNumbers.size();
+        if (selectedUnknownsCount > 0) {
+            String[] selectedUnknows = new String[selectedUnknownsCount];
+            icicle.putStringArray(SELECTED_UNKNOWN_PHONES_KEY,
+                    mSelectedPhoneNumbers.toArray(selectedUnknows));
+        }
+        int selectedKnownsCount = mSelectedPhoneIds.size();
+        if (selectedKnownsCount > 0) {
+            long[] selectedPhoneIds = new long [selectedKnownsCount];
+            int index = 0;
+            for (Long phoneId : mSelectedPhoneIds) {
+                selectedPhoneIds[index++] = phoneId.longValue();
+            }
+            icicle.putLongArray(SELECTED_PHONE_IDS_KEY, selectedPhoneIds);
+
+        }
+    }
+
+    public void restoreInstanceState(Bundle icicle) {
+        if (icicle != null) {
+            setSelection(icicle.getStringArray(SELECTED_UNKNOWN_PHONES_KEY),
+                    icicle.getLongArray(SELECTED_PHONE_IDS_KEY));
+        }
+    }
+
+    public void setSelection(final String[] selecedUnknownNumbers, final long[] selectedPhoneIds) {
+        if (selecedUnknownNumbers != null) {
+            for (String number : selecedUnknownNumbers) {
+                setPhoneSelected(number, true);
+            }
+        }
+        if (selectedPhoneIds != null) {
+            for (long id : selectedPhoneIds) {
+                setPhoneSelected(id, true);
+            }
+        }
+    }
+
+    public void setSelection(final List<String> selecedUnknownNumbers,
+            final List<Long> selectedPhoneIds) {
+        if (selecedUnknownNumbers != null) {
+            setPhoneNumbersSelected(selecedUnknownNumbers, true);
+        }
+        if (selectedPhoneIds != null) {
+            setPhoneIdsSelected(selectedPhoneIds, true);
+        }
+    }
+
+    private void setPhoneNumbersSelected(final List<String> phoneNumbers, boolean selected) {
+        if (selected) {
+            mSelectedPhoneNumbers.addAll(phoneNumbers);
+        } else {
+            mSelectedPhoneNumbers.removeAll(phoneNumbers);
+        }
+    }
+
+    private void setPhoneIdsSelected(final List<Long> phoneIds, boolean selected) {
+        if (selected) {
+            mSelectedPhoneIds.addAll(phoneIds);
+        } else {
+            mSelectedPhoneIds.removeAll(phoneIds);
+        }
+    }
+
+    public void setPhoneSelected(final String phoneNumber, boolean selected) {
+        if (!TextUtils.isEmpty(phoneNumber)) {
+            if (selected) {
+                mSelectedPhoneNumbers.add(phoneNumber);
+            } else {
+                mSelectedPhoneNumbers.remove(phoneNumber);
+            }
+        }
+    }
+
+    public void setPhoneSelected(long phoneId, boolean selected) {
+        if (selected) {
+            mSelectedPhoneIds.add(phoneId);
+        } else {
+            mSelectedPhoneIds.remove(phoneId);
+        }
+    }
+
+    public boolean isSelected(long phoneId) {
+        return mSelectedPhoneIds.contains(phoneId);
+    }
+
+    public boolean isSelected(final String phoneNumber) {
+        return mSelectedPhoneNumbers.contains(phoneNumber);
+    }
+
+    public void setAllPhonesSelected(boolean selected) {
+        if (selected) {
+            Cursor cursor = this.mMultiplePhoneSelectionActivity.mAdapter.getCursor();
+            if (cursor != null) {
+                int backupPos = cursor.getPosition();
+                cursor.moveToPosition(-1);
+                while (cursor.moveToNext()) {
+                    setPhoneSelected(cursor
+                            .getLong(MultiplePhonePickerActivity.PHONE_ID_COLUMN_INDEX), true);
+                }
+                cursor.moveToPosition(backupPos);
+            }
+            for (String number : this.mMultiplePhoneSelectionActivity.mPhoneNumberAdapter
+                    .getFilteredPhoneNumbers()) {
+                setPhoneSelected(number, true);
+            }
+        } else {
+            mSelectedPhoneIds.clear();
+            mSelectedPhoneNumbers.clear();
+        }
+    }
+
+    public boolean isAllSelected() {
+        return selectedCount() == this.mMultiplePhoneSelectionActivity.mPhoneNumberAdapter
+                .getFilteredPhoneNumbers().size()
+                + this.mMultiplePhoneSelectionActivity.mAdapter.getCount();
+    }
+
+    public int selectedCount() {
+        return mSelectedPhoneNumbers.size() + mSelectedPhoneIds.size();
+    }
+
+    public Iterator<Long> getSelectedPhonIds() {
+        return mSelectedPhoneIds.iterator();
+    }
+
+    private int fillSelectedNumbers(Uri[] uris, int from) {
+        int count = mSelectedPhoneNumbers.size();
+        if (count == 0)
+            return from;
+        // Below loop keeps phone numbers by initial order.
+        List<String> phoneNumbers = this.mMultiplePhoneSelectionActivity.mPhoneNumberAdapter
+                .getPhoneNumbers();
+        for (String phoneNumber : phoneNumbers) {
+            if (isSelected(phoneNumber)) {
+                Uri.Builder ub = new Uri.Builder();
+                ub.scheme(TEL_SCHEME);
+                ub.encodedOpaquePart(phoneNumber);
+                uris[from++] = ub.build();
+            }
+        }
+        return from;
+    }
+
+    private int fillSelectedPhoneIds(Uri[] uris, int from) {
+        int count = mSelectedPhoneIds.size();
+        if (count == 0)
+            return from;
+        Iterator<Long> it = mSelectedPhoneIds.iterator();
+        while (it.hasNext()) {
+            uris[from++] = ContentUris.withAppendedId(Phone.CONTENT_URI, it.next());
+        }
+        return from;
+    }
+
+    private Uri[] getSelected() {
+        Uri[] uris = new Uri[mSelectedPhoneNumbers.size() + mSelectedPhoneIds.size()];
+        int from  = fillSelectedNumbers(uris, 0);
+        fillSelectedPhoneIds(uris, from);
+        return uris;
+    }
+
+    public Intent createSelectionIntent() {
+        Intent intent = new Intent();
+        intent.putExtra(Intents.EXTRA_PHONE_URIS, getSelected());
+
+        return intent;
+    }
+
+    public void fillSelectionForSearchMode(Bundle bundle) {
+        bundle.putParcelableArray(EXTRA_SELECTION, getSelected());
+    }
+}
\ No newline at end of file
diff --git a/src/com/android/contacts/list/PinnedHeaderListAdapter.java b/src/com/android/contacts/list/PinnedHeaderListAdapter.java
new file mode 100644
index 0000000..27deef3
--- /dev/null
+++ b/src/com/android/contacts/list/PinnedHeaderListAdapter.java
@@ -0,0 +1,167 @@
+/*
+ * 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.R;
+import com.android.contacts.widget.PinnedHeaderListView;
+
+import android.content.Context;
+import android.content.res.ColorStateList;
+import android.graphics.Color;
+import android.graphics.drawable.Drawable;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.CursorAdapter;
+import android.widget.SectionIndexer;
+import android.widget.TextView;
+
+/**
+ * A list adapter that supports section indexer and a pinned header.
+ */
+public abstract class PinnedHeaderListAdapter extends CursorAdapter
+        implements SectionIndexer, PinnedHeaderListView.PinnedHeaderAdapter {
+
+    /**
+     * An approximation of the background color of the pinned header. This color
+     * is used when the pinned header is being pushed up.  At that point the header
+     * "fades away".  Rather than computing a faded bitmap based on the 9-patch
+     * normally used for the background, we will use a solid color, which will
+     * provide better performance and reduced complexity.
+     */
+    private int mPinnedHeaderBackgroundColor;
+
+    private SectionIndexer mIndexer;
+
+    public PinnedHeaderListAdapter(Context context) {
+        super(context, null, false);
+        this.mContext = context;
+        mPinnedHeaderBackgroundColor =
+                context.getResources().getColor(R.color.pinned_header_background);
+    }
+
+    public void setIndexer(SectionIndexer indexer) {
+        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[] { " " };
+        } else {
+            return mIndexer.getSections();
+        }
+    }
+
+    public int getPositionForSection(int sectionIndex) {
+        if (mIndexer == null) {
+            return -1;
+        }
+
+        return mIndexer.getPositionForSection(sectionIndex);
+    }
+
+    public int getSectionForPosition(int position) {
+        if (mIndexer == null) {
+            return -1;
+        }
+
+        return mIndexer.getSectionForPosition(position);
+    }
+
+    /**
+     * Computes the state of the pinned header.  It can be invisible, fully
+     * visible or partially pushed up out of the view.
+     */
+    public int getPinnedHeaderState(int position) {
+        if (mIndexer == null || mCursor == null || mCursor.getCount() == 0) {
+            return PINNED_HEADER_GONE;
+        }
+
+        int realPosition = getCursorPosition(position);
+        if (realPosition < 0) {
+            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) {
+            return PINNED_HEADER_PUSHED_UP;
+        }
+
+        return PINNED_HEADER_VISIBLE;
+    }
+
+    final static class PinnedHeaderCache {
+        public TextView titleView;
+        public ColorStateList textColor;
+        public Drawable background;
+    }
+
+    /**
+     * Configures the pinned header by setting the appropriate text label
+     * and also adjusting color if necessary.  The color needs to be
+     * adjusted when the pinned header is being pushed up from the view.
+     */
+    public void configurePinnedHeader(View header, int position, int alpha) {
+        PinnedHeaderCache cache = (PinnedHeaderCache)header.getTag();
+        if (cache == null) {
+            cache = new PinnedHeaderCache();
+            cache.titleView = (TextView)header.findViewById(R.id.header_text);
+            cache.textColor = cache.titleView.getTextColors();
+            cache.background = header.getBackground();
+            header.setTag(cache);
+        }
+
+        int realPosition = getCursorPosition(position);
+        int section = getSectionForPosition(realPosition);
+
+        String title = (String)mIndexer.getSections()[section];
+        cache.titleView.setText(title);
+
+        if (alpha == 255) {
+            // Opaque: use the default background, and the original text color
+            header.setBackgroundDrawable(cache.background);
+            cache.titleView.setTextColor(cache.textColor);
+        } else {
+            // Faded: use a solid color approximation of the background, and
+            // a translucent text color
+            header.setBackgroundColor(Color.rgb(
+                    Color.red(mPinnedHeaderBackgroundColor) * alpha / 255,
+                    Color.green(mPinnedHeaderBackgroundColor) * alpha / 255,
+                    Color.blue(mPinnedHeaderBackgroundColor) * alpha / 255));
+
+            int textColor = cache.textColor.getDefaultColor();
+            cache.titleView.setTextColor(Color.argb(alpha,
+                    Color.red(textColor), Color.green(textColor), Color.blue(textColor)));
+        }
+    }
+
+    public View createPinnedHeaderView(ViewGroup parent) {
+        return LayoutInflater.from(mContext).inflate(R.layout.list_section, parent, false);
+    }
+}
diff --git a/src/com/android/contacts/mvcframework/CursorLoader.java b/src/com/android/contacts/mvcframework/CursorLoader.java
deleted file mode 100644
index 25585be..0000000
--- a/src/com/android/contacts/mvcframework/CursorLoader.java
+++ /dev/null
@@ -1,105 +0,0 @@
-/*
- * Copyright (C) 2010 Google Inc.
- *
- * 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.mvcframework;
-
-import android.content.Context;
-import android.database.Cursor;
-import android.os.AsyncTask;
-
-public abstract class CursorLoader extends Loader<Cursor> {
-    Cursor mCursor;
-    ForceLoadContentObserver mObserver;
-    boolean mClosed;
-
-    final class LoadListTask extends AsyncTask<Void, Void, Cursor> {
-        /* Runs on a worker thread */
-        @Override
-        protected Cursor doInBackground(Void... params) {
-            Cursor cursor = doQueryInBackground();
-            // Ensure the data is loaded
-            if (cursor != null) {
-                cursor.getCount();
-                cursor.registerContentObserver(mObserver);
-            }
-            return cursor;
-        }
-
-        /* Runs on the UI thread */
-        @Override
-        protected void onPostExecute(Cursor cursor) {
-            if (mClosed) {
-                // An async query came in after the call to close()
-                cursor.close();
-                return;
-            }
-            mCursor = cursor;
-            deliverResult(cursor);
-        }
-    }
-
-    public CursorLoader(Context context) {
-        super(context);
-        mObserver = new ForceLoadContentObserver();
-    }
-
-    /**
-     * Starts an asynchronous load of the contacts list data. When the result is ready the callbacks
-     * will be called on the UI thread. If a previous load has been completed and is still valid
-     * the result may be passed to the callbacks immediately.
-     *
-     * Must be called from the UI thread
-     */
-    @Override
-    public void startLoading() {
-        if (mCursor != null) {
-            deliverResult(mCursor);
-        } else {
-            forceLoad();
-        }
-    }
-
-    /**
-     * Force an asynchronous load. Unlike {@link #startLoading()} this will ignore a previously
-     * loaded data set and load a new one.
-     */
-    @Override
-    public void forceLoad() {
-        new LoadListTask().execute((Void[]) null);
-    }
-
-    /**
-     * Must be called from the UI thread
-     */
-    @Override
-    public void stopLoading() {
-        if (mCursor != null && !mCursor.isClosed()) {
-            mCursor.close();
-            mCursor = null;
-        }
-    }
-
-    @Override
-    public void destroy() {
-        // Close up the cursor
-        stopLoading();
-        // Make sure that any outstanding loads clean themselves up properly
-        mClosed = true;
-    }
-
-    /** Called from a worker thread to execute the desired query */
-    protected abstract Cursor doQueryInBackground();
-}
diff --git a/src/com/android/contacts/ui/EditContactActivity.java b/src/com/android/contacts/ui/EditContactActivity.java
index 993417d..ffb1b00 100644
--- a/src/com/android/contacts/ui/EditContactActivity.java
+++ b/src/com/android/contacts/ui/EditContactActivity.java
@@ -30,9 +30,9 @@
 import com.android.contacts.model.ContactsSource.EditType;
 import com.android.contacts.model.Editor.EditorListener;
 import com.android.contacts.model.EntityDelta.ValuesDelta;
-import com.android.contacts.mvcframework.DialogManager;
 import com.android.contacts.ui.widget.BaseContactEditorView;
 import com.android.contacts.ui.widget.PhotoEditorView;
+import com.android.contacts.util.DialogManager;
 import com.android.contacts.util.EmptyService;
 import com.android.contacts.util.WeakAsyncTask;
 
diff --git a/src/com/android/contacts/ui/widget/GenericEditorView.java b/src/com/android/contacts/ui/widget/GenericEditorView.java
index 30ef8c1..7901f60 100644
--- a/src/com/android/contacts/ui/widget/GenericEditorView.java
+++ b/src/com/android/contacts/ui/widget/GenericEditorView.java
@@ -25,9 +25,9 @@
 import com.android.contacts.model.ContactsSource.EditField;
 import com.android.contacts.model.ContactsSource.EditType;
 import com.android.contacts.model.EntityDelta.ValuesDelta;
-import com.android.contacts.mvcframework.DialogManager;
-import com.android.contacts.mvcframework.DialogManager.DialogShowingView;
 import com.android.contacts.ui.ViewIdGenerator;
+import com.android.contacts.util.DialogManager;
+import com.android.contacts.util.DialogManager.DialogShowingView;
 
 import android.app.AlertDialog;
 import android.app.Dialog;
diff --git a/src/com/android/contacts/mvcframework/DialogManager.java b/src/com/android/contacts/util/DialogManager.java
similarity index 98%
rename from src/com/android/contacts/mvcframework/DialogManager.java
rename to src/com/android/contacts/util/DialogManager.java
index 420c4b2..4c6baf3 100644
--- a/src/com/android/contacts/mvcframework/DialogManager.java
+++ b/src/com/android/contacts/util/DialogManager.java
@@ -14,7 +14,7 @@
  * limitations under the License
  */
 
-package com.android.contacts.mvcframework;
+package com.android.contacts.util;
 
 import android.app.Activity;
 import android.app.Dialog;
diff --git a/src/com/android/contacts/views/detail/ContactLoader.java b/src/com/android/contacts/views/detail/ContactLoader.java
index 103136a..bc885df 100644
--- a/src/com/android/contacts/views/detail/ContactLoader.java
+++ b/src/com/android/contacts/views/detail/ContactLoader.java
@@ -16,9 +16,9 @@
 
 package com.android.contacts.views.detail;
 
-import com.android.contacts.mvcframework.Loader;
 import com.android.contacts.util.DataStatus;
 
+import android.app.patterns.Loader;
 import android.content.ContentResolver;
 import android.content.ContentUris;
 import android.content.Context;
@@ -44,6 +44,7 @@
  * Loads a single Contact and all it constituent RawContacts.
  */
 public class ContactLoader extends Loader<ContactLoader.Result> {
+    private boolean mIsSynchronous;
     private Uri mLookupUri;
     private Result mContact;
     private ForceLoadContentObserver mObserver;
@@ -68,10 +69,19 @@
         private final String mLookupKey;
         private final Uri mUri;
         private final long mId;
-        private final ArrayList<Entity> mEntities;
-        private final HashMap<Long, DataStatus> mStatuses;
         private final long mNameRawContactId;
         private final int mDisplayNameSource;
+        private final long mPhotoId;
+        private final String mDisplayName;
+        private final String mPhoneticName;
+        private final boolean mStarred;
+        private final Integer mPresence;
+        private final ArrayList<Entity> mEntities;
+        private final HashMap<Long, DataStatus> mStatuses;
+        private final String mStatus;
+        private final Long mStatusTimestamp;
+        private final Integer mStatusLabel;
+        private final String mStatusResPackage;
 
         /**
          * Constructor for case "no contact found". This must only be used for the
@@ -86,13 +96,24 @@
             mStatuses = null;
             mNameRawContactId = -1;
             mDisplayNameSource = DisplayNameSources.UNDEFINED;
+            mPhotoId = -1;
+            mDisplayName = null;
+            mPhoneticName = null;
+            mStarred = false;
+            mPresence = null;
+            mStatus = null;
+            mStatusTimestamp = null;
+            mStatusLabel = null;
+            mStatusResPackage = null;
         }
 
         /**
          * Constructor to call when contact was found
          */
         private Result(Uri lookupUri, String lookupKey, Uri uri, long id, long nameRawContactId,
-                int displayNameSource) {
+                int displayNameSource, long photoId, String displayName, String phoneticName,
+                boolean starred, Integer presence, String status, Long statusTimestamp,
+                Integer statusLabel, String statusResPackage) {
             mLookupUri = lookupUri;
             mLookupKey = lookupKey;
             mUri = uri;
@@ -101,6 +122,15 @@
             mStatuses = new HashMap<Long, DataStatus>();
             mNameRawContactId = nameRawContactId;
             mDisplayNameSource = displayNameSource;
+            mPhotoId = photoId;
+            mDisplayName = displayName;
+            mPhoneticName = phoneticName;
+            mStarred = starred;
+            mPresence = presence;
+            mStatus = status;
+            mStatusTimestamp = statusTimestamp;
+            mStatusLabel = statusLabel;
+            mStatusResPackage = statusResPackage;
         }
 
         public Uri getLookupUri() {
@@ -115,21 +145,48 @@
         public long getId() {
             return mId;
         }
-        public ArrayList<Entity> getEntities() {
-            return mEntities;
-        }
-        public HashMap<Long, DataStatus> getStatuses() {
-            return mStatuses;
-        }
         public long getNameRawContactId() {
             return mNameRawContactId;
         }
         public int getDisplayNameSource() {
             return mDisplayNameSource;
         }
+        public long getPhotoId() {
+            return mPhotoId;
+        }
+        public String getDisplayName() {
+            return mDisplayName;
+        }
+        public String getPhoneticName() {
+            return mPhoneticName;
+        }
+        public boolean getStarred() {
+            return mStarred;
+        }
+        public Integer getPresence() {
+            return mPresence;
+        }
+        public String getStatus() {
+            return mStatus;
+        }
+        public Long getStatusTimestamp() {
+            return mStatusTimestamp;
+        }
+        public Integer getStatusLabel() {
+            return mStatusLabel;
+        }
+        public String getStatusResPackage() {
+            return mStatusResPackage;
+        }
+        public ArrayList<Entity> getEntities() {
+            return mEntities;
+        }
+        public HashMap<Long, DataStatus> getStatuses() {
+            return mStatuses;
+        }
     }
 
-    interface StatusQuery {
+    private interface StatusQuery {
         final String[] PROJECTION = new String[] {
                 Data._ID, Data.STATUS, Data.STATUS_RES_PACKAGE, Data.STATUS_ICON,
                 Data.STATUS_LABEL, Data.STATUS_TIMESTAMP, Data.PRESENCE,
@@ -138,15 +195,38 @@
         final int _ID = 0;
     }
 
-    public final class LoadContactTask extends AsyncTask<Void, Void, Result> {
+    private interface ContactQuery {
+        //Projection used for the summary info in the header.
+        final static String[] COLUMNS = new String[] {
+                Contacts.NAME_RAW_CONTACT_ID,
+                Contacts.DISPLAY_NAME_SOURCE,
+                Contacts.LOOKUP_KEY,
+                Contacts.DISPLAY_NAME,
+                Contacts.PHONETIC_NAME,
+                Contacts.PHOTO_ID,
+                Contacts.STARRED,
+                Contacts.CONTACT_PRESENCE,
+                Contacts.CONTACT_STATUS,
+                Contacts.CONTACT_STATUS_TIMESTAMP,
+                Contacts.CONTACT_STATUS_RES_PACKAGE,
+                Contacts.CONTACT_STATUS_LABEL,
+        };
+        final static int NAME_RAW_CONTACT_ID = 0;
+        final static int DISPLAY_NAME_SOURCE = 1;
+        final static int LOOKUP_KEY = 2;
+        final static int DISPLAY_NAME = 3;
+        final static int PHONETIC_NAME = 4;
+        final static int PHOTO_ID = 5;
+        final static int STARRED = 6;
+        final static int CONTACT_PRESENCE = 7;
+        final static int CONTACT_STATUS = 8;
+        final static int CONTACT_STATUS_TIMESTAMP = 9;
+        final static int CONTACT_STATUS_RES_PACKAGE = 10;
+        final static int CONTACT_STATUS_LABEL = 11;
+    }
 
-        /**
-         * Used for synchronuous calls in unit test
-         * @hide
-         */
-        public Result testExecute() {
-            return doInBackground();
-        }
+
+    public final class LoadContactTask extends AsyncTask<Void, Void, Result> {
 
         @Override
         protected Result doInBackground(Void... args) {
@@ -203,8 +283,7 @@
          * Tries to lookup a contact using both Id and lookup key of the given Uri. Returns a
          * valid Result instance if successful or {@link Result#NOT_FOUND} if empty
          */
-        private Result loadContactHeaderData(final ContentResolver resolver,
-                final Uri lookupUri) {
+        private Result loadContactHeaderData(final ContentResolver resolver, final Uri lookupUri) {
             if (resolver == null) throw new IllegalArgumentException("resolver must not be null");
             if (lookupUri == null) {
                 // This can happen if the row was removed
@@ -222,14 +301,9 @@
             final long uriContactId = Long.parseLong(segments.get(3));
             final String uriLookupKey = Uri.encode(segments.get(2));
             final Uri contactUri = ContentUris.withAppendedId(Contacts.CONTENT_URI, uriContactId);
-            final Uri dataUri = Uri.withAppendedPath(contactUri, Contacts.Data.CONTENT_DIRECTORY);
 
-            final Cursor cursor = resolver.query(dataUri,
-                    new String[] {
-                        Contacts.NAME_RAW_CONTACT_ID,
-                        Contacts.DISPLAY_NAME_SOURCE,
-                        Contacts.LOOKUP_KEY
-                    }, null, null, null);
+            final Cursor cursor = resolver.query(contactUri, ContactQuery.COLUMNS, null, null,
+                    null);
             if (cursor == null) {
                 Log.e(TAG, "No cursor returned in trySetupContactHeader/query");
                 return null;
@@ -240,8 +314,7 @@
                             "ContactId must have changed or item has been removed");
                     return Result.NOT_FOUND;
                 }
-                String lookupKey =
-                        cursor.getString(cursor.getColumnIndex(Contacts.LOOKUP_KEY));
+                final String lookupKey = cursor.getString(ContactQuery.LOOKUP_KEY);
                 if (!lookupKey.equals(uriLookupKey)) {
                     // ID and lookup key do not match
                     Log.w(TAG, "Contact with Id=" + uriContactId + " has a wrong lookupKey ("
@@ -249,13 +322,28 @@
                     return Result.NOT_FOUND;
                 }
 
-                long nameRawContactId = cursor.getLong(cursor.getColumnIndex(
-                        Contacts.NAME_RAW_CONTACT_ID));
-                int displayNameSource = cursor.getInt(cursor.getColumnIndex(
-                        Contacts.DISPLAY_NAME_SOURCE));
+                final long nameRawContactId = cursor.getLong(ContactQuery.NAME_RAW_CONTACT_ID);
+                final int displayNameSource = cursor.getInt(ContactQuery.DISPLAY_NAME_SOURCE);
+                final String displayName = cursor.getString(ContactQuery.DISPLAY_NAME);
+                final String phoneticName = cursor.getString(ContactQuery.PHONETIC_NAME);
+                final long photoId = cursor.getLong(ContactQuery.PHOTO_ID);
+                final boolean starred = cursor.getInt(ContactQuery.STARRED) != 0;
+                final Integer presence = cursor.isNull(ContactQuery.CONTACT_PRESENCE)
+                        ? null
+                        : cursor.getInt(ContactQuery.CONTACT_PRESENCE);
+                final String status = cursor.getString(ContactQuery.CONTACT_STATUS);
+                final Long statusTimestamp = cursor.isNull(ContactQuery.CONTACT_STATUS_TIMESTAMP)
+                        ? null
+                        : cursor.getLong(ContactQuery.CONTACT_STATUS_TIMESTAMP);
+                final Integer statusLabel = cursor.isNull(ContactQuery.CONTACT_STATUS_LABEL)
+                        ? null
+                        : cursor.getInt(ContactQuery.CONTACT_STATUS_LABEL);
+                final String statusResPackage = cursor.getString(
+                        ContactQuery.CONTACT_STATUS_RES_PACKAGE);
 
                 return new Result(lookupUri, lookupKey, contactUri, uriContactId, nameRawContactId,
-                        displayNameSource);
+                        displayNameSource, photoId, displayName, phoneticName, starred, presence,
+                        status, statusTimestamp, statusLabel, statusResPackage);
             } finally {
                 cursor.close();
             }
@@ -356,7 +444,7 @@
     }
 
     public ContactLoader(Context context, Uri lookupUri) {
-            super(context);
+        super(context);
         mLookupUri = lookupUri;
     }
 
@@ -371,7 +459,12 @@
 
     @Override
     public void forceLoad() {
-        new LoadContactTask().execute((Void[])null);
+        LoadContactTask task = new LoadContactTask();
+        if (mIsSynchronous) {
+            task.onPostExecute(task.doInBackground((Void[])null));
+            return;
+        }
+        task.execute((Void[])null);
     }
 
     @Override
@@ -387,4 +480,9 @@
         mContact = null;
         mDestroyed = true;
     }
+
+
+    public void setSynchronous(boolean value) {
+        mIsSynchronous = value;
+    }
 }
diff --git a/src/com/android/contacts/views/detail/ContactDetailView.java b/src/com/android/contacts/views/detail/ContactPresenter.java
similarity index 95%
rename from src/com/android/contacts/views/detail/ContactDetailView.java
rename to src/com/android/contacts/views/detail/ContactPresenter.java
index 424c63a..d2dfda1 100644
--- a/src/com/android/contacts/views/detail/ContactDetailView.java
+++ b/src/com/android/contacts/views/detail/ContactPresenter.java
@@ -27,7 +27,7 @@
 import com.android.contacts.model.ContactsSource;
 import com.android.contacts.model.Sources;
 import com.android.contacts.model.ContactsSource.DataKind;
-import com.android.contacts.mvcframework.DialogManager;
+import com.android.contacts.util.DialogManager;
 import com.android.contacts.util.Constants;
 import com.android.contacts.util.DataStatus;
 import com.android.contacts.views.detail.ContactLoader.Result;
@@ -68,7 +68,6 @@
 import android.provider.ContactsContract.CommonDataKinds.Website;
 import android.telephony.PhoneNumberUtils;
 import android.text.TextUtils;
-import android.util.AttributeSet;
 import android.util.Log;
 import android.view.ContextMenu;
 import android.view.KeyEvent;
@@ -79,10 +78,10 @@
 import android.view.View;
 import android.view.ViewGroup;
 import android.view.ContextMenu.ContextMenuInfo;
+import android.view.View.OnClickListener;
 import android.view.View.OnCreateContextMenuListener;
 import android.widget.AdapterView;
 import android.widget.ImageView;
-import android.widget.LinearLayout;
 import android.widget.ListView;
 import android.widget.TextView;
 import android.widget.Toast;
@@ -90,7 +89,7 @@
 
 import java.util.ArrayList;
 
-public class ContactDetailView extends LinearLayout implements OnCreateContextMenuListener,
+public class ContactPresenter implements OnCreateContextMenuListener,
         OnItemClickListener, DialogManager.DialogShowingView {
     private static final String TAG = "ContactDetailsView";
     private static final boolean SHOW_SEPARATORS = false;
@@ -103,8 +102,11 @@
 
     private static final int MENU_ITEM_MAKE_DEFAULT = 3;
 
+    private final Context mContext;
+    private final View mView;
+
     private Result mContactData;
-    private Callbacks mCallbacks;
+    private Controller mCallbacks;
     private LayoutInflater mInflater;
     private ContactHeaderWidget mContactHeaderWidget;
     private ListView mListView;
@@ -141,22 +143,57 @@
     private ArrayList<ViewEntry> mOtherEntries = new ArrayList<ViewEntry>();
     private ArrayList<ArrayList<ViewEntry>> mSections = new ArrayList<ArrayList<ViewEntry>>();
 
-    public ContactDetailView(Context context) {
-        super(context);
-    }
+    public ContactPresenter(Context context, View view) {
+        mContext = context;
+        mView = view;
 
-    public ContactDetailView(Context context, AttributeSet attrs) {
-        super(context, attrs);
+        mInflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
+        mContactHeaderWidget = (ContactHeaderWidget) view.findViewById(R.id.contact_header_widget);
+        mContactHeaderWidget.showStar(true);
+        mContactHeaderWidget.setExcludeMimes(new String[] {
+            Contacts.CONTENT_ITEM_TYPE
+        });
+
+        mListView = (ListView) view.findViewById(android.R.id.list);
+        mListView.setOnCreateContextMenuListener(this);
+        mListView.setScrollBarStyle(ListView.SCROLLBARS_OUTSIDE_OVERLAY);
+        mListView.setOnItemClickListener(this);
+        // Don't set it to mListView yet.  We do so later when we bind the adapter.
+        mEmptyView = view.findViewById(android.R.id.empty);
+
+        // Build the list of sections. The order they're added to mSections dictates the
+        // order they are displayed in the list.
+        mSections.add(mPhoneEntries);
+        mSections.add(mSmsEntries);
+        mSections.add(mEmailEntries);
+        mSections.add(mImEntries);
+        mSections.add(mPostalEntries);
+        mSections.add(mNicknameEntries);
+        mSections.add(mOrganizationEntries);
+        mSections.add(mGroupEntries);
+        mSections.add(mOtherEntries);
+
+        //TODO Read this value from a preference
+        mShowSmsLinksForAllPhones = true;
     }
 
     public void setData(Result data) {
         mContactData = data;
 
-        mContactHeaderWidget.bindFromContactLookupUri(data.getUri());
         bindData();
     }
 
     private void bindData() {
+        // Set the header
+        mContactHeaderWidget.setContactUri(mContactData.getLookupUri());
+        mContactHeaderWidget.setDisplayName(mContactData.getDisplayName(),
+                mContactData.getPhoneticName());
+        mContactHeaderWidget.setPhotoId(mContactData.getPhotoId(), mContactData.getLookupUri());
+        mContactHeaderWidget.setStared(mContactData.getStarred());
+        mContactHeaderWidget.setPresence(mContactData.getPresence());
+        mContactHeaderWidget.setStatus(
+                mContactData.getStatus(), mContactData.getStatusTimestamp(),
+                mContactData.getStatusLabel(), mContactData.getStatusResPackage());
 
         // Build up the contact entries
         buildEntries();
@@ -375,15 +412,15 @@
         }
     }
 
-    public interface Callbacks {
+    public interface Controller {
         public void onPrimaryClick(ViewEntry entry);
         public void onSecondaryClick(ViewEntry entry);
     }
 
-    public static final class DefaultCallbacks implements Callbacks {
+    public static final class DefaultController implements Controller {
         private Context mContext;
 
-        public DefaultCallbacks(Context context) {
+        public DefaultController(Context context) {
             mContext = context;
         }
 
@@ -410,44 +447,10 @@
         }
     }
 
-    public void setCallbacks(Callbacks callbacks) {
+    public void setController(Controller callbacks) {
         mCallbacks = callbacks;
     }
 
-    @Override
-    protected void onFinishInflate() {
-        super.onFinishInflate();
-        Context context = getContext();
-        mInflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
-        mContactHeaderWidget = (ContactHeaderWidget) findViewById(R.id.contact_header_widget);
-        mContactHeaderWidget.showStar(true);
-        mContactHeaderWidget.setExcludeMimes(new String[] {
-            Contacts.CONTENT_ITEM_TYPE
-        });
-
-        mListView = (ListView) findViewById(android.R.id.list);
-        mListView.setOnCreateContextMenuListener(this);
-        mListView.setScrollBarStyle(ListView.SCROLLBARS_OUTSIDE_OVERLAY);
-        mListView.setOnItemClickListener(this);
-        // Don't set it to mListView yet.  We do so later when we bind the adapter.
-        mEmptyView = findViewById(android.R.id.empty);
-
-        // Build the list of sections. The order they're added to mSections dictates the
-        // order they are displayed in the list.
-        mSections.add(mPhoneEntries);
-        mSections.add(mSmsEntries);
-        mSections.add(mEmailEntries);
-        mSections.add(mImEntries);
-        mSections.add(mPostalEntries);
-        mSections.add(mNicknameEntries);
-        mSections.add(mOrganizationEntries);
-        mSections.add(mGroupEntries);
-        mSections.add(mOtherEntries);
-
-        //TODO Read this value from a preference
-        mShowSmsLinksForAllPhones = true;
-    }
-
     /* package */ static String buildActionString(DataKind kind, ContentValues values,
             boolean lowerCase, Context context) {
         if (kind.actionHeader == null) {
@@ -922,18 +925,17 @@
     /* package */ void showDialog(int bundleDialogId) {
         Bundle bundle = new Bundle();
         bundle.putInt(DIALOG_ID_KEY, bundleDialogId);
-        getDialogManager().showDialogInView(this, bundle);
+        getDialogManager().showDialogInView(mView, bundle);
     }
 
     private DialogManager getDialogManager() {
         if (mDialogManager == null) {
-            Context context = getContext();
-            if (!(context instanceof DialogManager.DialogShowingViewActivity)) {
+            if (!(mContext instanceof DialogManager.DialogShowingViewActivity)) {
                 throw new IllegalStateException(
                         "View must be hosted in an Activity that implements " +
                         "DialogManager.DialogShowingViewActivity");
             }
-            mDialogManager = ((DialogManager.DialogShowingViewActivity)context).getDialogManager();
+            mDialogManager = ((DialogManager.DialogShowingViewActivity)mContext).getDialogManager();
         }
         return mDialogManager;
     }
@@ -978,7 +980,6 @@
         return ContactEntryAdapter.getEntry(mSections, info.position, SHOW_SEPARATORS);
     }
 
-    @Override
     public boolean onKeyDown(int keyCode, KeyEvent event) {
         switch (keyCode) {
             case KeyEvent.KEYCODE_CALL: {
@@ -1017,6 +1018,6 @@
             }
         }
 
-        return super.onKeyDown(keyCode, event);
+        return false;
     }
 }
diff --git a/src/com/android/contacts/widget/CompositeListAdapter.java b/src/com/android/contacts/widget/CompositeListAdapter.java
new file mode 100644
index 0000000..bd0c8d6
--- /dev/null
+++ b/src/com/android/contacts/widget/CompositeListAdapter.java
@@ -0,0 +1,222 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.contacts.widget;
+
+import android.database.DataSetObserver;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.BaseAdapter;
+import android.widget.ListAdapter;
+
+/**
+ * A general purpose adapter that is composed of multiple sub-adapters. It just
+ * appends them in the order they are added. It listens to changes from all
+ * sub-adapters and propagates them to its own listeners.
+ */
+public class CompositeListAdapter extends BaseAdapter {
+
+    private static final int INITIAL_CAPACITY = 2;
+
+    private ListAdapter[] mAdapters;
+    private int[] mCounts;
+    private int[] mViewTypeCounts;
+    private int mSize = 0;
+    private int mCount = 0;
+    private int mViewTypeCount = 0;
+    private boolean mAllItemsEnabled = true;
+    private boolean mCacheValid = true;
+
+    private DataSetObserver mDataSetObserver = new DataSetObserver() {
+
+        @Override
+        public void onChanged() {
+            invalidate();
+            notifyDataChanged();
+        }
+
+        @Override
+        public void onInvalidated() {
+            invalidate();
+            notifyDataChanged();
+        }
+    };
+
+    public CompositeListAdapter() {
+        this(INITIAL_CAPACITY);
+    }
+
+    public CompositeListAdapter(int initialCapacity) {
+        mAdapters = new ListAdapter[INITIAL_CAPACITY];
+        mCounts = new int[INITIAL_CAPACITY];
+        mViewTypeCounts = new int[INITIAL_CAPACITY];
+    }
+
+    public void addAdapter(ListAdapter adapter) {
+        if (mSize >= mAdapters.length) {
+            int newCapacity = mSize + 2;
+            ListAdapter[] newAdapters = new ListAdapter[newCapacity];
+            System.arraycopy(mAdapters, 0, newAdapters, 0, mSize);
+            mAdapters = newAdapters;
+
+            int[] newCounts = new int[newCapacity];
+            System.arraycopy(mCounts, 0, newCounts, 0, mSize);
+            mCounts = newCounts;
+
+            int[] newViewTypeCounts = new int[newCapacity];
+            System.arraycopy(mViewTypeCounts, 0, newViewTypeCounts, 0, mSize);
+            mViewTypeCounts = newViewTypeCounts;
+        }
+
+        adapter.registerDataSetObserver(mDataSetObserver);
+
+        int count = adapter.getCount();
+        int viewTypeCount = adapter.getViewTypeCount();
+
+        mAdapters[mSize] = adapter;
+        mCounts[mSize] = count;
+        mCount += count;
+        mAllItemsEnabled &= adapter.areAllItemsEnabled();
+        mViewTypeCounts[mSize] = viewTypeCount;
+        mViewTypeCount += viewTypeCount;
+        mSize++;
+
+        notifyDataChanged();
+    }
+
+    protected void notifyDataChanged() {
+        if (getCount() > 0) {
+            notifyDataSetChanged();
+        } else {
+            notifyDataSetInvalidated();
+        }
+    }
+
+    protected void invalidate() {
+        mCacheValid = false;
+    }
+
+    protected void ensureCacheValid() {
+        if (mCacheValid) {
+            return;
+        }
+
+        mCount = 0;
+        mAllItemsEnabled = true;
+        mViewTypeCount = 0;
+        for (int i = 0; i < mSize; i++) {
+            int count = mAdapters[i].getCount();
+            int viewTypeCount = mAdapters[i].getViewTypeCount();
+            mCounts[i] = count;
+            mCount += count;
+            mAllItemsEnabled &= mAdapters[i].areAllItemsEnabled();
+            mViewTypeCount += viewTypeCount;
+        }
+
+        mCacheValid = true;
+    }
+
+    public int getCount() {
+        ensureCacheValid();
+        return mCount;
+    }
+
+    public Object getItem(int position) {
+        ensureCacheValid();
+        int start = 0;
+        for (int i = 0; i < mCounts.length; i++) {
+            int end = start + mCounts[i];
+            if (position >= start && position < end) {
+                return mAdapters[i].getItem(position - start);
+            }
+            start = end;
+        }
+
+        throw new ArrayIndexOutOfBoundsException(position);
+    }
+
+    public long getItemId(int position) {
+        ensureCacheValid();
+        int start = 0;
+        for (int i = 0; i < mCounts.length; i++) {
+            int end = start + mCounts[i];
+            if (position >= start && position < end) {
+                return mAdapters[i].getItemId(position - start);
+            }
+            start = end;
+        }
+
+        throw new ArrayIndexOutOfBoundsException(position);
+    }
+
+    @Override
+    public int getViewTypeCount() {
+        ensureCacheValid();
+        return mViewTypeCount;
+    }
+
+    @Override
+    public int getItemViewType(int position) {
+        ensureCacheValid();
+        int start = 0;
+        int viewTypeOffset = 0;
+        for (int i = 0; i < mCounts.length; i++) {
+            int end = start + mCounts[i];
+            if (position >= start && position < end) {
+                return viewTypeOffset + mAdapters[i].getItemViewType(position - start);
+            }
+            viewTypeOffset += mViewTypeCounts[i];
+            start = end;
+        }
+
+        throw new ArrayIndexOutOfBoundsException(position);
+    }
+
+    public View getView(int position, View convertView, ViewGroup parent) {
+        ensureCacheValid();
+        int start = 0;
+        for (int i = 0; i < mCounts.length; i++) {
+            int end = start + mCounts[i];
+            if (position >= start && position < end) {
+                return mAdapters[i].getView(position - start, convertView, parent);
+            }
+            start = end;
+        }
+
+        throw new ArrayIndexOutOfBoundsException(position);
+    }
+
+    @Override
+    public boolean areAllItemsEnabled() {
+        ensureCacheValid();
+        return mAllItemsEnabled;
+    }
+
+    @Override
+    public boolean isEnabled(int position) {
+        ensureCacheValid();
+        int start = 0;
+        for (int i = 0; i < mCounts.length; i++) {
+            int end = start + mCounts[i];
+            if (position >= start && position < end) {
+                return mAdapters[i].areAllItemsEnabled()
+                        || mAdapters[i].isEnabled(position - start);
+            }
+            start = end;
+        }
+
+        throw new ArrayIndexOutOfBoundsException(position);
+    }
+}
diff --git a/src/com/android/contacts/PinnedHeaderListView.java b/src/com/android/contacts/widget/PinnedHeaderListView.java
similarity index 85%
rename from src/com/android/contacts/PinnedHeaderListView.java
rename to src/com/android/contacts/widget/PinnedHeaderListView.java
index 9d1391b..a3e6794 100644
--- a/src/com/android/contacts/PinnedHeaderListView.java
+++ b/src/com/android/contacts/widget/PinnedHeaderListView.java
@@ -14,20 +14,22 @@
  * limitations under the License.
  */
 
-package com.android.contacts;
+package com.android.contacts.widget;
 
 import android.content.Context;
 import android.graphics.Canvas;
 import android.util.AttributeSet;
 import android.view.View;
+import android.widget.AbsListView;
 import android.widget.ListAdapter;
 import android.widget.ListView;
+import android.widget.AbsListView.OnScrollListener;
 
 /**
  * A ListView that maintains a header pinned at the top of the list. The
  * pinned header can be pushed up and dissolved as needed.
  */
-public class PinnedHeaderListView extends ListView {
+public class PinnedHeaderListView extends ListView implements OnScrollListener {
 
     /**
      * Adapter interface.  The list adapter must implement this interface.
@@ -78,6 +80,8 @@
 
     private int mHeaderViewHeight;
 
+    private OnScrollListener mOnScrollListener;
+
     public PinnedHeaderListView(Context context) {
         super(context);
     }
@@ -88,6 +92,7 @@
 
     public PinnedHeaderListView(Context context, AttributeSet attrs, int defStyle) {
         super(context, attrs, defStyle);
+        super.setOnScrollListener(this);
     }
 
     public void setPinnedHeaderView(View view) {
@@ -109,6 +114,12 @@
     }
 
     @Override
+    public void setOnScrollListener(OnScrollListener onScrollListener) {
+        mOnScrollListener = onScrollListener;
+        super.setOnScrollListener(this);
+    }
+
+    @Override
     protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
         super.onMeasure(widthMeasureSpec, heightMeasureSpec);
         if (mHeaderView != null) {
@@ -127,6 +138,20 @@
         }
     }
 
+    public void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount,
+            int totalItemCount) {
+        configureHeaderView(firstVisibleItem);
+        if (mOnScrollListener != null) {
+            mOnScrollListener.onScroll(this, firstVisibleItem, visibleItemCount, totalItemCount);
+        }
+    }
+
+    public void onScrollStateChanged(AbsListView view, int scrollState) {
+        if (mOnScrollListener != null) {
+            mOnScrollListener.onScrollStateChanged(this, scrollState);
+        }
+    }
+
     public void configureHeaderView(int position) {
         if (mHeaderView == null) {
             return;
diff --git a/src/com/android/contacts/widget/SingleItemAdapter.java b/src/com/android/contacts/widget/SingleItemAdapter.java
new file mode 100644
index 0000000..3532bfc
--- /dev/null
+++ b/src/com/android/contacts/widget/SingleItemAdapter.java
@@ -0,0 +1,47 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.contacts.widget;
+
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.BaseAdapter;
+
+/**
+ * A general purpose adapter that contains exactly one item.
+ */
+public abstract class SingleItemAdapter extends BaseAdapter {
+
+    public int getCount() {
+        return 1;
+    }
+
+    public Object getItem(int position) {
+        return null;
+    }
+
+    public long getItemId(int position) {
+        return 0;
+    }
+
+    public View getView(int position, View convertView, ViewGroup parent) {
+        return getView(convertView, parent);
+    }
+
+    /**
+     * Creates the view.
+     */
+    protected abstract View getView(View convertView, ViewGroup parent);
+}
diff --git a/tests/AndroidManifest.xml b/tests/AndroidManifest.xml
index 7af1a54..17ad3f7 100644
--- a/tests/AndroidManifest.xml
+++ b/tests/AndroidManifest.xml
@@ -4,9 +4,9 @@
      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.
@@ -17,16 +17,39 @@
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
     package="com.android.contacts.tests">
 
+    <uses-permission android:name="android.permission.READ_CONTACTS" />
+
     <application>
         <uses-library android:name="android.test.runner" />
         <meta-data android:name="com.android.contacts.iconset" android:resource="@xml/iconset" />
+        <uses-permission android:name="android.permission.READ_CONTACTS" />
+
+        <activity android:name=".allintents.AllIntentsActivity"
+            android:label="@string/contactsIntents"
+            >
+            <intent-filter>
+                <action android:name="android.intent.action.MAIN" />
+                <category android:name="android.intent.category.DEFAULT" />
+                <category android:name="android.intent.category.LAUNCHER" />
+            </intent-filter>
+        </activity>
+
+        <activity android:name=".allintents.ResultActivity"
+            android:label="@string/result"
+            >
+            <intent-filter>
+                <action android:name="android.intent.action.MAIN" />
+                <category android:name="android.intent.category.DEFAULT" />
+            </intent-filter>
+        </activity>
+
     </application>
 
     <instrumentation android:name="android.test.InstrumentationTestRunner"
         android:targetPackage="com.android.contacts"
         android:label="Contacts app tests">
     </instrumentation>
-    
+
     <instrumentation android:name="com.android.contacts.ContactsLaunchPerformance"
         android:targetPackage="com.android.contacts"
         android:label="Contacts launch performance">
@@ -38,4 +61,4 @@
         android:label="Dialer launch performance">
     </instrumentation>
 
-</manifest> 
+</manifest>
diff --git a/tests/res/layout/intent_list_item.xml b/tests/res/layout/intent_list_item.xml
new file mode 100644
index 0000000..4749224
--- /dev/null
+++ b/tests/res/layout/intent_list_item.xml
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2006 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.
+-->
+
+<TextView xmlns:android="http://schemas.android.com/apk/res/android"
+    android:id="@android:id/text1"
+    android:layout_width="match_parent"
+    android:layout_height="wrap_content"
+    android:textAppearance="?android:attr/textAppearanceSmall"
+    android:gravity="center_vertical"
+    android:paddingLeft="6dip"
+    android:minHeight="?android:attr/listPreferredItemHeight"
+/>
diff --git a/tests/res/layout/result.xml b/tests/res/layout/result.xml
new file mode 100644
index 0000000..0ab32c6
--- /dev/null
+++ b/tests/res/layout/result.xml
@@ -0,0 +1,32 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- 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.
+-->
+
+<ScrollView
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent"
+    android:fillViewport="true"
+>
+
+  <TableLayout xmlns:android="http://schemas.android.com/apk/res/android"
+      android:id="@+id/table"
+      android:layout_width="match_parent"
+      android:layout_height="match_parent"
+      android:shrinkColumns="1"
+      android:stretchColumns="*">
+  </TableLayout>
+</ScrollView>
+
diff --git a/tests/res/values/donottranslate_strings.xml b/tests/res/values/donottranslate_strings.xml
new file mode 100644
index 0000000..a4d29e1
--- /dev/null
+++ b/tests/res/values/donottranslate_strings.xml
@@ -0,0 +1,65 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- 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.
+-->
+<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+
+    <string name="contactsIntents">Contacts Intents</string>
+    <string name="result">Result returned by activity</string>
+
+    <string-array name="allIntents">
+        <item>LIST_DEFAULT</item>
+        <item>LIST_ALL_CONTACTS_ACTION</item>
+        <item>LIST_CONTACTS_WITH_PHONES_ACTION</item>
+        <item>LIST_STARRED_ACTION</item>
+        <item>LIST_STARRED_ACTION (filter)</item>
+        <item>LIST_FREQUENT_ACTION</item>
+        <item>LIST_FREQUENT_ACTION (filter)</item>
+        <item>LIST_STREQUENT_ACTION</item>
+        <item>LIST_STREQUENT_ACTION (filter)</item>
+        <item>ACTION_PICK: contact</item>
+        <item>ACTION_PICK: contact (legacy)</item>
+        <item>ACTION_PICK: phone</item>
+        <item>ACTION_PICK: phone (legacy)</item>
+        <item>ACTION_PICK: postal</item>
+        <item>ACTION_PICK: postal (legacy)</item>
+        <item>ACTION_CREATE_SHORTCUT: contact</item>
+        <item>ACTION_CREATE_SHORTCUT: contact (filter)</item>
+        <item>ACTION_CREATE_SHORTCUT: dial</item>
+        <item>ACTION_CREATE_SHORTCUT: dial (filter)</item>
+        <item>ACTION_CREATE_SHORTCUT: message</item>
+        <item>ACTION_CREATE_SHORTCUT: message (filter)</item>
+        <item>ACTION_GET_CONTENT: contact</item>
+        <item>ACTION_GET_CONTENT: contact (filter)</item>
+        <item>ACTION_GET_CONTENT: contact (legacy)</item>
+        <item>ACTION_GET_CONTENT: contact (filter, legacy)</item>
+        <item>ACTION_GET_CONTENT: phone</item>
+        <item>ACTION_GET_CONTENT: phone (filter)</item>
+        <item>ACTION_GET_CONTENT: phone (legacy)</item>
+        <item>ACTION_GET_CONTENT: postal</item>
+        <item>ACTION_GET_CONTENT: postal (filter)</item>
+        <item>ACTION_GET_CONTENT: postal (legacy)</item>
+        <item>ACTION_INSERT_OR_EDIT</item>
+        <item>ACTION_SEARCH (call button)</item>
+        <item>ACTION_SEARCH: contact</item>
+        <item>ACTION_SEARCH: email</item>
+        <item>ACTION_SEARCH: phone</item>
+        <item>SEARCH_SUGGESTION_CLICKED (call button)</item>
+        <item>SEARCH_SUGGESTION_CLICKED: contact</item>
+        <item>SEARCH_SUGGESTION_DIAL_NUMBER_CLICKED</item>
+        <item>SEARCH_SUGGESTION_CREATE_CONTACT_CLICKED</item>
+        <item>TODO: JOIN_CONTACT</item>
+        <item>ACTION_GET_MULTIPLE_PHONES</item>
+    </string-array>
+</resources>
diff --git a/tests/src/com/android/contacts/ContactDetailLoaderTest.java b/tests/src/com/android/contacts/ContactDetailLoaderTest.java
index f7a7a84..094de6a 100644
--- a/tests/src/com/android/contacts/ContactDetailLoaderTest.java
+++ b/tests/src/com/android/contacts/ContactDetailLoaderTest.java
@@ -16,33 +16,44 @@
 
 package com.android.contacts;
 
+import com.android.contacts.tests.mocks.ContactsMockContext;
+import com.android.contacts.tests.mocks.MockContentProvider;
 import com.android.contacts.views.detail.ContactLoader;
 
+import android.app.patterns.Loader;
+import android.app.patterns.Loader.OnLoadCompleteListener;
 import android.content.ContentUris;
-import android.content.ContentValues;
-import android.database.Cursor;
 import android.net.Uri;
+import android.provider.ContactsContract.CommonDataKinds;
 import android.provider.ContactsContract.Contacts;
-import android.provider.ContactsContract.Data;
+import android.provider.ContactsContract.DisplayNameSources;
 import android.provider.ContactsContract.RawContacts;
+import android.provider.ContactsContract.RawContactsEntity;
+import android.provider.ContactsContract.StatusUpdates;
 import android.provider.ContactsContract.CommonDataKinds.StructuredName;
+import android.provider.ContactsContract.RawContacts.Data;
+import android.provider.ContactsContract.RawContacts.Entity;
 import android.test.AndroidTestCase;
 
 import junit.framework.AssertionFailedError;
 
 /**
  * Runs ContactLoader tests for the the contact-detail view.
- * TODO: Warning: This currently only works on wiped phones as this will wipe
- * your contact data
- * TODO: Test all fields returned by the Loader
- * TODO: Test social entries returned by the Loader
  */
 public class ContactDetailLoaderTest extends AndroidTestCase {
+    private ContactsMockContext mMockContext;
+    private MockContentProvider mContactsProvider;
+
     @Override
     protected void setUp() throws Exception {
         super.setUp();
-        //mContext.getContentResolver().delete(Data.CONTENT_URI, null, null);
-        //mContext.getContentResolver().delete(RawContacts.CONTENT_URI, null, null);
+        mMockContext = new ContactsMockContext(getContext());
+        mContactsProvider = mMockContext.getContactsProvider();
+    }
+
+    @Override
+    protected void tearDown() throws Exception {
+        super.tearDown();
     }
 
     /**
@@ -65,87 +76,17 @@
     }
 
     private ContactLoader.Result assertLoadContact(Uri uri) {
-        final ContactLoader loader = new ContactLoader(mContext, uri);
-        final ContactLoader.LoadContactTask loadContactTask = loader.new LoadContactTask();
-        return loadContactTask.testExecute();
-    }
-
-    protected Uri insertStructuredName(long rawContactId, String givenName, String familyName) {
-        ContentValues values = new ContentValues();
-        StringBuilder sb = new StringBuilder();
-        if (givenName != null) {
-            sb.append(givenName);
-        }
-        if (givenName != null && familyName != null) {
-            sb.append(" ");
-        }
-        if (familyName != null) {
-            sb.append(familyName);
-        }
-        values.put(StructuredName.DISPLAY_NAME, sb.toString());
-        values.put(StructuredName.GIVEN_NAME, givenName);
-        values.put(StructuredName.FAMILY_NAME, familyName);
-
-        return insertStructuredName(rawContactId, values);
-    }
-
-    protected Uri insertStructuredName(long rawContactId, ContentValues values) {
-        values.put(Data.RAW_CONTACT_ID, rawContactId);
-        values.put(Data.MIMETYPE, StructuredName.CONTENT_ITEM_TYPE);
-        Uri resultUri = getContext().getContentResolver().insert(Data.CONTENT_URI, values);
-        return resultUri;
-    }
-
-    protected Cursor queryRawContact(long rawContactId) {
-        return getContext().getContentResolver().query(ContentUris.withAppendedId(
-                RawContacts.CONTENT_URI, rawContactId), null, null, null, null);
-    }
-
-    protected Cursor queryContact(long contactId) {
-        return getContext().getContentResolver().query(ContentUris.withAppendedId(
-                Contacts.CONTENT_URI, contactId), null, null, null, null);
-    }
-
-    private long getContactIdByRawContactId(long rawContactId) {
-        Cursor c = queryRawContact(rawContactId);
-        assertTrue(c.moveToFirst());
-        long contactId = c.getLong(c.getColumnIndex(RawContacts.CONTACT_ID));
-        c.close();
-        return contactId;
-    }
-
-    private String getContactLookupByContactId(long contactId) {
-        Cursor c = queryContact(contactId);
-        assertTrue(c.moveToFirst());
-        String lookup = c.getString(c.getColumnIndex(Contacts.LOOKUP_KEY));
-        c.close();
-        return lookup;
-    }
-
-    public long createRawContact(String sourceId, String givenName, String familyName) {
-        ContentValues values = new ContentValues();
-
-        values.put(RawContacts.ACCOUNT_NAME, "aa");
-        values.put(RawContacts.ACCOUNT_TYPE, "mock");
-        values.put(RawContacts.SOURCE_ID, sourceId);
-        values.put(RawContacts.VERSION, 1);
-        values.put(RawContacts.DELETED, 0);
-        values.put(RawContacts.AGGREGATION_MODE, RawContacts.AGGREGATION_MODE_DEFAULT);
-        values.put(RawContacts.CUSTOM_RINGTONE, "d");
-        values.put(RawContacts.SEND_TO_VOICEMAIL, 1);
-        values.put(RawContacts.LAST_TIME_CONTACTED, 12345);
-        values.put(RawContacts.STARRED, 1);
-        values.put(RawContacts.SYNC1, "e");
-        values.put(RawContacts.SYNC2, "f");
-        values.put(RawContacts.SYNC3, "g");
-        values.put(RawContacts.SYNC4, "h");
-
-        Uri rawContactUri =
-            getContext().getContentResolver().insert(RawContacts.CONTENT_URI, values);
-
-        long rawContactId = ContentUris.parseId(rawContactUri);
-        insertStructuredName(rawContactId, givenName, familyName);
-        return rawContactId;
+        final ContactLoader loader = new ContactLoader(mMockContext, uri);
+        loader.setSynchronous(true);
+        // A regular variable can not be written from an anonymous class, so use a 1-array
+        final ContactLoader.Result[] result = new ContactLoader.Result[1];
+        loader.registerListener(0, new OnLoadCompleteListener<ContactLoader.Result>() {
+            public void onLoadComplete(Loader loader, ContactLoader.Result data) {
+                result[0] = data;
+            }
+        });
+        loader.startLoading();
+        return result[0];
     }
 
     public void testNullUri() {
@@ -180,117 +121,373 @@
 
     public void testLoadContactWithContactIdUri() {
         // Use content Uris that only contain the ID
-        // Use some special characters in the source id to ensure that Encode/Decode properly
-        // works in Uris
-        long rawContactId1 = createRawContact("JohnDoe:;\"'[]{}=+-_\\|/.,<>?!@#$", "John", "Doe");
-        long rawContactId2 = createRawContact("JaneDuh%12%%^&*()", "Jane", "Duh");
+        final long contactId = 1;
+        final long rawContactId = 11;
+        final long dataId = 21;
 
-        long contactId1 = getContactIdByRawContactId(rawContactId1);
-        long contactId2 = getContactIdByRawContactId(rawContactId2);
+        final String encodedLookup = Uri.encode("aa%12%@!");
+        final Uri baseUri = ContentUris.withAppendedId(Contacts.CONTENT_URI, contactId);
+        final Uri lookupUri = ContentUris.withAppendedId(
+                Uri.withAppendedPath(Contacts.CONTENT_LOOKUP_URI, encodedLookup),
+                contactId);
+        final Uri dataUri = Uri.withAppendedPath(baseUri, Contacts.Data.CONTENT_DIRECTORY);
 
-        Uri contactUri1 = ContentUris.withAppendedId(Contacts.CONTENT_URI, contactId1);
-        Uri contactUri2 = ContentUris.withAppendedId(Contacts.CONTENT_URI, contactId2);
+        ContactQueries queries = new ContactQueries();
+        queries.fetchLookupAndId(baseUri, contactId, encodedLookup);
+        queries.fetchHeaderData(baseUri, rawContactId, encodedLookup);
+        queries.fetchSocial(dataUri, contactId);
+        queries.fetchRawContacts(contactId, dataId, rawContactId);
 
-        ContactLoader.Result contact1 = assertLoadContact(contactUri1);
-        ContactLoader.Result contact2 = assertLoadContact(contactUri2);
+        ContactLoader.Result contact = assertLoadContact(baseUri);
 
-        assertEquals(contactId1, contact1.getId());
-        assertEquals(contactId2, contact2.getId());
+        assertEquals(contactId, contact.getId());
+        assertEquals(rawContactId, contact.getNameRawContactId());
+        assertEquals(DisplayNameSources.STRUCTURED_NAME, contact.getDisplayNameSource());
+        assertEquals(encodedLookup, contact.getLookupKey());
+        assertEquals(lookupUri, contact.getLookupUri());
+        assertEquals(1, contact.getEntities().size());
+        assertEquals(1, contact.getStatuses().size());
+        mContactsProvider.verify();
     }
 
     public void testLoadContactWithOldStyleUri() {
         // Use content Uris that only contain the ID but use the format used in Donut
-        long rawContactId1 = createRawContact("JohnDoe", "John", "Doe");
-        long rawContactId2 = createRawContact("JaneDuh", "Jane", "Duh");
+        final long contactId = 1;
+        final long rawContactId = 11;
+        final long dataId = 21;
 
-        Uri oldUri1 = ContentUris.withAppendedId(Uri.parse("content://contacts"), rawContactId1);
-        Uri oldUri2 = ContentUris.withAppendedId(Uri.parse("content://contacts"), rawContactId2);
+        final String encodedLookup = Uri.encode("aa%12%@!");
+        final Uri legacyUri = ContentUris.withAppendedId(
+                Uri.parse("content://contacts"), rawContactId);
+        final Uri rawContactUri = ContentUris.withAppendedId(RawContacts.CONTENT_URI, rawContactId);
+        final Uri baseUri = ContentUris.withAppendedId(Contacts.CONTENT_URI, contactId);
+        final Uri lookupUri = ContentUris.withAppendedId(
+                Uri.withAppendedPath(Contacts.CONTENT_LOOKUP_URI, encodedLookup),
+                contactId);
+        final Uri dataUri = Uri.withAppendedPath(baseUri, Contacts.Data.CONTENT_DIRECTORY);
 
-        ContactLoader.Result contact1 = assertLoadContact(oldUri1);
-        ContactLoader.Result contact2 = assertLoadContact(oldUri2);
+        ContactQueries queries = new ContactQueries();
+        queries.fetchContactIdAndLookupFromRawContactUri(rawContactUri, contactId, encodedLookup);
+        queries.fetchHeaderData(baseUri, rawContactId, encodedLookup);
+        queries.fetchSocial(dataUri, contactId);
+        queries.fetchRawContacts(contactId, dataId, rawContactId);
 
-        long contactId1 = getContactIdByRawContactId(rawContactId1);
-        long contactId2 = getContactIdByRawContactId(rawContactId2);
+        ContactLoader.Result contact = assertLoadContact(legacyUri);
 
-        assertEquals(contactId1, contact1.getId());
-        assertEquals(contactId2, contact2.getId());
+        assertEquals(contactId, contact.getId());
+        assertEquals(rawContactId, contact.getNameRawContactId());
+        assertEquals(DisplayNameSources.STRUCTURED_NAME, contact.getDisplayNameSource());
+        assertEquals(encodedLookup, contact.getLookupKey());
+        assertEquals(lookupUri, contact.getLookupUri());
+        assertEquals(1, contact.getEntities().size());
+        assertEquals(1, contact.getStatuses().size());
+        mContactsProvider.verify();
     }
 
     public void testLoadContactWithContactLookupUri() {
         // Use lookup-style Uris that do not contain the Contact-ID
-        long rawContactId1 = createRawContact("JohnDoe", "John", "Doe");
-        long rawContactId2 = createRawContact("JaneDuh", "Jane", "Duh");
 
-        assertTrue(rawContactId1 != rawContactId2);
+        final long contactId = 1;
+        final long rawContactId = 11;
+        final long dataId = 21;
 
-        long contactId1 = getContactIdByRawContactId(rawContactId1);
-        long contactId2 = getContactIdByRawContactId(rawContactId2);
+        final String encodedLookup = Uri.encode("aa%12%@!");
+        final Uri baseUri = ContentUris.withAppendedId(Contacts.CONTENT_URI, contactId);
+        final Uri lookupNoIdUri = Uri.withAppendedPath(Contacts.CONTENT_LOOKUP_URI, encodedLookup);
+        final Uri lookupUri = ContentUris.withAppendedId(lookupNoIdUri, contactId);
+        final Uri dataUri = Uri.withAppendedPath(baseUri, Contacts.Data.CONTENT_DIRECTORY);
 
-        assertTrue(contactId1 != contactId2);
+        ContactQueries queries = new ContactQueries();
+        queries.fetchLookupAndId(lookupNoIdUri, contactId, encodedLookup);
+        queries.fetchHeaderData(baseUri, rawContactId, encodedLookup);
+        queries.fetchSocial(dataUri, contactId);
+        queries.fetchRawContacts(contactId, dataId, rawContactId);
 
-        String lookupKey1 = getContactLookupByContactId(contactId1);
-        String lookupKey2 = getContactLookupByContactId(contactId2);
-        assertFalse(lookupKey1.equals(lookupKey2));
+        ContactLoader.Result contact = assertLoadContact(lookupNoIdUri);
 
-        Uri contactLookupUri1 = Uri.withAppendedPath(Contacts.CONTENT_LOOKUP_URI, lookupKey1);
-        Uri contactLookupUri2 = Uri.withAppendedPath(Contacts.CONTENT_LOOKUP_URI, lookupKey2);
-
-        ContactLoader.Result contact1 = assertLoadContact(contactLookupUri1);
-        ContactLoader.Result contact2 = assertLoadContact(contactLookupUri2);
-
-        assertEquals(contactId1, contact1.getId());
-        assertEquals(contactId2, contact2.getId());
+        assertEquals(contactId, contact.getId());
+        assertEquals(rawContactId, contact.getNameRawContactId());
+        assertEquals(DisplayNameSources.STRUCTURED_NAME, contact.getDisplayNameSource());
+        assertEquals(encodedLookup, contact.getLookupKey());
+        assertEquals(lookupUri, contact.getLookupUri());
+        assertEquals(1, contact.getEntities().size());
+        assertEquals(1, contact.getStatuses().size());
+        mContactsProvider.verify();
     }
 
     public void testLoadContactWithContactLookupAndIdUri() {
         // Use lookup-style Uris that also contain the Contact-ID
-        long rawContactId1 = createRawContact("JohnDoe", "John", "Doe");
-        long rawContactId2 = createRawContact("JaneDuh", "Jane", "Duh");
+        final long contactId = 1;
+        final long rawContactId = 11;
+        final long dataId = 21;
 
-        long contactId1 = getContactIdByRawContactId(rawContactId1);
-        long contactId2 = getContactIdByRawContactId(rawContactId2);
+        final String encodedLookup = Uri.encode("aa%12%@!");
+        final Uri baseUri = ContentUris.withAppendedId(Contacts.CONTENT_URI, contactId);
+        final Uri lookupUri = ContentUris.withAppendedId(
+                Uri.withAppendedPath(Contacts.CONTENT_LOOKUP_URI, encodedLookup),
+                contactId);
+        final Uri dataUri = Uri.withAppendedPath(baseUri, Contacts.Data.CONTENT_DIRECTORY);
 
-        String lookupKey1 = getContactLookupByContactId(contactId1);
-        String lookupKey2 = getContactLookupByContactId(contactId2);
+        ContactQueries queries = new ContactQueries();
+        queries.fetchHeaderData(baseUri, rawContactId, encodedLookup);
+        queries.fetchSocial(dataUri, contactId);
+        queries.fetchRawContacts(contactId, dataId, rawContactId);
 
-        Uri contactLookupUri1 = ContentUris.withAppendedId(
-                Uri.withAppendedPath(Contacts.CONTENT_LOOKUP_URI, lookupKey1), contactId1);
-        Uri contactLookupUri2 = ContentUris.withAppendedId(
-                Uri.withAppendedPath(Contacts.CONTENT_LOOKUP_URI, lookupKey2), contactId2);
+        ContactLoader.Result contact = assertLoadContact(lookupUri);
 
-        ContactLoader.Result contact1 = assertLoadContact(contactLookupUri1);
-        ContactLoader.Result contact2 = assertLoadContact(contactLookupUri2);
-
-        assertEquals(contactId1, contact1.getId());
-        assertEquals(contactId2, contact2.getId());
+        assertEquals(contactId, contact.getId());
+        assertEquals(rawContactId, contact.getNameRawContactId());
+        assertEquals(DisplayNameSources.STRUCTURED_NAME, contact.getDisplayNameSource());
+        assertEquals(encodedLookup, contact.getLookupKey());
+        assertEquals(lookupUri, contact.getLookupUri());
+        assertEquals(1, contact.getEntities().size());
+        assertEquals(1, contact.getStatuses().size());
+        mContactsProvider.verify();
     }
 
     public void testLoadContactWithContactLookupWithIncorrectIdUri() {
         // Use lookup-style Uris that contain incorrect Contact-ID
         // (we want to ensure that still the correct contact is chosen)
+        // In this test, the incorrect Id references another Contact
 
-        long rawContactId1 = createRawContact("JohnDoe", "John", "Doe");
-        long rawContactId2 = createRawContact("JaneDuh", "Jane", "Duh");
+        final long contactId = 1;
+        final long wrongContactId = 2;
+        final long rawContactId = 11;
+        final long wrongRawContactId = 12;
+        final long dataId = 21;
 
-        long contactId1 = getContactIdByRawContactId(rawContactId1);
-        long contactId2 = getContactIdByRawContactId(rawContactId2);
+        final String encodedLookup = Uri.encode("aa%12%@!");
+        final String wrongEncodedLookup = Uri.encode("ab%12%@!");
+        final Uri baseUri = ContentUris.withAppendedId(Contacts.CONTENT_URI, contactId);
+        final Uri wrongBaseUri = ContentUris.withAppendedId(Contacts.CONTENT_URI, wrongContactId);
+        final Uri lookupUri = ContentUris.withAppendedId(
+                Uri.withAppendedPath(Contacts.CONTENT_LOOKUP_URI, encodedLookup),
+                contactId);
+        final Uri lookupWithWrongIdUri = ContentUris.withAppendedId(
+                Uri.withAppendedPath(Contacts.CONTENT_LOOKUP_URI, encodedLookup),
+                wrongContactId);
+        final Uri dataUri = Uri.withAppendedPath(baseUri, Contacts.Data.CONTENT_DIRECTORY);
 
-        String lookupKey1 = getContactLookupByContactId(contactId1);
-        String lookupKey2 = getContactLookupByContactId(contactId2);
+        ContactQueries queries = new ContactQueries();
+        queries.fetchHeaderData(wrongBaseUri, wrongRawContactId, wrongEncodedLookup);
+        queries.fetchLookupAndId(lookupWithWrongIdUri, contactId, encodedLookup);
+        queries.fetchHeaderData(baseUri, rawContactId, encodedLookup);
+        queries.fetchSocial(dataUri, contactId);
+        queries.fetchRawContacts(contactId, dataId, rawContactId);
 
-        long[] fakeIds = new long[] { 0, rawContactId1, rawContactId2, contactId1, contactId2 };
+        ContactLoader.Result contact = assertLoadContact(lookupWithWrongIdUri);
 
-        for (long fakeContactId : fakeIds) {
-            Uri contactLookupUri1 = ContentUris.withAppendedId(
-                    Uri.withAppendedPath(Contacts.CONTENT_LOOKUP_URI, lookupKey1), fakeContactId);
-            Uri contactLookupUri2 = ContentUris.withAppendedId(
-                    Uri.withAppendedPath(Contacts.CONTENT_LOOKUP_URI, lookupKey2), fakeContactId);
+        assertEquals(contactId, contact.getId());
+        assertEquals(rawContactId, contact.getNameRawContactId());
+        assertEquals(DisplayNameSources.STRUCTURED_NAME, contact.getDisplayNameSource());
+        assertEquals(encodedLookup, contact.getLookupKey());
+        assertEquals(lookupUri, contact.getLookupUri());
+        assertEquals(1, contact.getEntities().size());
+        assertEquals(1, contact.getStatuses().size());
 
-            ContactLoader.Result contact1 = assertLoadContact(contactLookupUri1);
-            ContactLoader.Result contact2 = assertLoadContact(contactLookupUri2);
+        mContactsProvider.verify();
+    }
 
-            assertEquals(contactId1, contact1.getId());
-            assertEquals(contactId2, contact2.getId());
+    public void testLoadContactWithContactLookupWithIncorrectIdUri2() {
+        // Use lookup-style Uris that contain incorrect Contact-ID
+        // (we want to ensure that still the correct contact is chosen)
+        // In this test, the incorrect Id references no contact
+
+        final long contactId = 1;
+        final long wrongContactId = 2;
+        final long rawContactId = 11;
+        final long wrongRawContactId = 12;
+        final long dataId = 21;
+
+        final String encodedLookup = Uri.encode("aa%12%@!");
+        final String wrongEncodedLookup = Uri.encode("ab%12%@!");
+        final Uri baseUri = ContentUris.withAppendedId(Contacts.CONTENT_URI, contactId);
+        final Uri wrongBaseUri = ContentUris.withAppendedId(Contacts.CONTENT_URI, wrongContactId);
+        final Uri lookupUri = ContentUris.withAppendedId(
+                Uri.withAppendedPath(Contacts.CONTENT_LOOKUP_URI, encodedLookup),
+                contactId);
+        final Uri lookupWithWrongIdUri = ContentUris.withAppendedId(
+                Uri.withAppendedPath(Contacts.CONTENT_LOOKUP_URI, encodedLookup),
+                wrongContactId);
+        final Uri dataUri = Uri.withAppendedPath(baseUri, Contacts.Data.CONTENT_DIRECTORY);
+
+        ContactQueries queries = new ContactQueries();
+        queries.fetchHeaderDataNoResult(wrongBaseUri);
+        queries.fetchLookupAndId(lookupWithWrongIdUri, contactId, encodedLookup);
+        queries.fetchHeaderData(baseUri, rawContactId, encodedLookup);
+        queries.fetchSocial(dataUri, contactId);
+        queries.fetchRawContacts(contactId, dataId, rawContactId);
+
+        ContactLoader.Result contact = assertLoadContact(lookupWithWrongIdUri);
+
+        assertEquals(contactId, contact.getId());
+        assertEquals(rawContactId, contact.getNameRawContactId());
+        assertEquals(DisplayNameSources.STRUCTURED_NAME, contact.getDisplayNameSource());
+        assertEquals(encodedLookup, contact.getLookupKey());
+        assertEquals(lookupUri, contact.getLookupUri());
+        assertEquals(1, contact.getEntities().size());
+        assertEquals(1, contact.getStatuses().size());
+
+        mContactsProvider.verify();
+    }
+
+    public void testLoadContactWithContactLookupWithIncorrectIdUri3() {
+        // Use lookup-style Uris that contain incorrect Contact-ID
+        // (we want to ensure that still the correct contact is chosen)
+        // In this test, the incorrect Id references no contact and the lookup
+        // key can also not be resolved
+
+        final long contactId = 1;
+        final long wrongContactId = 2;
+        final long rawContactId = 11;
+        final long wrongRawContactId = 12;
+        final long dataId = 21;
+
+        final String encodedLookup = Uri.encode("aa%12%@!");
+        final String wrongEncodedLookup = Uri.encode("ab%12%@!");
+        final Uri baseUri = ContentUris.withAppendedId(Contacts.CONTENT_URI, contactId);
+        final Uri wrongBaseUri = ContentUris.withAppendedId(Contacts.CONTENT_URI, wrongContactId);
+        final Uri lookupUri = ContentUris.withAppendedId(
+                Uri.withAppendedPath(Contacts.CONTENT_LOOKUP_URI, encodedLookup),
+                contactId);
+        final Uri lookupWithWrongIdUri = ContentUris.withAppendedId(
+                Uri.withAppendedPath(Contacts.CONTENT_LOOKUP_URI, encodedLookup),
+                wrongContactId);
+        final Uri dataUri = Uri.withAppendedPath(baseUri, Contacts.Data.CONTENT_DIRECTORY);
+
+        ContactQueries queries = new ContactQueries();
+        queries.fetchHeaderDataNoResult(wrongBaseUri);
+        queries.fetchLookupAndIdNoResult(lookupWithWrongIdUri);
+
+        ContactLoader.Result contact = assertLoadContact(lookupWithWrongIdUri);
+
+        assertEquals(ContactLoader.Result.NOT_FOUND, contact);
+
+        mContactsProvider.verify();
+    }
+
+    private class ContactQueries {
+        private void fetchRawContacts(final long contactId, final long dataId,
+                final long rawContactId) {
+            mContactsProvider.expectQuery(RawContactsEntity.CONTENT_URI)
+                .withDefaultProjection(new String[] {
+                        RawContacts._ID, RawContacts.ACCOUNT_NAME, RawContacts.ACCOUNT_TYPE,
+                        RawContacts.DIRTY, RawContacts.VERSION, RawContacts.SOURCE_ID,
+                        RawContacts.SYNC1, RawContacts.SYNC2, RawContacts.SYNC3, RawContacts.SYNC4,
+                        RawContacts.DELETED, RawContacts.CONTACT_ID, RawContacts.STARRED,
+                        RawContacts.IS_RESTRICTED, RawContacts.NAME_VERIFIED,
+
+                        Entity.DATA_ID, Data.RES_PACKAGE, Data.MIMETYPE, Data.IS_PRIMARY,
+                        Data.IS_SUPER_PRIMARY, Data.DATA_VERSION,
+                        CommonDataKinds.GroupMembership.GROUP_SOURCE_ID,
+                        Data.DATA1, Data.DATA2, Data.DATA3, Data.DATA4, Data.DATA5,
+                        Data.DATA6, Data.DATA7, Data.DATA8, Data.DATA9, Data.DATA10,
+                        Data.DATA11, Data.DATA12, Data.DATA13, Data.DATA14, Data.DATA15,
+                        Data.SYNC1, Data.SYNC2, Data.SYNC3, Data.SYNC4
+                })
+                .withSelection(
+                        RawContacts.CONTACT_ID + "=?",
+                        new String[] { String.valueOf(contactId) } )
+                .returnRow(
+                        rawContactId, "mockAccountName", "mockAccountType",
+                        0, 1, "aa%12%@!",
+                        "", "", "", "",
+                        0, contactId, 0,
+                        0, 1,
+
+                        dataId, "", StructuredName.CONTENT_ITEM_TYPE, 1,
+                        1, 1,
+                        "mockGroupId",
+                        "dat1", "dat2", "dat3", "dat4", "dat5",
+                        "dat6", "dat7", "dat8", "dat9", "dat10",
+                        "dat11", "dat12", "dat13", "dat14", null,
+                        "syn1", "syn2", "syn3", "syn4");
+        }
+
+        private void fetchSocial(final Uri dataUri, final long expectedContactId) {
+            mContactsProvider.expectQuery(dataUri)
+                    .withProjection(
+                            Contacts._ID, StatusUpdates.STATUS, StatusUpdates.STATUS_RES_PACKAGE,
+                            StatusUpdates.STATUS_ICON, StatusUpdates.STATUS_LABEL,
+                            StatusUpdates.STATUS_TIMESTAMP, StatusUpdates.PRESENCE)
+                    .withSelection(
+                            StatusUpdates.PRESENCE +" IS NOT NULL OR " +
+                            StatusUpdates.STATUS + " IS NOT NULL",
+                            (String[]) null)
+                    .returnRow(
+                            expectedContactId, "This is a mock Status update", 0,
+                            1, 2,
+                            0, StatusUpdates.AVAILABLE);
+        }
+
+        private void fetchHeaderData(final Uri uri, final long expectedRawContactId,
+                final String expectedEncodedLookup) {
+            mContactsProvider.expectQuery(uri)
+                    .withProjection(
+                            Contacts.NAME_RAW_CONTACT_ID,
+                            Contacts.DISPLAY_NAME_SOURCE,
+                            Contacts.LOOKUP_KEY,
+                            Contacts.DISPLAY_NAME,
+                            Contacts.PHONETIC_NAME,
+                            Contacts.PHOTO_ID,
+                            Contacts.STARRED,
+                            Contacts.CONTACT_PRESENCE,
+                            Contacts.CONTACT_STATUS,
+                            Contacts.CONTACT_STATUS_TIMESTAMP,
+                            Contacts.CONTACT_STATUS_RES_PACKAGE,
+                            Contacts.CONTACT_STATUS_LABEL)
+                    .returnRow(
+                            expectedRawContactId,
+                            DisplayNameSources.STRUCTURED_NAME,
+                            expectedEncodedLookup,
+                            "contactDisplayName",
+                            "contactPhoneticName",
+                            null,
+                            0,
+                            null,
+                            null,
+                            null,
+                            null,
+                            null);
+        }
+
+        private void fetchHeaderDataNoResult(final Uri uri) {
+            mContactsProvider.expectQuery(uri)
+                    .withProjection(
+                            Contacts.NAME_RAW_CONTACT_ID,
+                            Contacts.DISPLAY_NAME_SOURCE,
+                            Contacts.LOOKUP_KEY,
+                            Contacts.DISPLAY_NAME,
+                            Contacts.PHONETIC_NAME,
+                            Contacts.PHOTO_ID,
+                            Contacts.STARRED,
+                            Contacts.CONTACT_PRESENCE,
+                            Contacts.CONTACT_STATUS,
+                            Contacts.CONTACT_STATUS_TIMESTAMP,
+                            Contacts.CONTACT_STATUS_RES_PACKAGE,
+                            Contacts.CONTACT_STATUS_LABEL);
+        }
+
+        private void fetchLookupAndId(final Uri sourceUri, final long expectedContactId,
+                final String expectedEncodedLookup) {
+            mContactsProvider.expectQuery(sourceUri)
+                    .withProjection(Contacts.LOOKUP_KEY, Contacts._ID)
+                    .returnRow(expectedEncodedLookup, expectedContactId);
+        }
+
+        private void fetchLookupAndIdNoResult(final Uri sourceUri) {
+            mContactsProvider.expectQuery(sourceUri)
+                    .withProjection(Contacts.LOOKUP_KEY, Contacts._ID);
+        }
+
+        private void fetchContactIdAndLookupFromRawContactUri(final Uri rawContactUri,
+                final long expectedContactId, final String expectedEncodedLookup) {
+            // TODO: use a lighter query by joining rawcontacts with contacts in provider
+            // (See ContactContracts.java)
+            final Uri dataUri = Uri.withAppendedPath(rawContactUri, Data.CONTENT_DIRECTORY);
+            mContactsProvider.expectQuery(dataUri)
+                    .withProjection(RawContacts.CONTACT_ID, Contacts.LOOKUP_KEY)
+                    .returnRow(expectedContactId, expectedEncodedLookup);
         }
     }
 }
diff --git a/tests/src/com/android/contacts/ContactDetailTest.java b/tests/src/com/android/contacts/ContactDetailTest.java
new file mode 100644
index 0000000..926a353
--- /dev/null
+++ b/tests/src/com/android/contacts/ContactDetailTest.java
@@ -0,0 +1,45 @@
+package com.android.contacts;
+
+import com.android.contacts.activities.ContactDetailActivity;
+import com.android.contacts.tests.mocks.ContactsMockContext;
+import com.android.contacts.tests.mocks.MockContentProvider;
+import com.android.contacts.views.detail.ContactLoader;
+
+import android.content.ContentUris;
+import android.content.Intent;
+import android.provider.ContactsContract.Contacts;
+import android.test.ActivityUnitTestCase;
+
+public class ContactDetailTest extends ActivityUnitTestCase<ContactDetailActivity> {
+    private ContactsMockContext mContext;
+    private MockContentProvider mContactsProvider;
+
+    public ContactDetailTest() {
+        super(ContactDetailActivity.class);
+    }
+
+    @Override
+    protected void setUp() throws Exception {
+        super.setUp();
+        mContext = new ContactsMockContext(getInstrumentation().getTargetContext());
+        mContactsProvider = mContext.getContactsProvider();
+        setActivityContext(mContext);
+    }
+
+    @Override
+    protected void tearDown() throws Exception {
+        super.tearDown();
+    }
+
+//    public void testFoo() {
+//        // Use lookup-style Uris that also contain the Contact-ID
+//        //long rawContactId1 = mCreator.createRawContact("JohnDoe", "John", "Doe");
+//        //long contactId1 = mCreator.getContactIdByRawContactId(rawContactId1);
+//        //Uri contactUri1 = ContentUris.withAppendedId(Contacts.CONTENT_URI, contactId1);
+//        Intent intent = new Intent(Intent.ACTION_VIEW,
+//                ContentUris.withAppendedId(Contacts.CONTENT_URI, 123));
+//        startActivity(intent, null, null);
+//        ContactDetailActivity activity = getActivity();
+//        mContactsProvider.verify();
+//    }
+}
diff --git a/tests/src/com/android/contacts/tests/allintents/AllIntentsActivity.java b/tests/src/com/android/contacts/tests/allintents/AllIntentsActivity.java
new file mode 100644
index 0000000..d6b9cac
--- /dev/null
+++ b/tests/src/com/android/contacts/tests/allintents/AllIntentsActivity.java
@@ -0,0 +1,432 @@
+/*
+ * 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.tests.allintents;
+
+import com.android.contacts.ContactsSearchManager;
+import com.android.contacts.tests.R;
+
+import android.app.ListActivity;
+import android.app.SearchManager;
+import android.content.ComponentName;
+import android.content.ContentUris;
+import android.content.Intent;
+import android.database.Cursor;
+import android.net.Uri;
+import android.os.Bundle;
+import android.provider.Contacts.ContactMethods;
+import android.provider.Contacts.People;
+import android.provider.Contacts.Phones;
+import android.provider.ContactsContract.Contacts;
+import android.provider.ContactsContract.Intents;
+import android.provider.ContactsContract.CommonDataKinds.Phone;
+import android.provider.ContactsContract.CommonDataKinds.StructuredPostal;
+import android.provider.ContactsContract.Intents.Insert;
+import android.provider.ContactsContract.Intents.UI;
+import android.view.View;
+import android.widget.ArrayAdapter;
+import android.widget.ListView;
+
+/**
+ * An activity that provides access to various modes of the contacts application.
+ * Useful for manual and scripted tests.
+ */
+@SuppressWarnings("deprecation")
+public class AllIntentsActivity extends ListActivity {
+
+    private static final String ANDROID_CONTACTS_PACKAGE = "com.android.contacts";
+
+    private static final String CONTACTS_LIST_ACTIVITY_CLASS_NAME =
+            "com.android.contacts.ContactsListActivity";
+    private static final String SEARCH_RESULTS_ACTIVITY_CLASS_NAME =
+            "com.android.contacts.SearchResultsActivity";
+    private static final String MULTIPLE_PHONE_PICKER_ACTIVITY_CLASS_NAME =
+        "com.android.contacts.MultiplePhonePickerActivity";
+
+    private static final int LIST_DEFAULT = 0;
+    private static final int LIST_ALL_CONTACTS_ACTION = 1;
+    private static final int LIST_CONTACTS_WITH_PHONES_ACTION = 2;
+    private static final int LIST_STARRED_ACTION = 3;
+    private static final int LIST_STARRED_ACTION_WITH_FILTER = 4;
+    private static final int LIST_FREQUENT_ACTION = 5;
+    private static final int LIST_FREQUENT_ACTION_WITH_FILTER = 6;
+    private static final int LIST_STREQUENT_ACTION = 7;
+    private static final int LIST_STREQUENT_ACTION_WITH_FILTER = 8;
+    private static final int ACTION_PICK_CONTACT = 9;
+    private static final int ACTION_PICK_CONTACT_LEGACY = 10;
+    private static final int ACTION_PICK_PHONE = 11;
+    private static final int ACTION_PICK_PHONE_LEGACY = 12;
+    private static final int ACTION_PICK_POSTAL = 13;
+    private static final int ACTION_PICK_POSTAL_LEGACY = 14;
+    private static final int ACTION_CREATE_SHORTCUT_CONTACT = 15;
+    private static final int ACTION_CREATE_SHORTCUT_CONTACT_FILTER = 16;
+    private static final int ACTION_CREATE_SHORTCUT_DIAL = 17;
+    private static final int ACTION_CREATE_SHORTCUT_DIAL_FILTER = 18;
+    private static final int ACTION_CREATE_SHORTCUT_MESSAGE = 19;
+    private static final int ACTION_CREATE_SHORTCUT_MESSAGE_FILTER = 20;
+    private static final int ACTION_GET_CONTENT_CONTACT = 21;
+    private static final int ACTION_GET_CONTENT_CONTACT_FILTER = 22;
+    private static final int ACTION_GET_CONTENT_CONTACT_LEGACY = 23;
+    private static final int ACTION_GET_CONTENT_CONTACT_FILTER_LEGACY = 24;
+    private static final int ACTION_GET_CONTENT_PHONE = 25;
+    private static final int ACTION_GET_CONTENT_PHONE_FILTER = 26;
+    private static final int ACTION_GET_CONTENT_PHONE_LEGACY = 27;
+    private static final int ACTION_GET_CONTENT_POSTAL = 28;
+    private static final int ACTION_GET_CONTENT_POSTAL_FILTER = 29;
+    private static final int ACTION_GET_CONTENT_POSTAL_LEGACY = 30;
+    private static final int ACTION_INSERT_OR_EDIT = 31;
+    private static final int ACTION_SEARCH_CALL = 32;
+    private static final int ACTION_SEARCH_CONTACT = 33;
+    private static final int ACTION_SEARCH_EMAIL = 34;
+    private static final int ACTION_SEARCH_PHONE = 35;
+    private static final int SEARCH_SUGGESTION_CLICKED_CALL_BUTTON = 36;
+    private static final int SEARCH_SUGGESTION_CLICKED_CONTACT = 37;
+    private static final int SEARCH_SUGGESTION_DIAL_NUMBER_CLICKED = 38;
+    private static final int SEARCH_SUGGESTION_CREATE_CONTACT_CLICKED = 39;
+    private static final int JOIN_CONTACT = 40;
+    private static final int ACTION_GET_MULTIPLE_PHONES = 41;
+
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        setListAdapter(new ArrayAdapter<String>(this, R.layout.intent_list_item,
+                getResources().getStringArray(R.array.allIntents)));
+    }
+
+    @Override
+    protected void onListItemClick(ListView l, View v, int position, long id) {
+        super.onListItemClick(l, v, position, id);
+
+        switch (position) {
+            case LIST_DEFAULT: {
+                startContactsListActivity(
+                        new Intent(Intent.ACTION_VIEW, Contacts.CONTENT_URI));
+                break;
+            }
+            case LIST_ALL_CONTACTS_ACTION: {
+                startContactsListActivity(
+                        new Intent(UI.LIST_ALL_CONTACTS_ACTION, Contacts.CONTENT_URI));
+                break;
+            }
+            case LIST_CONTACTS_WITH_PHONES_ACTION: {
+                startContactsListActivity(
+                        new Intent(UI.LIST_CONTACTS_WITH_PHONES_ACTION, Contacts.CONTENT_URI));
+                break;
+            }
+            case LIST_STARRED_ACTION: {
+                startContactsListActivity(
+                        new Intent(UI.LIST_STARRED_ACTION, Contacts.CONTENT_URI));
+                break;
+            }
+            case LIST_STARRED_ACTION_WITH_FILTER: {
+                startContactsListActivity(
+                        buildFilterIntent(UI.LIST_STARRED_ACTION, null, null));
+                break;
+            }
+            case LIST_FREQUENT_ACTION: {
+                startContactsListActivity(
+                        new Intent(UI.LIST_FREQUENT_ACTION, Contacts.CONTENT_URI));
+                break;
+            }
+            case LIST_FREQUENT_ACTION_WITH_FILTER: {
+                startContactsListActivity(
+                        buildFilterIntent(UI.LIST_FREQUENT_ACTION, null, null));
+                break;
+            }
+            case LIST_STREQUENT_ACTION: {
+                startContactsListActivity(
+                        new Intent(UI.LIST_STREQUENT_ACTION, Contacts.CONTENT_URI));
+                break;
+            }
+            case LIST_STREQUENT_ACTION_WITH_FILTER: {
+                startContactsListActivity(
+                        buildFilterIntent(UI.LIST_STREQUENT_ACTION, null, null));
+                break;
+            }
+            case ACTION_PICK_CONTACT: {
+                startContactsListActivityForResult(
+                        new Intent(Intent.ACTION_PICK, Contacts.CONTENT_URI));
+                break;
+            }
+            case ACTION_PICK_CONTACT_LEGACY: {
+                startContactsListActivityForResult(
+                        new Intent(Intent.ACTION_PICK, People.CONTENT_URI));
+                break;
+            }
+            case ACTION_PICK_PHONE: {
+                startContactsListActivityForResult(
+                        new Intent(Intent.ACTION_PICK, Phone.CONTENT_URI));
+                break;
+            }
+            case ACTION_PICK_PHONE_LEGACY: {
+                startContactsListActivityForResult(
+                        new Intent(Intent.ACTION_PICK, Phones.CONTENT_URI));
+                break;
+            }
+            case ACTION_PICK_POSTAL: {
+                startContactsListActivityForResult(
+                        new Intent(Intent.ACTION_PICK, StructuredPostal.CONTENT_URI));
+                break;
+            }
+            case ACTION_PICK_POSTAL_LEGACY: {
+                Intent intent = new Intent(Intent.ACTION_PICK);
+                intent.setType(ContactMethods.CONTENT_POSTAL_TYPE);
+                startContactsListActivityForResult(intent);
+                break;
+            }
+            case ACTION_CREATE_SHORTCUT_CONTACT: {
+                Intent intent = new Intent(Intent.ACTION_CREATE_SHORTCUT);
+                startContactsListActivityForResult(intent);
+                break;
+            }
+            case ACTION_CREATE_SHORTCUT_CONTACT_FILTER: {
+                startContactsListActivityForResult(
+                        buildFilterIntent(Intent.ACTION_CREATE_SHORTCUT,
+                                CONTACTS_LIST_ACTIVITY_CLASS_NAME, null));
+                break;
+            }
+            case ACTION_CREATE_SHORTCUT_DIAL: {
+                Intent intent = new Intent(Intent.ACTION_CREATE_SHORTCUT);
+                intent.setComponent(
+                        new ComponentName(ANDROID_CONTACTS_PACKAGE, "alias.DialShortcut"));
+                startActivityForResult(intent, 0);
+                break;
+            }
+            case ACTION_CREATE_SHORTCUT_DIAL_FILTER: {
+                startContactsListActivityForResult(
+                        buildFilterIntent(Intent.ACTION_CREATE_SHORTCUT,
+                                "alias.DialShortcut", null));
+                break;
+            }
+            case ACTION_CREATE_SHORTCUT_MESSAGE: {
+                Intent intent = new Intent(Intent.ACTION_CREATE_SHORTCUT);
+                intent.setComponent(
+                        new ComponentName(ANDROID_CONTACTS_PACKAGE, "alias.MessageShortcut"));
+                startActivityForResult(intent, 0);
+                break;
+            }
+            case ACTION_CREATE_SHORTCUT_MESSAGE_FILTER: {
+                startContactsListActivityForResult(
+                        buildFilterIntent(Intent.ACTION_CREATE_SHORTCUT,
+                                "alias.MessageShortcut", null));
+                break;
+            }
+            case ACTION_GET_CONTENT_CONTACT: {
+                Intent intent = new Intent(Intent.ACTION_GET_CONTENT);
+                intent.setType(Contacts.CONTENT_ITEM_TYPE);
+                startContactsListActivityForResult(intent);
+                break;
+            }
+            case ACTION_GET_CONTENT_CONTACT_LEGACY: {
+                Intent intent = new Intent(Intent.ACTION_GET_CONTENT);
+                intent.setType(People.CONTENT_ITEM_TYPE);
+                startContactsListActivityForResult(intent);
+                break;
+            }
+            case ACTION_GET_CONTENT_CONTACT_FILTER: {
+                startContactsListActivityForResult(
+                        buildFilterIntent(Intent.ACTION_GET_CONTENT,
+                                CONTACTS_LIST_ACTIVITY_CLASS_NAME,
+                                Contacts.CONTENT_ITEM_TYPE));
+                break;
+            }
+            case ACTION_GET_CONTENT_CONTACT_FILTER_LEGACY: {
+                startContactsListActivityForResult(
+                        buildFilterIntent(Intent.ACTION_GET_CONTENT,
+                                CONTACTS_LIST_ACTIVITY_CLASS_NAME,
+                                People.CONTENT_ITEM_TYPE));
+                break;
+            }
+            case ACTION_GET_CONTENT_PHONE: {
+                Intent intent = new Intent(Intent.ACTION_GET_CONTENT);
+                intent.setType(Phone.CONTENT_ITEM_TYPE);
+                startContactsListActivityForResult(intent);
+                break;
+            }
+            case ACTION_GET_CONTENT_PHONE_FILTER: {
+                startContactsListActivityForResult(
+                        buildFilterIntent(Intent.ACTION_GET_CONTENT,
+                                CONTACTS_LIST_ACTIVITY_CLASS_NAME,
+                                Phone.CONTENT_ITEM_TYPE));
+                break;
+            }
+            case ACTION_GET_CONTENT_PHONE_LEGACY: {
+                Intent intent = new Intent(Intent.ACTION_GET_CONTENT);
+                intent.setType(Phones.CONTENT_ITEM_TYPE);
+                startContactsListActivityForResult(intent);
+                break;
+            }
+            case ACTION_GET_CONTENT_POSTAL: {
+                Intent intent = new Intent(Intent.ACTION_GET_CONTENT);
+                intent.setType(StructuredPostal.CONTENT_ITEM_TYPE);
+                startContactsListActivityForResult(intent);
+                break;
+            }
+            case ACTION_GET_CONTENT_POSTAL_FILTER: {
+                startContactsListActivityForResult(
+                        buildFilterIntent(Intent.ACTION_GET_CONTENT,
+                                CONTACTS_LIST_ACTIVITY_CLASS_NAME,
+                                StructuredPostal.CONTENT_ITEM_TYPE));
+                break;
+            }
+            case ACTION_GET_CONTENT_POSTAL_LEGACY: {
+                Intent intent = new Intent(Intent.ACTION_GET_CONTENT);
+                intent.setType(ContactMethods.CONTENT_POSTAL_ITEM_TYPE);
+                startContactsListActivityForResult(intent);
+                break;
+            }
+            case ACTION_INSERT_OR_EDIT: {
+                Intent intent = new Intent(Intent.ACTION_INSERT_OR_EDIT);
+                startContactsListActivity(intent);
+                break;
+            }
+            case ACTION_SEARCH_CALL: {
+                Intent intent = new Intent(Intent.ACTION_SEARCH);
+                intent.putExtra(SearchManager.ACTION_MSG, "call");
+                intent.putExtra(SearchManager.QUERY, "800-4664-411");
+                startSearchResultActivity(intent);
+                break;
+            }
+            case ACTION_SEARCH_CONTACT: {
+                Intent intent = new Intent(Intent.ACTION_SEARCH);
+                intent.putExtra(SearchManager.QUERY, "a");
+                startSearchResultActivity(intent);
+                break;
+            }
+            case ACTION_SEARCH_EMAIL: {
+                Intent intent = new Intent(Intent.ACTION_SEARCH);
+                intent.putExtra(Insert.EMAIL, "a");
+                startSearchResultActivity(intent);
+                break;
+            }
+            case ACTION_SEARCH_PHONE: {
+                Intent intent = new Intent(Intent.ACTION_SEARCH);
+                intent.putExtra(Insert.PHONE, "800");
+                startSearchResultActivity(intent);
+                break;
+            }
+            case SEARCH_SUGGESTION_CLICKED_CALL_BUTTON: {
+                long contactId = findArbitraryContactWithPhoneNumber();
+                if (contactId != -1) {
+                    Uri contactUri = ContentUris.withAppendedId(Contacts.CONTENT_URI, contactId);
+                    Intent intent = new Intent(Intents.SEARCH_SUGGESTION_CLICKED);
+                    intent.setData(contactUri);
+                    intent.putExtra(SearchManager.ACTION_MSG, "call");
+                    startContactsListActivity(intent);
+                }
+                break;
+            }
+            case SEARCH_SUGGESTION_CLICKED_CONTACT: {
+                long contactId = findArbitraryContactWithPhoneNumber();
+                if (contactId != -1) {
+                    Uri contactUri = ContentUris.withAppendedId(Contacts.CONTENT_URI, contactId);
+                    Intent intent = new Intent(Intents.SEARCH_SUGGESTION_CLICKED);
+                    intent.setData(contactUri);
+                    startContactsListActivity(intent);
+                }
+                break;
+            }
+            case SEARCH_SUGGESTION_DIAL_NUMBER_CLICKED: {
+                Intent intent = new Intent(Intents.SEARCH_SUGGESTION_DIAL_NUMBER_CLICKED);
+                intent.setData(Uri.parse("tel:800-4664411"));
+                startContactsListActivity(intent);
+                break;
+            }
+            case SEARCH_SUGGESTION_CREATE_CONTACT_CLICKED: {
+                Intent intent = new Intent(Intents.SEARCH_SUGGESTION_CREATE_CONTACT_CLICKED);
+                intent.setData(Uri.parse("tel:800-4664411"));
+                startContactsListActivity(intent);
+                break;
+            }
+            case JOIN_CONTACT: {
+                // TODO
+                break;
+            }
+            case ACTION_GET_MULTIPLE_PHONES: {
+                Intent intent = new Intent(Intents.ACTION_GET_MULTIPLE_PHONES);
+                intent.setType(Phone.CONTENT_TYPE);
+                intent.putExtra(Intents.EXTRA_PHONE_URIS, new Uri[] {
+                        Uri.parse("tel:555-1212"), Uri.parse("tel:555-2121")
+                });
+                startMultiplePhoneSelectionActivityForResult(intent);
+                break;
+            }
+        }
+    }
+
+    private Intent buildFilterIntent(String action, String component, String type) {
+        Intent intent = new Intent(UI.FILTER_CONTACTS_ACTION);
+        intent.putExtra(UI.FILTER_TEXT_EXTRA_KEY, "A");
+        intent.putExtra(ContactsSearchManager.ORIGINAL_ACTION_EXTRA_KEY, action);
+        if (component != null) {
+            intent.putExtra(ContactsSearchManager.ORIGINAL_COMPONENT_EXTRA_KEY, component);
+        }
+        if (type != null) {
+            intent.putExtra(ContactsSearchManager.ORIGINAL_TYPE_EXTRA_KEY, type);
+        }
+        return intent;
+    }
+
+    private void startContactsListActivity(Intent intent) {
+        intent.setComponent(
+                new ComponentName(ANDROID_CONTACTS_PACKAGE, CONTACTS_LIST_ACTIVITY_CLASS_NAME));
+        intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+        startActivity(intent);
+    }
+
+    private void startContactsListActivityForResult(Intent intent) {
+        intent.setComponent(
+                new ComponentName(ANDROID_CONTACTS_PACKAGE, CONTACTS_LIST_ACTIVITY_CLASS_NAME));
+        startActivityForResult(intent, 12);
+    }
+
+    private void startSearchResultActivity(Intent intent) {
+        intent.setComponent(
+                new ComponentName(ANDROID_CONTACTS_PACKAGE, SEARCH_RESULTS_ACTIVITY_CLASS_NAME));
+        intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+        startActivity(intent);
+    }
+
+    private void startMultiplePhoneSelectionActivityForResult(Intent intent) {
+        intent.setComponent(
+                new ComponentName(ANDROID_CONTACTS_PACKAGE,
+                        MULTIPLE_PHONE_PICKER_ACTIVITY_CLASS_NAME));
+        startActivityForResult(intent, 13);
+    }
+
+    @Override
+    protected void onActivityResult(int requestCode, int resultCode, Intent data) {
+        Intent intent = new Intent(this, ResultActivity.class);
+        intent.putExtra("resultCode", resultCode);
+        intent.putExtra("data", data);
+        startActivity(intent);
+    }
+
+    private long findArbitraryContactWithPhoneNumber() {
+        Cursor cursor = getContentResolver().query(Contacts.CONTENT_URI,
+                new String[]{Contacts._ID},
+                Contacts.HAS_PHONE_NUMBER + "!=0", null, Contacts._ID + " LIMIT 1");
+        try {
+            if (cursor.moveToFirst()) {
+                return cursor.getLong(0);
+            }
+        } finally {
+            cursor.close();
+        }
+
+        return -1;
+    }
+}
diff --git a/tests/src/com/android/contacts/tests/allintents/ResultActivity.java b/tests/src/com/android/contacts/tests/allintents/ResultActivity.java
new file mode 100644
index 0000000..562f2ba
--- /dev/null
+++ b/tests/src/com/android/contacts/tests/allintents/ResultActivity.java
@@ -0,0 +1,196 @@
+/*
+ * 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.tests.allintents;
+
+import com.android.contacts.tests.R;
+
+import android.app.Activity;
+import android.content.ContentUris;
+import android.content.Intent;
+import android.database.Cursor;
+import android.graphics.Bitmap;
+import android.graphics.BitmapFactory;
+import android.graphics.Color;
+import android.net.Uri;
+import android.os.Bundle;
+import android.os.Parcelable;
+import android.provider.ContactsContract.Contacts;
+import android.provider.ContactsContract.Data;
+import android.provider.ContactsContract.CommonDataKinds.Photo;
+import android.text.TextUtils;
+import android.view.View;
+import android.widget.ImageView;
+import android.widget.TableLayout;
+import android.widget.TableRow;
+import android.widget.TextView;
+import android.widget.ImageView.ScaleType;
+
+import java.util.Arrays;
+
+/**
+ * An activity that shows the result of a contacts activity invocation.
+ */
+public class ResultActivity extends Activity {
+
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        setContentView(R.layout.result);
+
+        Intent intent = getIntent();
+        addRowsForIntent((Intent)intent.getExtras().get("data"));
+    }
+
+    private void addRowsForIntent(Intent intent) {
+        if (intent == null) {
+            addRow("", "No data intent returned");
+        } else {
+            addRow("INTENT", intent.toString());
+            addSeparator(3);
+
+            Bundle extras = intent.getExtras();
+            if (extras != null && !extras.isEmpty()) {
+                for (String key : extras.keySet()) {
+                    Object value = extras.get(key);
+                    addRow("EXTRA", key);
+                    addRowForValue("", value);
+                }
+
+                addSeparator(3);
+            }
+
+            String dataUri = intent.getDataString();
+            if (dataUri != null) {
+                addRowsForQuery(Uri.parse(dataUri));
+            }
+        }
+    }
+
+    private void addRowForValue(String label, Object value) {
+        if (value == null) {
+            addRow(label, "null");
+        } else if (value instanceof Bitmap) {
+            addRowWithBitmap(label, (Bitmap)value);
+        } else if (value instanceof Intent) {
+            addRow(label, "INTENT");
+            addRowsForIntent((Intent)value);
+        } else if (value instanceof Uri) {
+            addRow(label, "DATA");
+            addRowsForQuery((Uri)value);
+        } else if (value.getClass().isArray()) {
+            addRow(label, "ARRAY");
+            Parcelable[] array = (Parcelable[])value;
+            for (int i = 0; i < array.length; i++) {
+                addRowForValue("[" + i + "]", String.valueOf(array[i]));
+            }
+        } else {
+            addRow(label, String.valueOf(value));
+        }
+    }
+
+    private void addRowsForQuery(Uri dataUri) {
+        Cursor cursor = getContentResolver().query(dataUri, null, null, null, null);
+        if (cursor == null) {
+            addRow("", "No data for this URI");
+        } else {
+            try {
+                while (cursor.moveToNext()) {
+                    addRow("", "DATA");
+                    String[] columnNames = cursor.getColumnNames();
+                    String[] names = new String[columnNames.length];
+                    System.arraycopy(columnNames, 0, names, 0, columnNames.length);
+                    Arrays.sort(names);
+                    for (int i = 0; i < names.length; i++) {
+                        int index = cursor.getColumnIndex(names[i]);
+                        String value = cursor.getString(index);
+                        addRow(names[i], value);
+
+                        if (names[i].equals(Contacts.PHOTO_ID) && !TextUtils.isEmpty(value)) {
+                            addRowWithPhoto(Long.parseLong(value));
+                        }
+                    }
+                }
+            } finally {
+                cursor.close();
+            }
+        }
+    }
+
+    private void addRow(String column0, String column1) {
+        TextView label = new TextView(this);
+        label.setPadding(4, 4, 4, 4);
+        label.setText(column0);
+        TextView value = new TextView(this);
+        value.setPadding(4, 4, 4, 4);
+        value.setText(column1);
+        addRow(label, value);
+    }
+
+    private void addRowWithPhoto(long photoId) {
+        byte[] data = null;
+        Cursor cursor = getContentResolver().query(
+                ContentUris.withAppendedId(Data.CONTENT_URI, photoId),
+                new String[]{Photo.PHOTO}, null, null, null);
+        try {
+            if (cursor.moveToNext()) {
+                data = cursor.getBlob(0);
+            }
+        } finally {
+            if (cursor != null) {
+                cursor.close();
+            }
+        }
+
+        if (data == null) {
+            return;
+        }
+
+        addRowWithBitmap("Photo", BitmapFactory.decodeByteArray(data, 0, data.length));
+    }
+
+    private void addRowWithBitmap(String label, Bitmap bitmap) {
+        TextView labelView = new TextView(this);
+        labelView.setPadding(4, 4, 4, 4);
+        labelView.setText(label);
+
+        ImageView imageView = new ImageView(this);
+        imageView.setImageBitmap(bitmap);
+        imageView.setPadding(4, 4, 4, 4);
+        imageView.setScaleType(ScaleType.FIT_START);
+        addRow(labelView, imageView);
+    }
+
+    private void addRow(View column0, View column1) {
+        TableLayout table = (TableLayout)findViewById(R.id.table);
+        TableRow row = new TableRow(this);
+        row.addView(column0);
+        row.addView(column1);
+        table.addView(row);
+
+        addSeparator(1);
+    }
+
+    private void addSeparator(int height) {
+        TableLayout table = (TableLayout)findViewById(R.id.table);
+        View separator = new View(this);
+        TableLayout.LayoutParams params = new TableLayout.LayoutParams();
+        params.height = height;
+        separator.setLayoutParams(params);
+        separator.setBackgroundColor(Color.rgb(33, 66, 33));
+        table.addView(separator);
+    }
+}
diff --git a/tests/src/com/android/contacts/tests/mocks/ContactsMockContext.java b/tests/src/com/android/contacts/tests/mocks/ContactsMockContext.java
index bd2010e..4697b83 100644
--- a/tests/src/com/android/contacts/tests/mocks/ContactsMockContext.java
+++ b/tests/src/com/android/contacts/tests/mocks/ContactsMockContext.java
@@ -55,4 +55,9 @@
     public MockContentProvider getSettingsProvider() {
         return mSettingsProvider;
     }
+
+    @Override
+    public Context getApplicationContext() {
+        return this;
+    }
 }
diff --git a/tests/src/com/android/contacts/tests/mocks/MockContentProvider.java b/tests/src/com/android/contacts/tests/mocks/MockContentProvider.java
index 63b134a..00e1f03 100644
--- a/tests/src/com/android/contacts/tests/mocks/MockContentProvider.java
+++ b/tests/src/com/android/contacts/tests/mocks/MockContentProvider.java
@@ -104,7 +104,7 @@
             return true;
         }
 
-        private boolean equals(String[] array1, String[] array2) {
+        private static boolean equals(String[] array1, String[] array2) {
             boolean empty1 = array1 == null || array1.length == 0;
             boolean empty2 = array2 == null || array2.length == 0;
             if (empty1 && empty2) {
@@ -114,6 +114,8 @@
                 return false;
             }
 
+            if (array1.length != array2.length) return false;
+
             for (int i = 0; i < array1.length; i++) {
                 if (!array1[i].equals(array2[i])) {
                     return false;
@@ -193,7 +195,7 @@
         }
         if (selection != null) {
             sb.append(" selection: '").append(selection).append("'");
-            if (projection != null) {
+            if (selectionArgs != null) {
                 sb.append(Arrays.toString(selectionArgs));
             } else {
                 sb.append("[]");
@@ -204,4 +206,9 @@
         }
         return sb.toString();
     }
+
+    public void verify() {
+        Assert.assertTrue("Not all expected queries have been called: " +
+                mExpectedQueries, mExpectedQueries.isEmpty());
+    }
 }
diff --git a/tests/src/com/android/contacts/widget/CompositeListAdapterTest.java b/tests/src/com/android/contacts/widget/CompositeListAdapterTest.java
new file mode 100644
index 0000000..87d268b
--- /dev/null
+++ b/tests/src/com/android/contacts/widget/CompositeListAdapterTest.java
@@ -0,0 +1,324 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.contacts.widget;
+
+import com.google.android.collect.Lists;
+
+import android.content.Context;
+import android.database.DataSetObserver;
+import android.test.AndroidTestCase;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.ArrayAdapter;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+
+/**
+ * Tests for {@link CompositeListAdapter}.
+ */
+public class CompositeListAdapterTest extends AndroidTestCase {
+
+    private final class MockAdapter extends ArrayAdapter<String> {
+        boolean allItemsEnabled = true;
+        HashSet<Integer> enabledItems = new HashSet<Integer>();
+        int viewTypeCount = 1;
+        HashMap<Integer, Integer> viewTypes = new HashMap<Integer, Integer>();
+
+        private MockAdapter(Context context, List<String> objects) {
+            super(context, android.R.layout.simple_list_item_1, objects);
+            for (int i = 0; i < objects.size(); i++) {
+                viewTypes.put(i, 0);
+            }
+        }
+
+        @Override
+        public View getView(int position, View convertView, ViewGroup parent) {
+            return new MockView(getContext(), position);
+        }
+
+        @Override
+        public boolean areAllItemsEnabled() {
+            return allItemsEnabled;
+        }
+
+        @Override
+        public boolean isEnabled(int position) {
+            return enabledItems.contains(position);
+        }
+
+        @Override
+        public int getViewTypeCount() {
+            return viewTypeCount;
+        }
+
+        @Override
+        public int getItemViewType(int position) {
+            return viewTypes.get(position);
+        }
+    }
+
+    private final class MockView extends View {
+        public MockView(Context context, int position) {
+            super(context);
+            setTag(position);
+        }
+    }
+
+    private final class TestDataSetObserver extends DataSetObserver {
+
+        public int changeCount;
+        public int invalidationCount;
+
+        @Override
+        public void onChanged() {
+            changeCount++;
+        }
+
+        @Override
+        public void onInvalidated() {
+            invalidationCount++;
+        }
+    }
+
+    private MockAdapter mAdapter1;
+    private MockAdapter mAdapter2;
+    private MockAdapter mAdapter3;
+
+    @Override
+    protected void setUp() throws Exception {
+        super.setUp();
+        mAdapter1 = new MockAdapter(getContext(), Lists.newArrayList("A", "B"));
+        mAdapter2 = new MockAdapter(getContext(), new ArrayList<String>());
+        mAdapter3 = new MockAdapter(getContext(), Lists.newArrayList("C", "D", "E"));
+    }
+
+    public void testGetCount() {
+        CompositeListAdapter adapter = new CompositeListAdapter();
+        adapter.addAdapter(mAdapter1);
+        adapter.addAdapter(mAdapter2);
+        adapter.addAdapter(mAdapter3);
+
+        assertEquals(5, adapter.getCount());
+    }
+
+    public void testGetCountWithInvalidation() {
+        CompositeListAdapter adapter = new CompositeListAdapter();
+        assertEquals(0, adapter.getCount());
+
+        adapter.addAdapter(mAdapter1);
+        assertEquals(2, adapter.getCount());
+
+        adapter.addAdapter(mAdapter2);
+        assertEquals(2, adapter.getCount());
+
+        adapter.addAdapter(mAdapter3);
+        assertEquals(5, adapter.getCount());
+    }
+
+    public void testGetItem() {
+        CompositeListAdapter adapter = new CompositeListAdapter();
+        adapter.addAdapter(mAdapter1);
+        adapter.addAdapter(mAdapter2);
+        adapter.addAdapter(mAdapter3);
+
+        assertEquals("A", adapter.getItem(0));
+        assertEquals("B", adapter.getItem(1));
+        assertEquals("C", adapter.getItem(2));
+        assertEquals("D", adapter.getItem(3));
+        assertEquals("E", adapter.getItem(4));
+    }
+
+    public void testGetItemId() {
+        CompositeListAdapter adapter = new CompositeListAdapter();
+        adapter.addAdapter(mAdapter1);
+        adapter.addAdapter(mAdapter2);
+        adapter.addAdapter(mAdapter3);
+
+        assertEquals(0, adapter.getItemId(0));
+        assertEquals(1, adapter.getItemId(1));
+        assertEquals(0, adapter.getItemId(2));
+        assertEquals(1, adapter.getItemId(3));
+        assertEquals(2, adapter.getItemId(4));
+    }
+
+    public void testGetView() {
+        CompositeListAdapter adapter = new CompositeListAdapter();
+        adapter.addAdapter(mAdapter1);
+        adapter.addAdapter(mAdapter2);
+        adapter.addAdapter(mAdapter3);
+
+        assertEquals(0, adapter.getView(0, null, null).getTag());
+        assertEquals(1, adapter.getView(1, null, null).getTag());
+        assertEquals(0, adapter.getView(2, null, null).getTag());
+        assertEquals(1, adapter.getView(3, null, null).getTag());
+        assertEquals(2, adapter.getView(4, null, null).getTag());
+    }
+
+    public void testGetViewTypeCount() {
+        mAdapter1.viewTypeCount = 2;
+        mAdapter2.viewTypeCount = 3;
+        CompositeListAdapter adapter = new CompositeListAdapter();
+        adapter.addAdapter(mAdapter1);
+        adapter.addAdapter(mAdapter2);
+        adapter.addAdapter(mAdapter3);
+
+        // Note that mAdapter2 adds an implicit +1
+        assertEquals(6, adapter.getViewTypeCount());
+    }
+
+    public void testGetItemViewType() {
+        mAdapter1.viewTypeCount = 2;
+        mAdapter1.viewTypes.put(0, 1);
+        mAdapter1.viewTypes.put(1, 0);
+
+        mAdapter3.viewTypeCount = 3;
+        mAdapter3.viewTypes.put(0, 1);
+        mAdapter3.viewTypes.put(1, 2);
+        mAdapter3.viewTypes.put(2, 0);
+
+        CompositeListAdapter adapter = new CompositeListAdapter();
+        adapter.addAdapter(mAdapter1);
+        adapter.addAdapter(mAdapter2);
+        adapter.addAdapter(mAdapter3);
+
+        assertEquals(1, adapter.getItemViewType(0));
+        assertEquals(0, adapter.getItemViewType(1));
+
+        // Note: mAdapter2 throws in a +1
+
+        assertEquals(4, adapter.getItemViewType(2));
+        assertEquals(5, adapter.getItemViewType(3));
+        assertEquals(3, adapter.getItemViewType(4));
+    }
+
+    public void testNotifyDataSetChangedPropagated() {
+        CompositeListAdapter adapter = new CompositeListAdapter();
+        adapter.addAdapter(mAdapter1);
+        adapter.addAdapter(mAdapter2);
+
+        TestDataSetObserver observer = new TestDataSetObserver();
+        adapter.registerDataSetObserver(observer);
+        mAdapter1.add("X");
+
+        assertEquals(1, observer.changeCount);
+        assertEquals(0, observer.invalidationCount);
+        assertEquals(3, adapter.getCount());
+        assertEquals("A", adapter.getItem(0));
+        assertEquals("B", adapter.getItem(1));
+        assertEquals("X", adapter.getItem(2));
+
+        mAdapter2.add("Y");
+        assertEquals(2, observer.changeCount);
+        assertEquals(0, observer.invalidationCount);
+        assertEquals(4, adapter.getCount());
+        assertEquals("A", adapter.getItem(0));
+        assertEquals("B", adapter.getItem(1));
+        assertEquals("X", adapter.getItem(2));
+        assertEquals("Y", adapter.getItem(3));
+
+    }
+
+    public void testNotifyDataSetChangedOnAddingAdapter() {
+        CompositeListAdapter adapter = new CompositeListAdapter();
+        adapter.addAdapter(mAdapter1);
+
+        TestDataSetObserver observer = new TestDataSetObserver();
+        adapter.registerDataSetObserver(observer);
+        adapter.addAdapter(mAdapter3);
+
+        assertEquals(1, observer.changeCount);
+        assertEquals(0, observer.invalidationCount);
+        assertEquals(5, adapter.getCount());
+        assertEquals("A", adapter.getItem(0));
+        assertEquals("B", adapter.getItem(1));
+        assertEquals("C", adapter.getItem(2));
+        assertEquals("D", adapter.getItem(3));
+        assertEquals("E", adapter.getItem(4));
+    }
+
+    public void testNotifyDataSetInvalidated() {
+        CompositeListAdapter adapter = new CompositeListAdapter();
+        adapter.addAdapter(mAdapter1);
+
+        TestDataSetObserver observer = new TestDataSetObserver();
+        adapter.registerDataSetObserver(observer);
+
+        mAdapter1.remove("A");
+        assertEquals(1, observer.changeCount);
+        assertEquals(0, observer.invalidationCount);
+        assertEquals(1, adapter.getCount());
+
+        mAdapter1.remove("B");
+        assertEquals(1, observer.changeCount);
+        assertEquals(1, observer.invalidationCount);
+        assertEquals(0, adapter.getCount());
+    }
+
+    public void testAreAllItemsEnabled() {
+        CompositeListAdapter adapter = new CompositeListAdapter();
+        adapter.addAdapter(mAdapter1);
+        adapter.addAdapter(mAdapter3);
+
+        assertTrue(adapter.areAllItemsEnabled());
+    }
+
+    public void testAreAllItemsEnabledWithInvalidation() {
+        CompositeListAdapter adapter = new CompositeListAdapter();
+        adapter.addAdapter(mAdapter1);
+        assertTrue(adapter.areAllItemsEnabled());
+
+        mAdapter3.allItemsEnabled = false;
+        adapter.addAdapter(mAdapter3);
+
+        assertFalse(adapter.areAllItemsEnabled());
+    }
+
+    public void testIsEnabled() {
+        mAdapter1.allItemsEnabled = false;
+        mAdapter1.enabledItems.add(1);
+
+        CompositeListAdapter adapter = new CompositeListAdapter();
+        adapter.addAdapter(mAdapter1);
+        adapter.addAdapter(mAdapter2);
+        adapter.addAdapter(mAdapter3);
+
+        assertFalse(adapter.isEnabled(0));
+        assertTrue(adapter.isEnabled(1));
+        assertTrue(adapter.isEnabled(2));
+        assertTrue(adapter.isEnabled(3));
+        assertTrue(adapter.isEnabled(4));
+    }
+
+    public void testIsEnabledWhenAllEnabledAtLeastOneAdapter() {
+        mAdapter1.allItemsEnabled = false;
+        mAdapter1.enabledItems.add(1);
+        mAdapter3.allItemsEnabled = false;
+        mAdapter3.enabledItems.add(1);
+
+        CompositeListAdapter adapter = new CompositeListAdapter();
+        adapter.addAdapter(mAdapter1);
+        adapter.addAdapter(mAdapter3);
+
+        assertFalse(adapter.isEnabled(0));
+        assertTrue(adapter.isEnabled(1));
+        assertFalse(adapter.isEnabled(2));
+        assertTrue(adapter.isEnabled(3));
+        assertFalse(adapter.isEnabled(4));
+    }
+}