Initial cut of "Split Aggregate" UI.
diff --git a/src/com/android/contacts/ContactEntryAdapter.java b/src/com/android/contacts/ContactEntryAdapter.java
index bf7bf01..aac578d 100644
--- a/src/com/android/contacts/ContactEntryAdapter.java
+++ b/src/com/android/contacts/ContactEntryAdapter.java
@@ -34,39 +34,41 @@
     public static final String[] AGGREGATE_PROJECTION = new String[] {
         Aggregates.DISPLAY_NAME, // 0
         Aggregates.STARRED, //1
-        Data._ID, // 2
-        Data.PACKAGE, //3
-        Data.MIMETYPE, //4
-        Data.IS_PRIMARY, //5
-        Data.IS_SUPER_PRIMARY, //6
-        Data.DATA1, //7
-        Data.DATA2, //8
-        Data.DATA3, //9
-        Data.DATA4, //10
-        Data.DATA5, //11
-        Data.DATA6, //12
-        Data.DATA7, //13
-        Data.DATA8, //14
-        Data.DATA9, //15
-        Data.DATA10, //16
+        Data._ID, //2
+        Data.CONTACT_ID, //3
+        Data.PACKAGE, //4
+        Data.MIMETYPE, //5
+        Data.IS_PRIMARY, //6
+        Data.IS_SUPER_PRIMARY, //7
+        Data.DATA1, //8
+        Data.DATA2, //9
+        Data.DATA3, //10
+        Data.DATA4, //11
+        Data.DATA5, //12
+        Data.DATA6, //13
+        Data.DATA7, //14
+        Data.DATA8, //15
+        Data.DATA9, //16
+        Data.DATA10, //17
     };
     public static final int AGGREGATE_DISPLAY_NAME_COLUMN = 0;
     public static final int AGGREGATE_STARRED_COLUMN = 1;
     public static final int DATA_ID_COLUMN = 2;
-    public static final int DATA_PACKAGE_COLUMN = 3;
-    public static final int DATA_MIMETYPE_COLUMN = 4;
-    public static final int DATA_IS_PRIMARY_COLUMN = 5;
-    public static final int DATA_IS_SUPER_PRIMARY_COLUMN = 6;
-    public static final int DATA_1_COLUMN = 7;
-    public static final int DATA_2_COLUMN = 8;
-    public static final int DATA_3_COLUMN = 9;
-    public static final int DATA_4_COLUMN = 10;
-    public static final int DATA_5_COLUMN = 11;
-    public static final int DATA_6_COLUMN = 12;
-    public static final int DATA_7_COLUMN = 13;
-    public static final int DATA_8_COLUMN = 14;
-    public static final int DATA_9_COLUMN = 15;
-    public static final int DATA_10_COLUMN = 16;
+    public static final int DATA_CONTACT_ID_COLUMN = 3;
+    public static final int DATA_PACKAGE_COLUMN = 4;
+    public static final int DATA_MIMETYPE_COLUMN = 5;
+    public static final int DATA_IS_PRIMARY_COLUMN = 6;
+    public static final int DATA_IS_SUPER_PRIMARY_COLUMN = 7;
+    public static final int DATA_1_COLUMN = 8;
+    public static final int DATA_2_COLUMN = 9;
+    public static final int DATA_3_COLUMN = 10;
+    public static final int DATA_4_COLUMN = 11;
+    public static final int DATA_5_COLUMN = 12;
+    public static final int DATA_6_COLUMN = 13;
+    public static final int DATA_7_COLUMN = 14;
+    public static final int DATA_8_COLUMN = 15;
+    public static final int DATA_9_COLUMN = 16;
+    public static final int DATA_10_COLUMN = 17;
 
     protected ArrayList<ArrayList<E>> mSections;
     protected LayoutInflater mInflater;
@@ -81,6 +83,7 @@
         public String data;
         public Uri uri;
         public long id = 0;
+        public long contactId;
         public int maxLines = 1;
         public String mimetype;
 
diff --git a/src/com/android/contacts/ContactsListActivity.java b/src/com/android/contacts/ContactsListActivity.java
index f6d714a..f104781 100644
--- a/src/com/android/contacts/ContactsListActivity.java
+++ b/src/com/android/contacts/ContactsListActivity.java
@@ -106,6 +106,7 @@
     public static final int MENU_DISPLAY_GROUP = 11;
 
     private static final int SUBACTIVITY_NEW_CONTACT = 1;
