Initial cut of "Split Aggregate" UI.
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;
+        }
+    }
+}