+    private static final int SUBACTIVITY_VIEW_CONTACT = 2;
 
     /** Mask for picker mode */
     static final int MODE_MASK_PICKER = 0x80000000;
@@ -798,6 +799,13 @@
                     returnPickerResult(data.getStringExtra(Intent.EXTRA_SHORTCUT_NAME),
                             data.getData());
                 }
+                break;
+
+            case SUBACTIVITY_VIEW_CONTACT:
+                if (resultCode == RESULT_OK) {
+                    mAdapter.notifyDataSetChanged();
+                }
+                break;
         }
     }
 
@@ -971,7 +979,7 @@
             if ((mMode & MODE_MASK_PICKER) == 0) {
                 Intent intent = new Intent(Intent.ACTION_VIEW,
                         ContentUris.withAppendedId(Aggregates.CONTENT_URI, id));
-                startActivity(intent);
+                startActivityForResult(intent, SUBACTIVITY_VIEW_CONTACT);
             } /*else if (mMode == MODE_QUERY_PICK_TO_VIEW) {
                 // Started with query that should launch to view contact
                 Cursor c = (Cursor) mAdapter.getItem(position);
diff --git a/src/com/android/contacts/SplitAggregateView.java b/src/com/android/contacts/SplitAggregateView.java
new file mode 100644
index 0000000..8d20b6c
--- /dev/null
+++ b/src/com/android/contacts/SplitAggregateView.java
@@ -0,0 +1,273 @@
+/*
+ * 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.
+ */
+
+package com.android.contacts;
+
+import com.google.common.util.text.TextUtil;
+
+import android.content.Context;
+import android.database.Cursor;
+import android.graphics.Bitmap;
+import android.net.Uri;
+import android.provider.ContactsContract.Aggregates.Data;
+import android.provider.ContactsContract.CommonDataKinds.Email;
+import android.provider.ContactsContract.CommonDataKinds.Nickname;
+import android.provider.ContactsContract.CommonDataKinds.Phone;
+import android.provider.ContactsContract.CommonDataKinds.StructuredName;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.AdapterView;
+import android.widget.ArrayAdapter;
+import android.widget.ImageView;
+import android.widget.ListView;
+import android.widget.TextView;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.List;
+
+/**
+ * A list view for constituent contacts of an aggregate.  Shows the contact name, source icon
+ * and additional data such as a nickname, email address or phone number, whichever
+ * is available.
+ */
+public class SplitAggregateView extends ListView {
+
+    private static final String[] AGGREGATE_DATA_PROJECTION = new String[] {
+            Data.MIMETYPE, Data.PACKAGE, Data.CONTACT_ID, Data.DATA1, Data.DATA2, Data.IS_PRIMARY,
+            StructuredName.DISPLAY_NAME
+    };
+
+    private static final int COL_MIMETYPE = 0;
+    private static final int COL_PACKAGE = 1;
+    private static final int COL_CONTACT_ID = 2;
+    private static final int COL_DATA1 = 3;
+    private static final int COL_DATA2 = 4;
+    private static final int COL_IS_PRIMARY = 5;
+    private static final int COL_DISPLAY_NAME = 6;
+
+
+    private final Uri mAggregateUri;
+    private OnContactSelectedListener mListener;
+
+    /**
+     * Listener interface that gets the contact ID of the user-selected contact.
+     */
+    public interface OnContactSelectedListener {
+        void onContactSelected(long contactId);
+    }
+
+    /**
+     * Constructor.
+     */
+    public SplitAggregateView(Context context, Uri aggregateUri) {
+        super(context);
+
+        mAggregateUri = aggregateUri;
+
+        final List<ContactInfo> list = loadData();
+
+        setAdapter(new SplitAggregateAdapter(context, list));
+        setOnItemClickListener(new OnItemClickListener() {
+
+            public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
+                mListener.onContactSelected(list.get(position).contactId);
+            }
+        });
+    }
+
+    /**
+     * Sets a contact selection listener.
+     */
+    public void setOnContactSelectedListener(OnContactSelectedListener listener) {
+        mListener = listener;
+    }
+
+    /**
+     * Contact information loaded from the content provider.
+     */
+    private static class ContactInfo implements Comparable<ContactInfo> {
+        final long contactId;
+        String packageName;
+        String name;
+        String phone;
+        String email;
+        String nickname;
+
+        public ContactInfo(long contactId) {
+            this.contactId = contactId;
+        }
+
+        public String getAdditionalData() {
+            if (nickname != null) {
+                return nickname;
+            }
+
+            if (email != null) {
+                return email;
+            }
+
+            if (phone != null) {
+                return phone;
+            }
+
+            return "";
+        }
+
+        public int compareTo(ContactInfo another) {
+            return packageName.compareTo(another.packageName);
+        }
+    }
+
+    /**
+     * Loads data from the content provider, organizes it into {@link ContactInfo} objects
+     * and returns a sorted list of {@link ContactInfo}'s.
+     */
+    private List<ContactInfo> loadData() {
+        HashMap<Long, ContactInfo> contactInfos = new HashMap<Long, ContactInfo>();
+        Uri dataUri = Uri.withAppendedPath(mAggregateUri, Data.CONTENT_DIRECTORY);
+        Cursor cursor = getContext().getContentResolver().query(dataUri,
+                AGGREGATE_DATA_PROJECTION, null, null, null);
+        try {
+            while (cursor.moveToNext()) {
+                long contactId = cursor.getLong(COL_CONTACT_ID);
+                ContactInfo info = contactInfos.get(contactId);
+                if (info == null) {
+                    info = new ContactInfo(contactId);
+                    contactInfos.put(contactId, info);
+                    info.packageName = cursor.getString(COL_PACKAGE);
+                }
+
+                String mimetype = cursor.getString(COL_MIMETYPE);
+                if (StructuredName.CONTENT_ITEM_TYPE.equals(mimetype)) {
+                    loadStructuredName(cursor, info);
+                } else if (Phone.CONTENT_ITEM_TYPE.equals(mimetype)) {
+                    loadPhoneNumber(cursor, info);
+                } else if (Email.CONTENT_ITEM_TYPE.equals(mimetype)) {
+                    loadEmail(cursor, info);
+                } else if (Nickname.CONTENT_ITEM_TYPE.equals(mimetype)) {
+                    loadNickname(cursor, info);
+                }
+            }
+        } finally {
+            cursor.close();
+        }
+
+        List<ContactInfo> list = new ArrayList<ContactInfo>(contactInfos.values());
+        Collections.sort(list);
+        return list;
+    }
+
+    private void loadStructuredName(Cursor cursor, ContactInfo info) {
+        info.name = cursor.getString(COL_DISPLAY_NAME);
+        if (info.name != null) {
+            return;
+        }
+
+        StringBuilder sb = new StringBuilder();
+        String firstName = cursor.getString(COL_DATA1);
+        String lastName = cursor.getString(COL_DATA2);
+        if (!TextUtil.isEmpty(firstName)) {
+            sb.append(firstName);
+        }
+        if  (!TextUtil.isEmpty(firstName) && !TextUtil.isEmpty(lastName)) {
+            sb.append(" ");
+        }
+        if (!TextUtil.isEmpty(lastName)) {
+            sb.append(lastName);
+        }
+
+        if (sb.length() != 0) {
+            info.name = sb.toString();
+        }
+    }
+
+    private void loadNickname(Cursor cursor, ContactInfo info) {
+        if (info.nickname == null || cursor.getInt(COL_IS_PRIMARY) != 0) {
+            info.nickname = cursor.getString(COL_DATA2);
+        }
+    }
+
+    private void loadEmail(Cursor cursor, ContactInfo info) {
+        if (info.email == null || cursor.getInt(COL_IS_PRIMARY) != 0) {
+            info.email = cursor.getString(COL_DATA2);
+        }
+    }
+
+    private void loadPhoneNumber(Cursor cursor, ContactInfo info) {
+        if (info.phone == null || cursor.getInt(COL_IS_PRIMARY) != 0) {
+            info.phone = cursor.getString(COL_DATA2);
+        }
+    }
+
+    /**
+     * List adapter for the list of {@link ContactInfo} objects.
+     */
+    private static class SplitAggregateAdapter extends ArrayAdapter<ContactInfo> {
+
+        private static class SplitAggregateItemCache  {
+            TextView name;
+            TextView additionalData;
+            ImageView sourceIcon;
+        }
+
+        private LayoutInflater mInflater;
+
+        public SplitAggregateAdapter(Context context, List<ContactInfo> sources) {
+            super(context, 0, sources);
+            mInflater = (LayoutInflater)context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
+        }
+
+        @Override
+        public View getView(int position, View convertView, ViewGroup parent) {
+            if (convertView == null) {
+                convertView = mInflater.inflate(R.layout.split_aggregate_list_item, parent, false);
+            }
+
+            SplitAggregateItemCache cache = (SplitAggregateItemCache)convertView.getTag();
+            if (cache == null) {
+                cache = new SplitAggregateItemCache();
+                cache.name = (TextView)convertView.findViewById(R.id.name);
+                cache.additionalData = (TextView)convertView.findViewById(R.id.additionalData);
+                cache.sourceIcon = (ImageView)convertView.findViewById(R.id.sourceIcon);
+                convertView.setTag(cache);
+            }
+
+            final ContactInfo info = getItem(position);
+            cache.name.setText(info.name);
+            cache.additionalData.setText(info.getAdditionalData());
+
+            Bitmap sourceBitmap = getSourceIcon(info.packageName);
+            if (sourceBitmap != null) {
+                cache.sourceIcon.setImageBitmap(sourceBitmap);
+            } else {
+                cache.sourceIcon.setImageResource(R.drawable.unknown_source);
+            }
+            return convertView;
+        }
+
+        /**
+         * Returns the icon corresponding to the source of contact information.
+         */
+        private Bitmap getSourceIcon(String packageName) {
+
+            // TODO: obtain from PackageManager
+            return null;
+        }
+    }
+}
diff --git a/src/com/android/contacts/ViewContactActivity.java b/src/com/android/contacts/ViewContactActivity.java
index 9dc898f..3b80da5 100644
--- a/src/com/android/contacts/ViewContactActivity.java
+++ b/src/com/android/contacts/ViewContactActivity.java
@@ -16,25 +16,20 @@
 
 package com.android.contacts;
 
-import static com.android.contacts.ContactEntryAdapter.AGGREGATE_DISPLAY_NAME_COLUMN;
 import static com.android.contacts.ContactEntryAdapter.AGGREGATE_PROJECTION;
 import static com.android.contacts.ContactEntryAdapter.AGGREGATE_STARRED_COLUMN;
-import static com.android.contacts.ContactEntryAdapter.DATA_ID_COLUMN;
-import static com.android.contacts.ContactEntryAdapter.DATA_PACKAGE_COLUMN;
-import static com.android.contacts.ContactEntryAdapter.DATA_MIMETYPE_COLUMN;
-import static com.android.contacts.ContactEntryAdapter.DATA_IS_PRIMARY_COLUMN;
-import static com.android.contacts.ContactEntryAdapter.DATA_IS_SUPER_PRIMARY_COLUMN;
 import static com.android.contacts.ContactEntryAdapter.DATA_1_COLUMN;
 import static com.android.contacts.ContactEntryAdapter.DATA_2_COLUMN;
 import static com.android.contacts.ContactEntryAdapter.DATA_3_COLUMN;
 import static com.android.contacts.ContactEntryAdapter.DATA_4_COLUMN;
 import static com.android.contacts.ContactEntryAdapter.DATA_5_COLUMN;
-import static com.android.contacts.ContactEntryAdapter.DATA_6_COLUMN;
-import static com.android.contacts.ContactEntryAdapter.DATA_7_COLUMN;
-import static com.android.contacts.ContactEntryAdapter.DATA_8_COLUMN;
 import static com.android.contacts.ContactEntryAdapter.DATA_9_COLUMN;
-import static com.android.contacts.ContactEntryAdapter.DATA_10_COLUMN;
+import static com.android.contacts.ContactEntryAdapter.DATA_CONTACT_ID_COLUMN;
+import static com.android.contacts.ContactEntryAdapter.DATA_ID_COLUMN;
+import static com.android.contacts.ContactEntryAdapter.DATA_IS_SUPER_PRIMARY_COLUMN;
+import static com.android.contacts.ContactEntryAdapter.DATA_MIMETYPE_COLUMN;
 
+import com.android.contacts.SplitAggregateView.OnContactSelectedListener;
 import com.android.internal.telephony.ITelephony;
 
 import android.app.AlertDialog;
@@ -47,13 +42,13 @@
 import android.content.Context;
 import android.content.DialogInterface;
 import android.content.Intent;
+import android.content.DialogInterface.OnClickListener;
 import android.content.pm.PackageManager;
 import android.content.pm.ResolveInfo;
 import android.content.res.Resources;
 import android.database.ContentObserver;
 import android.database.Cursor;
 import android.graphics.Bitmap;
-import android.graphics.BitmapFactory;
 import android.graphics.drawable.Drawable;
 import android.media.Ringtone;
 import android.media.RingtoneManager;
@@ -63,14 +58,14 @@
 import android.os.RemoteException;
 import android.os.ServiceManager;
 import android.os.SystemClock;
-import android.provider.ContactsContract.CommonDataKinds;
 import android.provider.ContactsContract.Aggregates;
-import android.provider.ContactsContract.Contacts;
+import android.provider.ContactsContract.AggregationExceptions;
+import android.provider.ContactsContract.CommonDataKinds;
 import android.provider.ContactsContract.Data;
-import android.provider.Im;
 import android.text.TextUtils;
 import android.util.Log;
 import android.view.ContextMenu;
+import android.view.ContextThemeWrapper;
 import android.view.KeyEvent;
 import android.view.Menu;
 import android.view.MenuItem;
@@ -85,7 +80,6 @@
 import android.widget.Toast;
 
 import java.util.ArrayList;
-import java.util.List;
 
 /**
  * Displays the details of a specific contact.
@@ -103,6 +97,12 @@
     public static final int MENU_ITEM_DELETE = 1;
     public static final int MENU_ITEM_MAKE_DEFAULT = 2;
     public static final int MENU_ITEM_SHOW_BARCODE = 3;
+    public static final int MENU_ITEM_SPLIT_AGGREGATE = 4;
+
+    private static final String[] AGGREGATION_EXCEPTIONS_PROJECTION =
+            new String[] { AggregationExceptions._ID};
+
+    private static final int AGGREGATION_EXCEPTIONS_COL_ID = 0;
 
     private Uri mUri;
     private Uri mAggDataUri;
@@ -110,6 +110,11 @@
     private ViewAdapter mAdapter;
     private int mNumPhoneNumbers = 0;
 
+    /**
+     * A list of distinct contact IDs included in the current aggregate.
+     */
+    private ArrayList<Long> mContactIds = new ArrayList<Long>();
+
     /* package */ ArrayList<ViewEntry> mPhoneEntries = new ArrayList<ViewEntry>();
     /* package */ ArrayList<ViewEntry> mSmsEntries = new ArrayList<ViewEntry>();
     /* package */ ArrayList<ViewEntry> mEmailEntries = new ArrayList<ViewEntry>();
@@ -300,7 +305,8 @@
                 .setAlphabeticShortcut('e');
         menu.add(0, MENU_ITEM_DELETE, 0, R.string.menu_deleteContact)
                 .setIcon(android.R.drawable.ic_menu_delete);
-
+        menu.add(0, MENU_ITEM_SPLIT_AGGREGATE, 0, R.string.menu_splitAggregate)
+                .setIcon(android.R.drawable.ic_menu_share);
         return true;
     }
 
@@ -317,6 +323,9 @@
         } else {
             menu.removeItem(MENU_ITEM_SHOW_BARCODE);
         }
+
+        boolean isAggregate = mContactIds.size() > 1;
+        menu.findItem(MENU_ITEM_SPLIT_AGGREGATE).setEnabled(isAggregate);
         return true;
     }
 
@@ -373,6 +382,11 @@
                 return true;
             }
 
+            case MENU_ITEM_SPLIT_AGGREGATE: {
+                showSplitAggregateDialog();
+                return true;
+            }
+
             // TODO(emillar) Bring this back.
             /*case MENU_ITEM_SHOW_BARCODE:
                 if (mCursor.moveToFirst()) {
@@ -423,32 +437,131 @@
     public boolean onContextItemSelected(MenuItem item) {
         switch (item.getItemId()) {
             case MENU_ITEM_MAKE_DEFAULT: {
-                AdapterView.AdapterContextMenuInfo info;
-                try {
-                     info = (AdapterView.AdapterContextMenuInfo) item.getMenuInfo();
-                } catch (ClassCastException e) {
-                    Log.e(TAG, "bad menuInfo", e);
-                    break;
+                if (makeItemDefault(item)) {
+                    return true;
                 }
-
-                ViewEntry entry = ContactEntryAdapter.getEntry(mSections, info.position,
-                        SHOW_SEPARATORS);
-
-                // Update the primary values in the data record.
-                ContentValues values = new ContentValues(1);
-                values.put(Data.IS_PRIMARY, 1);
-                values.put(Data.IS_SUPER_PRIMARY, 1);
-
-                Log.i(TAG, mUri.toString());
-                getContentResolver().update(ContentUris.withAppendedId(Data.CONTENT_URI, entry.id),
-                        values, null, null);
-                dataChanged();
-                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(2);
+        values.put(Data.IS_PRIMARY, 1);
+        values.put(Data.IS_SUPER_PRIMARY, 1);
+
+        Log.i(TAG, mUri.toString());
+        getContentResolver().update(ContentUris.withAppendedId(Data.CONTENT_URI, entry.id),
+                values, null, null);
+        dataChanged();
+        return true;
+    }
+
+    /**
+     * Shows a dialog that contains a list of all constituent contacts in this aggregate.
+     * The user picks a contact to be split into its own aggregate or clicks Cancel.
+     */
+    private void showSplitAggregateDialog() {
+
+        // Wrap this dialog in a specific theme so that list items have correct text color.
+        final ContextThemeWrapper dialogContext =
+                new ContextThemeWrapper(this, android.R.style.Theme_Light);
+        AlertDialog.Builder builder =
+                new AlertDialog.Builder(dialogContext);
+        builder.setTitle(getString(R.string.splitAggregate_title));
+
+        final SplitAggregateView view = new SplitAggregateView(dialogContext, mUri);
+        builder.setView(view);
+
+        builder.setInverseBackgroundForced(true);
+        builder.setCancelable(true);
+        builder.setNegativeButton(android.R.string.cancel,
+                new OnClickListener() {
+                    public void onClick(DialogInterface dialog, int which) {
+                        dialog.dismiss();
+                    }
+                });
+        final AlertDialog dialog = builder.create();
+
+        view.setOnContactSelectedListener(new OnContactSelectedListener() {
+            public void onContactSelected(long contactId) {
+                dialog.dismiss();
+                splitContact(contactId);
+            }
+        });
+
+        dialog.show();
+    }
+
+    /**
+     * Given an ID of a constituent contact, splits it off into a separate aggregate.
+     */
+    protected void splitContact(long contactToSplit) {
+      long contactToKeep;
+
+      // We are guaranteed to have at least two items in the mContactIds array, because
+      // otherwise the "Split" menu item would be disabled.
+      if (mContactIds.get(0) != contactToSplit) {
+          contactToKeep = mContactIds.get(0);
+      } else {
+          contactToKeep = mContactIds.get(1);
+      }
+
+      ContentValues values = new ContentValues(3);
+      values.put(AggregationExceptions.TYPE, AggregationExceptions.TYPE_NEVER_MATCH);
+
+      boolean updated = false;
+
+      // First see if there is an existing exception for this pair of contacts
+      Cursor c = mResolver.query(AggregationExceptions.CONTENT_URI,
+              AGGREGATION_EXCEPTIONS_PROJECTION,
+              "(" + AggregationExceptions.CONTACT_ID1 + "=" + contactToSplit
+              + " AND " + AggregationExceptions.CONTACT_ID2 + "=" + contactToKeep
+              + ") OR (" + AggregationExceptions.CONTACT_ID1 + "=" + contactToKeep
+              + " AND " + AggregationExceptions.CONTACT_ID2 + "=" + contactToSplit + ")",
+              null, null);
+
+      try {
+          if (c.moveToFirst()) {
+              long exceptionId = c.getLong(AGGREGATION_EXCEPTIONS_COL_ID);
+              mResolver.update(ContentUris.withAppendedId(AggregationExceptions.CONTENT_URI,
+                      exceptionId), values, null, null);
+              updated = true;
+          }
+      } finally {
+          c.close();
+      }
+
+      // Otherwise, insert a new exception
+      if (!updated) {
+          values.put(AggregationExceptions.CONTACT_ID1, contactToSplit);
+          values.put(AggregationExceptions.CONTACT_ID2, contactToKeep);
+          mResolver.insert(AggregationExceptions.CONTENT_URI, values);
+      }
+
+      mAdapter.notifyDataSetChanged();
+    }
+
+    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) {
@@ -535,6 +648,8 @@
             mSections.get(i).clear();
         }
 
+        mContactIds.clear();
+
         // Build up method entries
         if (mUri != null) {
             Bitmap photoBitmap = null;
@@ -548,6 +663,10 @@
                 entry.id = id;
                 entry.uri = uri;
                 entry.mimetype = mimetype;
+                entry.contactId = aggCursor.getLong(DATA_CONTACT_ID_COLUMN);
+                if (!mContactIds.contains(entry.contactId)) {
+                    mContactIds.add(entry.contactId);
+                }
 
                 if (mimetype.equals(CommonDataKinds.Phone.CONTENT_ITEM_TYPE)
                         || mimetype.equals(CommonDataKinds.Email.CONTENT_ITEM_TYPE)