Modal field editors

Bug:2680726
Change-Id: I167b02c50653abfa22ed72023993c9dd31f752a9
diff --git a/src/com/android/contacts/Collapser.java b/src/com/android/contacts/Collapser.java
index 3872dfd..d072dce 100644
--- a/src/com/android/contacts/Collapser.java
+++ b/src/com/android/contacts/Collapser.java
@@ -16,7 +16,6 @@
 
 package com.android.contacts;
 
-import java.util.HashMap;
 import java.util.Iterator;
 import java.util.ArrayList;
 
@@ -44,7 +43,7 @@
 
     /**
      * Collapses a list of Collapsible items into a list of collapsed items. Items are collapsed
-     * if {@link Collapsible#shouldCollapseWith(Object) return strue, and are collapsed
+     * if {@link Collapsible#shouldCollapseWith(Object)} returns strue, and are collapsed
      * through the {@Link Collapsible#collapseWith(Object)} function implemented by the data item.
      *
      * @param list ArrayList of Objects of type <T extends Collapsible<T>> to be collapsed.
diff --git a/src/com/android/contacts/ContactEntryAdapter.java b/src/com/android/contacts/ContactEntryAdapter.java
deleted file mode 100644
index 90a41ca..0000000
--- a/src/com/android/contacts/ContactEntryAdapter.java
+++ /dev/null
@@ -1,263 +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 android.content.Context;
-import android.net.Uri;
-import android.os.Parcel;
-import android.provider.ContactsContract.Contacts;
-import android.provider.ContactsContract.Data;
-import android.view.LayoutInflater;
-import android.view.View;
-import android.view.ViewGroup;
-import android.widget.BaseAdapter;
-
-import java.util.ArrayList;
-
-public abstract class ContactEntryAdapter<E extends ContactEntryAdapter.Entry>
-        extends BaseAdapter {
-
-    protected ArrayList<ArrayList<E>> mSections;
-    protected LayoutInflater mInflater;
-    protected Context mContext;
-    protected boolean mSeparators;
-
-    /**
-     * Base class for adapter entries.
-     */
-    public static class Entry {
-        public int type = -1;
-        public String label;
-        public String data;
-        public Uri uri;
-        public long id = 0;
-        public long contactId;
-        public int maxLines = 1;
-        public String mimetype;
-
-        /**
-         * Helper for making subclasses parcelable.
-         */
-        protected void writeToParcel(Parcel p) {
-            p.writeInt(type);
-            p.writeString(label);
-            p.writeString(data);
-            p.writeParcelable(uri, 0);
-            p.writeLong(id);
-            p.writeInt(maxLines);
-            p.writeString(mimetype);
-        }
-
-        /**
-         * Helper for making subclasses parcelable.
-         */
-        protected void readFromParcel(Parcel p) {
-            final ClassLoader loader = getClass().getClassLoader();
-            type = p.readInt();
-            label = p.readString();
-            data = p.readString();
-            uri = p.readParcelable(loader);
-            id = p.readLong();
-            maxLines = p.readInt();
-            mimetype = p.readString();
-        }
-    }
-
-    protected ContactEntryAdapter(Context context, ArrayList<ArrayList<E>> sections,
-            boolean separators) {
-        mContext = context;
-        mInflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
-        mSections = sections;
-        mSeparators = separators;
-    }
-
-    /**
-     * Resets the section data.
-     *
-     * @param sections the section data
-     */
-    public final void setSections(ArrayList<ArrayList<E>> sections, boolean separators) {
-        mSections = sections;
-        mSeparators = separators;
-        notifyDataSetChanged();
-    }
-
-    /**
-     * Resets the section data and returns the position of the given entry.
-     *
-     * @param sections the section data
-     * @param entry the entry to return the position for
-     * @return the position of entry, or -1 if it isn't found
-     */
-    public final int setSections(ArrayList<ArrayList<E>> sections, E entry) {
-        mSections = sections;
-        notifyDataSetChanged();
-
-        int numSections = mSections.size();
-        int position = 0;
-        for (int i = 0; i < numSections; i++) {
-            ArrayList<E> section = mSections.get(i);
-            int sectionSize = section.size();
-            for (int j = 0; j < sectionSize; j++) {
-                E e = section.get(j);
-                if (e.equals(entry)) {
-                    position += j;
-                    return position;
-                }
-            }
-            position += sectionSize;
-        }
-        return -1;
-    }
-
-    /**
-     * @see android.widget.ListAdapter#getCount()
-     */
-    public final int getCount() {
-        return countEntries(mSections, mSeparators);
-    }
-
-    /**
-     * @see android.widget.ListAdapter#hasSeparators()
-     */
-    @Override
-    public final boolean areAllItemsEnabled() {
-        return mSeparators == false;
-    }
-
-    /**
-     * @see android.widget.ListAdapter#isSeparator(int)
-     */
-    @Override
-    public final boolean isEnabled(int position) {
-        if (!mSeparators) {
-            return true;
-        }
-
-        int numSections = mSections.size();
-        for (int i = 0; i < numSections; i++) {
-            ArrayList<E> section = mSections.get(i);
-            int sectionSize = section.size();
-            if (sectionSize == 1) {
-                // The section only contains a separator and nothing else, skip it
-                continue;
-            }
-            if (position == 0) {
-                // The first item in a section is always the separator
-                return false;
-            }
-            position -= sectionSize;
-        }
-        return true;
-    }
-
-    /**
-     * @see android.widget.ListAdapter#getItem(int)
-     */
-    public final Object getItem(int position) {
-        return getEntry(mSections, position, mSeparators);
-    }
-
-    /**
-     * Get the entry for the given position.
-     *
-     * @param sections the list of sections
-     * @param position the position for the desired entry
-     * @return the ContactEntry for the given position
-     */
-    public final static <T extends Entry> T getEntry(ArrayList<ArrayList<T>> sections,
-            int position, boolean separators) {
-        int numSections = sections.size();
-        for (int i = 0; i < numSections; i++) {
-            ArrayList<T> section = sections.get(i);
-            int sectionSize = section.size();
-            if (separators && sectionSize == 1) {
-                // The section only contains a separator and nothing else, skip it
-                continue;
-            }
-            if (position < section.size()) {
-                return section.get(position);
-            }
-            position -= section.size();
-        }
-        return null;
-    }
-
-    /**
-     * Get the count of entries in all sections
-     *
-     * @param sections the list of sections
-     * @return the count of entries in all sections
-     */
-    public static <T extends Entry> int countEntries(ArrayList<ArrayList<T>> sections,
-            boolean separators) {
-        int count = 0;
-        int numSections = sections.size();
-        for (int i = 0; i < numSections; i++) {
-            ArrayList<T> section = sections.get(i);
-            int sectionSize = section.size();
-            if (separators && sectionSize == 1) {
-                // The section only contains a separator and nothing else, skip it
-                continue;
-            }
-            count += sections.get(i).size();
-        }
-        return count;
-    }
-
-    /**
-     * @see android.widget.ListAdapter#getItemId(int)
-     */
-    public final long getItemId(int position) {
-        Entry entry = getEntry(mSections, position, mSeparators);
-        if (entry != null) {
-            return entry.id;
-        } else {
-            return -1;
-        }
-    }
-
-    /**
-     * @see android.widget.ListAdapter#getView(int, View, ViewGroup)
-     */
-    public View getView(int position, View convertView, ViewGroup parent) {
-        View v;
-        if (convertView == null) {
-            v = newView(position, parent);
-        } else {
-            v = convertView;
-        }
-        bindView(v, getEntry(mSections, position, mSeparators));
-        return v;
-    }
-
-    /**
-     * Create a new view for an entry.
-     *
-     * @parent the parent ViewGroup
-     * @return the newly created view
-     */
-    protected abstract View newView(int position, ViewGroup parent);
-
-    /**
-     * Binds the data from an entry to a view.
-     *
-     * @param view the view to display the entry in
-     * @param entry the data to bind
-     */
-    protected abstract void bindView(View view, E entry);
-}
diff --git a/src/com/android/contacts/ScrollingTabWidget.java b/src/com/android/contacts/ScrollingTabWidget.java
deleted file mode 100644
index b45abe4..0000000
--- a/src/com/android/contacts/ScrollingTabWidget.java
+++ /dev/null
@@ -1,418 +0,0 @@
-/*
- * Copyright (C) 2009 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * 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 android.content.Context;
-import android.graphics.Canvas;
-import android.util.AttributeSet;
-import android.view.LayoutInflater;
-import android.view.View;
-import android.view.ViewGroup;
-import android.view.ViewTreeObserver;
-import android.view.View.OnClickListener;
-import android.view.View.OnFocusChangeListener;
-import android.widget.HorizontalScrollView;
-import android.widget.ImageView;
-import android.widget.RelativeLayout;
-
-/*
- * Tab widget that can contain more tabs than can fit on screen at once and scroll over them.
- */
-public class ScrollingTabWidget extends RelativeLayout
-        implements OnClickListener, ViewTreeObserver.OnGlobalFocusChangeListener,
-        OnFocusChangeListener {
-
-    private static final String TAG = "ScrollingTabWidget";
-
-    private OnTabSelectionChangedListener mSelectionChangedListener;
-    private int mSelectedTab = 0;
-    private ImageView mLeftArrowView;
-    private ImageView mRightArrowView;
-    private HorizontalScrollView mTabsScrollWrapper;
-    private TabStripView mTabsView;
-    private LayoutInflater mInflater;
-
-    // Keeps track of the left most visible tab.
-    private int mLeftMostVisibleTabIndex = 0;
-
-    public ScrollingTabWidget(Context context) {
-        this(context, null);
-    }
-
-    public ScrollingTabWidget(Context context, AttributeSet attrs) {
-        this(context, attrs, 0);
-    }
-
-    public ScrollingTabWidget(Context context, AttributeSet attrs, int defStyle) {
-        super(context, attrs);
-
-        mInflater = (LayoutInflater) mContext.getSystemService(
-                Context.LAYOUT_INFLATER_SERVICE);
-
-        setFocusable(true);
-        setOnFocusChangeListener(this);
-        if (!hasFocus()) {
-            setDescendantFocusability(FOCUS_BLOCK_DESCENDANTS);
-        }
-
-        mLeftArrowView = (ImageView) mInflater.inflate(R.layout.tab_left_arrow, this, false);
-        mLeftArrowView.setOnClickListener(this);
-        mRightArrowView = (ImageView) mInflater.inflate(R.layout.tab_right_arrow, this, false);
-        mRightArrowView.setOnClickListener(this);
-        mTabsScrollWrapper = (HorizontalScrollView) mInflater.inflate(
-                R.layout.tab_layout, this, false);
-        mTabsView = (TabStripView) mTabsScrollWrapper.findViewById(android.R.id.tabs);
-        View accountNameView = mInflater.inflate(R.layout.tab_account_name, this, false);
-
-        mLeftArrowView.setVisibility(View.INVISIBLE);
-        mRightArrowView.setVisibility(View.INVISIBLE);
-
-        addView(mTabsScrollWrapper);
-        addView(mLeftArrowView);
-        addView(mRightArrowView);
-        addView(accountNameView);
-    }
-
-    @Override
-    protected void onAttachedToWindow() {
-        super.onAttachedToWindow();
-        final ViewTreeObserver treeObserver = getViewTreeObserver();
-        if (treeObserver != null) {
-            treeObserver.addOnGlobalFocusChangeListener(this);
-        }
-    }
-
-    @Override
-    protected void onDetachedFromWindow() {
-        super.onDetachedFromWindow();
-        final ViewTreeObserver treeObserver = getViewTreeObserver();
-        if (treeObserver != null) {
-            treeObserver.removeOnGlobalFocusChangeListener(this);
-        }
-    }
-
-    protected void updateArrowVisibility() {
-        int scrollViewLeftEdge = mTabsScrollWrapper.getScrollX();
-        int tabsViewLeftEdge = mTabsView.getLeft();
-        int scrollViewRightEdge = scrollViewLeftEdge + mTabsScrollWrapper.getWidth();
-        int tabsViewRightEdge = mTabsView.getRight();
-
-        int rightArrowCurrentVisibility = mRightArrowView.getVisibility();
-        if (scrollViewRightEdge == tabsViewRightEdge
-                && rightArrowCurrentVisibility == View.VISIBLE) {
-            mRightArrowView.setVisibility(View.INVISIBLE);
-        } else if (scrollViewRightEdge < tabsViewRightEdge
-                && rightArrowCurrentVisibility != View.VISIBLE) {
-            mRightArrowView.setVisibility(View.VISIBLE);
-        }
-
-        int leftArrowCurrentVisibility = mLeftArrowView.getVisibility();
-        if (scrollViewLeftEdge == tabsViewLeftEdge
-                && leftArrowCurrentVisibility == View.VISIBLE) {
-            mLeftArrowView.setVisibility(View.INVISIBLE);
-        } else if (scrollViewLeftEdge > tabsViewLeftEdge
-                && leftArrowCurrentVisibility != View.VISIBLE) {
-            mLeftArrowView.setVisibility(View.VISIBLE);
-        }
-    }
-
-    /**
-     * Returns the tab indicator view at the given index.
-     *
-     * @param index the zero-based index of the tab indicator view to return
-     * @return the tab indicator view at the given index
-     */
-    public View getChildTabViewAt(int index) {
-        return mTabsView.getChildAt(index);
-    }
-
-    /**
-     * Returns the number of tab indicator views.
-     *
-     * @return the number of tab indicator views.
-     */
-    public int getTabCount() {
-        return mTabsView.getChildCount();
-    }
-
-    /**
-     * Returns the {@link ViewGroup} that actually contains the tabs. This is where the tab
-     * views should be attached to when being inflated.
-     */
-    public ViewGroup getTabParent() {
-        return mTabsView;
-    }
-
-    public void removeAllTabs() {
-        mTabsView.removeAllViews();
-    }
-
-    @Override
-    public void dispatchDraw(Canvas canvas) {
-        updateArrowVisibility();
-        super.dispatchDraw(canvas);
-    }
-
-    /**
-     * Sets the current tab.
-     * This method is used to bring a tab to the front of the Widget,
-     * and is used to post to the rest of the UI that a different tab
-     * has been brought to the foreground.
-     *
-     * Note, this is separate from the traditional "focus" that is
-     * employed from the view logic.
-     *
-     * For instance, if we have a list in a tabbed view, a user may be
-     * navigating up and down the list, moving the UI focus (orange
-     * highlighting) through the list items.  The cursor movement does
-     * not effect the "selected" tab though, because what is being
-     * scrolled through is all on the same tab.  The selected tab only
-     * changes when we navigate between tabs (moving from the list view
-     * to the next tabbed view, in this example).
-     *
-     * To move both the focus AND the selected tab at once, please use
-     * {@link #focusCurrentTab}. Normally, the view logic takes care of
-     * adjusting the focus, so unless you're circumventing the UI,
-     * you'll probably just focus your interest here.
-     *
-     *  @param index The tab that you want to indicate as the selected
-     *  tab (tab brought to the front of the widget)
-     *
-     *  @see #focusCurrentTab
-     */
-    public void setCurrentTab(int index) {
-        if (index < 0 || index >= getTabCount()) {
-            return;
-        }
-
-        if (mSelectedTab < getTabCount()) {
-            mTabsView.setSelected(mSelectedTab, false);
-        }
-        mSelectedTab = index;
-        mTabsView.setSelected(mSelectedTab, true);
-    }
-
-    /**
-     * Return index of the currently selected tab.
-     */
-    public int getCurrentTab() {
-        return mSelectedTab;
-    }
-
-    /**
-     * Sets the current tab and focuses the UI on it.
-     * This method makes sure that the focused tab matches the selected
-     * tab, normally at {@link #setCurrentTab}.  Normally this would not
-     * be an issue if we go through the UI, since the UI is responsible
-     * for calling TabWidget.onFocusChanged(), but in the case where we
-     * are selecting the tab programmatically, we'll need to make sure
-     * focus keeps up.
-     *
-     *  @param index The tab that you want focused (highlighted in orange)
-     *  and selected (tab brought to the front of the widget)
-     *
-     *  @see #setCurrentTab
-     */
-    public void focusCurrentTab(int index) {
-        if (index < 0 || index >= getTabCount()) {
-            return;
-        }
-
-        setCurrentTab(index);
-        getChildTabViewAt(index).requestFocus();
-
-    }
-
-    /**
-     * Adds a tab to the list of tabs. The tab's indicator view is specified
-     * by a layout id. InflateException will be thrown if there is a problem
-     * inflating.
-     *
-     * @param layoutResId The layout id to be inflated to make the tab indicator.
-     */
-    public void addTab(int layoutResId) {
-        addTab(mInflater.inflate(layoutResId, mTabsView, false));
-    }
-
-    /**
-     * Adds a tab to the list of tabs. The tab's indicator view must be provided.
-     *
-     * @param child
-     */
-    public void addTab(View child) {
-        if (child == null) {
-            return;
-        }
-
-        if (child.getLayoutParams() == null) {
-            final LayoutParams lp = new LayoutParams(
-                    ViewGroup.LayoutParams.WRAP_CONTENT,
-                    ViewGroup.LayoutParams.WRAP_CONTENT);
-            lp.setMargins(0, 0, 0, 0);
-            child.setLayoutParams(lp);
-        }
-
-        // Ensure you can navigate to the tab with the keyboard, and you can touch it
-        child.setFocusable(true);
-        child.setClickable(true);
-        child.setOnClickListener(new TabClickListener());
-        child.setOnFocusChangeListener(this);
-
-        mTabsView.addView(child);
-    }
-
-    /**
-     * Provides a way for ViewContactActivity and EditContactActivity to be notified that the
-     * user clicked on a tab indicator.
-     */
-    public void setTabSelectionListener(OnTabSelectionChangedListener listener) {
-        mSelectionChangedListener = listener;
-    }
-
-    public void onGlobalFocusChanged(View oldFocus, View newFocus) {
-        if (isTab(oldFocus) && !isTab(newFocus)) {
-            onLoseFocus();
-        }
-    }
-
-    public void onFocusChange(View v, boolean hasFocus) {
-        if (v == this && hasFocus) {
-            onObtainFocus();
-            return;
-        }
-
-        if (hasFocus) {
-            for (int i = 0; i < getTabCount(); i++) {
-                if (getChildTabViewAt(i) == v) {
-                    setCurrentTab(i);
-                    mSelectionChangedListener.onTabSelectionChanged(i, false);
-                    break;
-                }
-            }
-        }
-    }
-
-    /**
-     * Called when the {@link ScrollingTabWidget} gets focus. Here the
-     * widget decides which of it's tabs should have focus.
-     */
-    protected void onObtainFocus() {
-        // Setting this flag, allows the children of this View to obtain focus.
-        setDescendantFocusability(FOCUS_AFTER_DESCENDANTS);
-        // Assign focus to the last selected tab.
-        focusCurrentTab(mSelectedTab);
-        mSelectionChangedListener.onTabSelectionChanged(mSelectedTab, false);
-    }
-
-    /**
-     * Called when the focus has left the {@link ScrollingTabWidget} or its
-     * descendants. At this time we want the children of this view to be marked
-     * as un-focusable, so that next time focus is moved to the widget, the widget
-     * gets control, and can assign focus where it wants.
-     */
-    protected void onLoseFocus() {
-        // Setting this flag will effectively make the tabs unfocusable. This will
-        // be toggled when the widget obtains focus again.
-        setDescendantFocusability(FOCUS_BLOCK_DESCENDANTS);
-    }
-
-    public boolean isTab(View v) {
-        for (int i = 0; i < getTabCount(); i++) {
-            if (getChildTabViewAt(i) == v) {
-                return true;
-            }
-        }
-        return false;
-    }
-
-    private class TabClickListener implements OnClickListener {
-        public void onClick(View v) {
-            for (int i = 0; i < getTabCount(); i++) {
-                if (getChildTabViewAt(i) == v) {
-                    setCurrentTab(i);
-                    mSelectionChangedListener.onTabSelectionChanged(i, true);
-                    break;
-                }
-            }
-        }
-    }
-
-    public interface OnTabSelectionChangedListener {
-        /**
-         * Informs the tab widget host which tab was selected. It also indicates
-         * if the tab was clicked/pressed or just focused into.
-         *
-         * @param tabIndex index of the tab that was selected
-         * @param clicked whether the selection changed due to a touch/click
-         * or due to focus entering the tab through navigation. Pass true
-         * if it was due to a press/click and false otherwise.
-         */
-        void onTabSelectionChanged(int tabIndex, boolean clicked);
-    }
-
-    public void onClick(View v) {
-        updateLeftMostVisible();
-        if (v == mRightArrowView && (mLeftMostVisibleTabIndex + 1 < getTabCount())) {
-            tabScroll(true /* right */);
-        } else if (v == mLeftArrowView && mLeftMostVisibleTabIndex > 0) {
-            tabScroll(false /* left */);
-        }
-    }
-
-    /*
-     * Updates our record of the left most visible tab. We keep track of this explicitly
-     * on arrow clicks, but need to re-calibrate after focus navigation.
-     */
-    protected void updateLeftMostVisible() {
-        int viewableLeftEdge = mTabsScrollWrapper.getScrollX();
-
-        if (mLeftArrowView.getVisibility() == View.VISIBLE) {
-            viewableLeftEdge += mLeftArrowView.getWidth();
-        }
-
-        for (int i = 0; i < getTabCount(); i++) {
-            View tab = getChildTabViewAt(i);
-            int tabLeftEdge = tab.getLeft();
-            if (tabLeftEdge >= viewableLeftEdge) {
-                mLeftMostVisibleTabIndex = i;
-                break;
-            }
-        }
-    }
-
-    /**
-     * Scrolls the tabs by exactly one tab width.
-     *
-     * @param directionRight if true, scroll to the right, if false, scroll to the left.
-     */
-    protected void tabScroll(boolean directionRight) {
-        int scrollWidth = 0;
-        View newLeftMostVisibleTab = null;
-        if (directionRight) {
-            newLeftMostVisibleTab = getChildTabViewAt(++mLeftMostVisibleTabIndex);
-        } else {
-            newLeftMostVisibleTab = getChildTabViewAt(--mLeftMostVisibleTabIndex);
-        }
-
-        scrollWidth = newLeftMostVisibleTab.getLeft() - mTabsScrollWrapper.getScrollX();
-        if (mLeftMostVisibleTabIndex > 0) {
-            scrollWidth -= mLeftArrowView.getWidth();
-        }
-        mTabsScrollWrapper.smoothScrollBy(scrollWidth, 0);
-    }
-
-}
diff --git a/src/com/android/contacts/activities/ContactDetailActivity.java b/src/com/android/contacts/activities/ContactDetailActivity.java
index f1a3c7e..856899e 100644
--- a/src/com/android/contacts/activities/ContactDetailActivity.java
+++ b/src/com/android/contacts/activities/ContactDetailActivity.java
@@ -29,27 +29,23 @@
 import android.view.KeyEvent;
 import android.view.Menu;
 import android.view.MenuItem;
-import android.view.View;
 
 public class ContactDetailActivity extends Activity {
     private static final String TAG = "ContactDetailActivity";
 
     private ContactDetailFragment mFragment;
 
-    private final FragmentCallbackHandler mCallbackHandler = new FragmentCallbackHandler();
-
     @Override
     public void onCreate(Bundle savedState) {
         super.onCreate(savedState);
 
         setContentView(R.layout.contact_detail_activity);
 
-        Log.i(TAG, getIntent().getData().toString());
-
-        final View view = findViewById(R.id.contact_detail_fragment);
         mFragment = (ContactDetailFragment) findFragmentById(R.id.contact_detail_fragment);
-        mFragment.setCallbacks(mCallbackHandler);
+        mFragment.setListener(mFragmentListener);
         mFragment.loadUri(getIntent().getData());
+
+        Log.i(TAG, getIntent().getData().toString());
     }
 
     @Override
@@ -113,17 +109,22 @@
         return super.onKeyDown(keyCode, event);
     }
 
-    private class FragmentCallbackHandler implements ContactDetailFragment.Callbacks {
-        public void closeBecauseContactNotFound() {
+    private final ContactDetailFragment.Listener mFragmentListener =
+            new ContactDetailFragment.Listener() {
+        public void onContactNotFound() {
             finish();
         }
 
-        public void editContact(Uri rawContactUri) {
+        public void onEditRequested(Uri rawContactUri) {
             startActivity(new Intent(Intent.ACTION_EDIT, rawContactUri));
         }
 
-        public void itemClicked(Intent intent) {
+        public void onItemClicked(Intent intent) {
             startActivity(intent);
         }
-    }
+
+        public void onDialogRequested(int id, Bundle bundle) {
+            showDialog(id, bundle);
+        }
+    };
 }
diff --git a/src/com/android/contacts/activities/ContactEditActivity.java b/src/com/android/contacts/activities/ContactEditActivity.java
deleted file mode 100644
index 22aae18..0000000
--- a/src/com/android/contacts/activities/ContactEditActivity.java
+++ /dev/null
@@ -1,155 +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.activities;
-
-import com.android.contacts.ContactsSearchManager;
-import com.android.contacts.R;
-import com.android.contacts.util.DialogManager;
-import com.android.contacts.views.edit.ContactEditFragment;
-
-import android.app.Activity;
-import android.app.Dialog;
-import android.content.Intent;
-import android.net.Uri;
-import android.os.Bundle;
-import android.util.Log;
-import android.view.Menu;
-import android.view.MenuItem;
-
-public class ContactEditActivity extends Activity implements
-        DialogManager.DialogShowingViewActivity {
-
-    private static final String TAG = "ContactEditActivity";
-    private static final int DIALOG_VIEW_DIALOGS_ID1 = 1;
-    private static final int DIALOG_VIEW_DIALOGS_ID2 = 2;
-
-    private final FragmentCallbackHandler mCallbackHandler = new FragmentCallbackHandler();
-    private final DialogManager mDialogManager = new DialogManager(this, DIALOG_VIEW_DIALOGS_ID1,
-            DIALOG_VIEW_DIALOGS_ID2);
-
-    private ContactEditFragment mFragment;
-
-    @Override
-    public void onCreate(Bundle savedState) {
-        super.onCreate(savedState);
-
-        setContentView(R.layout.contact_edit_activity);
-
-        final Intent intent = getIntent();
-        final String action = intent.getAction();
-        final Uri uri = intent.getData();
-        final String mimeType = intent.resolveType(getContentResolver());
-        final Bundle intentExtras = intent.getExtras();
-
-        mFragment = (ContactEditFragment) findFragmentById(R.id.contact_edit_fragment);
-        mFragment.setCallbacks(mCallbackHandler);
-        mFragment.load(action, uri, mimeType, intentExtras);
-    }
-
-    private class FragmentCallbackHandler implements ContactEditFragment.Callbacks {
-        public void closeAfterRevert() {
-            finish();
-        }
-
-        public void closeAfterDelete() {
-            finish();
-        }
-
-        public void closeBecauseContactNotFound() {
-            finish();
-        }
-
-        public void closeAfterSplit() {
-            finish();
-        }
-
-        public void closeBecauseAccountSelectorAborted() {
-            finish();
-        }
-
-        public void setTitleTo(int resourceId) {
-            setTitle(resourceId);
-        }
-
-        public void closeAfterSaving(int resultCode, Intent resultIntent) {
-            setResult(resultCode, resultIntent);
-            finish();
-        }
-    }
-
-    public DialogManager getDialogManager() {
-        return mDialogManager;
-    }
-
-    @Override
-    protected Dialog onCreateDialog(int id, Bundle args) {
-        // If this is a dynamic dialog, use the DialogManager
-        if (id == DIALOG_VIEW_DIALOGS_ID1 || id == DIALOG_VIEW_DIALOGS_ID2) {
-            final Dialog dialog = mDialogManager.onCreateDialog(id, args);
-            if (dialog != null) return dialog;
-            return super.onCreateDialog(id, args);
-        }
-
-        // ask the Fragment whether it knows about the dialog
-        final Dialog fragmentResult = mFragment.onCreateDialog(id, args);
-        if (fragmentResult != null) return fragmentResult;
-
-        // Nobody knows about the Dialog
-        Log.w(TAG, "Unknown dialog requested, id: " + id + ", args: " + args);
-        return null;
-    }
-
-    @Override
-    protected void onActivityResult(int requestCode, int resultCode, Intent data) {
-        // TODO: This is too hardwired.
-        mFragment.onActivityResult(requestCode, resultCode, data);
-    }
-
-    @Override
-    public boolean onCreateOptionsMenu(Menu menu) {
-        // TODO: This is too hardwired.
-        if (mFragment.onCreateOptionsMenu(menu, getMenuInflater())) return true;
-
-        return super.onCreateOptionsMenu(menu);
-    }
-
-    @Override
-    public boolean onPrepareOptionsMenu(Menu menu) {
-        // TODO: This is too hardwired.
-        if (mFragment.onPrepareOptionsMenu(menu)) return true;
-
-        return super.onPrepareOptionsMenu(menu);
-    }
-
-    @Override
-    public boolean onOptionsItemSelected(MenuItem item) {
-        // TODO: This is too hardwired.
-        if (mFragment.onOptionsItemSelected(item)) return true;
-
-        return super.onOptionsItemSelected(item);
-    }
-
-    @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/ContactEditorActivity.java b/src/com/android/contacts/activities/ContactEditorActivity.java
new file mode 100644
index 0000000..377648a
--- /dev/null
+++ b/src/com/android/contacts/activities/ContactEditorActivity.java
@@ -0,0 +1,122 @@
+/*
+ * 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.activities;
+
+import com.android.contacts.ContactsSearchManager;
+import com.android.contacts.R;
+import com.android.contacts.views.editor.ContactEditorFragment;
+
+import android.app.Activity;
+import android.app.Dialog;
+import android.content.Intent;
+import android.os.Bundle;
+import android.util.Log;
+import android.view.Menu;
+import android.view.MenuItem;
+
+public class ContactEditorActivity extends Activity {
+    private static final String TAG = "ContactEditorActivity";
+
+    private ContactEditorFragment mFragment;
+
+    @Override
+    public void onCreate(Bundle savedState) {
+        super.onCreate(savedState);
+
+        setContentView(R.layout.contact_editor_activity);
+
+        mFragment = (ContactEditorFragment) findFragmentById(R.id.contact_editor_fragment);
+        mFragment.setListener(mFragmentListener);
+        mFragment.loadUri(getIntent().getData());
+
+        Log.i(TAG, getIntent().getData().toString());
+    }
+
+    @Override
+    public boolean onCreateOptionsMenu(Menu menu) {
+        // TODO: This is too hardwired.
+        if (mFragment.onCreateOptionsMenu(menu, getMenuInflater())) return true;
+
+        return super.onCreateOptionsMenu(menu);
+    }
+
+    @Override
+    public boolean onPrepareOptionsMenu(Menu menu) {
+        // TODO: This is too hardwired.
+        if (mFragment.onPrepareOptionsMenu(menu)) return true;
+
+        return super.onPrepareOptionsMenu(menu);
+    }
+
+    @Override
+    public boolean onOptionsItemSelected(MenuItem item) {
+        // TODO: This is too hardwired.
+        if (mFragment.onOptionsItemSelected(item)) return true;
+
+        return super.onOptionsItemSelected(item);
+    }
+
+    @Override
+    protected Dialog onCreateDialog(int id, Bundle args) {
+        // ask the Fragment whether it knows about the dialog
+        final Dialog fragmentResult = mFragment.onCreateDialog(id, args);
+        if (fragmentResult != null) return fragmentResult;
+
+        // Nobody knows about the Dialog
+        Log.w(TAG, "Unknown dialog requested, id: " + id + ", args: " + args);
+        return null;
+    }
+
+    @Override
+    public boolean onContextItemSelected(MenuItem item) {
+        // TODO: This is too hardwired.
+        if (mFragment.onContextItemSelected(item)) return true;
+
+        return super.onContextItemSelected(item);
+    }
+
+    @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);
+        }
+    }
+
+    private final ContactEditorFragment.Listener mFragmentListener =
+            new ContactEditorFragment.Listener() {
+        public void onContactNotFound() {
+            // TODO: Show error
+            finish();
+        }
+
+        public void onError() {
+            // TODO: Show error message
+            finish();
+        }
+
+        public void onItemClicked(Intent intent) {
+            startActivity(intent);
+        }
+
+        public void onDialogRequested(int id, Bundle bundle) {
+            showDialog(id, bundle);
+        }
+    };
+}
diff --git a/src/com/android/contacts/activities/ContactFieldEditorActivity.java b/src/com/android/contacts/activities/ContactFieldEditorActivity.java
new file mode 100644
index 0000000..3358ee3
--- /dev/null
+++ b/src/com/android/contacts/activities/ContactFieldEditorActivity.java
@@ -0,0 +1,81 @@
+/*
+ * 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.activities;
+
+import com.android.contacts.views.editor.ContactFieldEditorBaseFragment;
+import com.android.contacts.views.editor.ContactFieldEditorEmailFragment;
+
+import android.app.Activity;
+import android.content.Intent;
+import android.net.Uri;
+import android.os.Bundle;
+
+public class ContactFieldEditorActivity extends Activity {
+    public final static String BUNDLE_RAW_CONTACT_URI = "RawContactUri";
+
+    private ContactFieldEditorBaseFragment mFragment;
+
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+
+        mFragment = new ContactFieldEditorEmailFragment();
+        mFragment.setListener(mFragmentListener);
+
+        openFragmentTransaction()
+            .add(android.R.id.content, mFragment)
+            .commit();
+
+        final Intent intent = getIntent();
+        final Uri rawContactUri = Uri.parse(intent.getStringExtra(BUNDLE_RAW_CONTACT_URI));
+        final boolean isInsert;
+        if (Intent.ACTION_EDIT.equals(intent.getAction())) {
+            isInsert = false;
+        } else if (Intent.ACTION_INSERT.equals(intent.getAction())) {
+            isInsert = true;
+        } else throw new IllegalArgumentException("Action is neither EDIT nor INSERT");
+
+        if (isInsert) {
+            mFragment.setupUris(rawContactUri, null);
+        } else {
+            mFragment.setupUris(rawContactUri, intent.getData());
+        }
+    }
+
+    private ContactFieldEditorBaseFragment.Listener mFragmentListener =
+            new ContactFieldEditorBaseFragment.Listener() {
+        public void onCancel() {
+            setResult(RESULT_CANCELED);
+            finish();
+        }
+
+        public void onContactNotFound() {
+            setResult(RESULT_CANCELED);
+            finish();
+        }
+
+        public void onDataNotFound() {
+            setResult(RESULT_CANCELED);
+            finish();
+        }
+
+        public void onSaved() {
+            setResult(RESULT_OK);
+            finish();
+        }
+    };
+}
diff --git a/src/com/android/contacts/activities/TwoPaneActivity.java b/src/com/android/contacts/activities/TwoPaneActivity.java
index 3b52156..ec6d4e3 100644
--- a/src/com/android/contacts/activities/TwoPaneActivity.java
+++ b/src/com/android/contacts/activities/TwoPaneActivity.java
@@ -34,8 +34,8 @@
     private final static String TAG = "TwoPaneActivity";
     private DefaultContactBrowseListFragment mListFragment;
     private ContactDetailFragment mDetailFragment;
-    private DetailCallbackHandler mDetailCallbackHandler = new DetailCallbackHandler();
-    private ListCallbackHandler mListCallbackHandler = new ListCallbackHandler();
+    private DetailFragmentListener mDetailFragmentListener = new DetailFragmentListener();
+    private ListFragmentListener mListFragmentListener = new ListFragmentListener();
 
     @Override
     protected void onCreate(Bundle savedInstanceState) {
@@ -44,10 +44,10 @@
         setContentView(R.layout.two_pane_activity);
 
         mListFragment = (DefaultContactBrowseListFragment) findFragmentById(R.id.two_pane_list);
-        mListFragment.setOnContactListActionListener(mListCallbackHandler);
+        mListFragment.setOnContactListActionListener(mListFragmentListener);
 
         mDetailFragment = (ContactDetailFragment) findFragmentById(R.id.two_pane_detail);
-        mDetailFragment.setCallbacks(mDetailCallbackHandler);
+        mDetailFragment.setListener(mDetailFragmentListener);
 
         setupSearchUI();
     }
@@ -65,7 +65,7 @@
         });
     }
 
-    private class ListCallbackHandler implements OnContactBrowserActionListener {
+    private class ListFragmentListener implements OnContactBrowserActionListener {
         public void onAddToFavoritesAction(Uri contactUri) {
             Toast.makeText(TwoPaneActivity.this, "onAddToFavoritesAction",
                     Toast.LENGTH_LONG).show();
@@ -116,19 +116,24 @@
         }
     }
 
-    private class DetailCallbackHandler implements ContactDetailFragment.Callbacks {
-        public void closeBecauseContactNotFound() {
-            Toast.makeText(TwoPaneActivity.this, "closeBecauseContactNotFound",
-                    Toast.LENGTH_LONG).show();
+    private class DetailFragmentListener implements ContactDetailFragment.Listener {
+        public void onContactNotFound() {
+            Toast.makeText(TwoPaneActivity.this, "onContactNotFound", Toast.LENGTH_LONG).show();
         }
 
-        public void editContact(Uri rawContactUri) {
-            Toast.makeText(TwoPaneActivity.this, "editContact",
-                    Toast.LENGTH_LONG).show();
+        public void onEditRequested(Uri contactLookupUri) {
+//          final ContactEditFragment fragment = new ContactEditFragment();
+//          openFragmentTransaction().replace(mDetailFragment.getView().getId(), fragment).commit();
+//          fragment.loadUri(contactLookupUri);
+            Toast.makeText(TwoPaneActivity.this, "editContact", Toast.LENGTH_LONG).show();
         }
 
-        public void itemClicked(Intent intent) {
+        public void onItemClicked(Intent intent) {
             startActivity(intent);
         }
+
+        public void onDialogRequested(int id, Bundle bundle) {
+            showDialog(id, bundle);
+        }
     }
 }
diff --git a/src/com/android/contacts/model/FallbackSource.java b/src/com/android/contacts/model/FallbackSource.java
index 9cc855c..a23426c 100644
--- a/src/com/android/contacts/model/FallbackSource.java
+++ b/src/com/android/contacts/model/FallbackSource.java
@@ -107,6 +107,8 @@
         if (kind == null) {
             kind = addKind(new DataKind(StructuredName.CONTENT_ITEM_TYPE,
                     R.string.nameLabelsGroup, -1, -1, true));
+            kind.actionHeader = new SimpleInflater(R.string.nameLabelsGroup);
+            kind.actionBody = new SimpleInflater(Nickname.NAME);
         }
 
         if (inflateLevel >= ContactsSource.LEVEL_CONSTRAINTS) {
diff --git a/src/com/android/contacts/views/detail/ContactDetailLoader.java b/src/com/android/contacts/views/ContactLoader.java
similarity index 92%
rename from src/com/android/contacts/views/detail/ContactDetailLoader.java
rename to src/com/android/contacts/views/ContactLoader.java
index e8ec96a..f03d269 100644
--- a/src/com/android/contacts/views/detail/ContactDetailLoader.java
+++ b/src/com/android/contacts/views/ContactLoader.java
@@ -14,7 +14,7 @@
  * limitations under the License
  */
 
-package com.android.contacts.views.detail;
+package com.android.contacts.views;
 
 import com.android.contacts.util.DataStatus;
 
@@ -43,7 +43,7 @@
 /**
  * Loads a single Contact and all it constituent RawContacts.
  */
-public class ContactDetailLoader extends Loader<ContactDetailLoader.Result> {
+public class ContactLoader extends Loader<ContactLoader.Result> {
     private static final String TAG = "ContactLoader";
 
     private Uri mLookupUri;
@@ -51,7 +51,7 @@
     private ForceLoadContentObserver mObserver;
     private boolean mDestroyed;
 
-    public interface Callbacks {
+    public interface Listener {
         public void onContactLoaded(Result contact);
     }
 
@@ -237,7 +237,7 @@
         protected Result doInBackground(Void... args) {
             try {
                 final ContentResolver resolver = getContext().getContentResolver();
-                final Uri uriCurrentFormat = convertLegacyIfNecessary(mLookupUri);
+                final Uri uriCurrentFormat = ensureIsContactUri(resolver, mLookupUri);
                 Result result = loadContactHeaderData(resolver, uriCurrentFormat);
                 if (result == Result.NOT_FOUND) {
                     // No record found. Try to lookup up a new record with the same lookupKey.
@@ -258,6 +258,7 @@
 
                 return result;
             } catch (Exception e) {
+                Log.w(TAG, "Error loading the contact: " + e.getMessage());
                 return Result.ERROR;
             }
         }
@@ -265,15 +266,30 @@
         /**
          * Transforms the given Uri and returns a Lookup-Uri that represents the contact.
          * For legacy contacts, a raw-contact lookup is performed.
+         * @param resolver
          */
-        private Uri convertLegacyIfNecessary(Uri uri) {
+        private Uri ensureIsContactUri(final ContentResolver resolver, final Uri uri) {
             if (uri == null) throw new IllegalArgumentException("uri must not be null");
 
             final String authority = uri.getAuthority();
 
-            // Current Style Uri? Just return it
+            // Current Style Uri?
             if (ContactsContract.AUTHORITY.equals(authority)) {
-                return uri;
+                final String type = resolver.getType(uri);
+                // Contact-Uri? Good, return it
+                if (Contacts.CONTENT_ITEM_TYPE.equals(type)) {
+                    return uri;
+                }
+
+                // RawContact-Uri? Transform it to ContactUri
+                if (RawContacts.CONTENT_ITEM_TYPE.equals(type)) {
+                    final long rawContactId = ContentUris.parseId(uri);
+                    return RawContacts.getContactLookupUri(getContext().getContentResolver(),
+                            ContentUris.withAppendedId(RawContacts.CONTENT_URI, rawContactId));
+                }
+
+                // Anything else? We don't know what this is
+                throw new IllegalArgumentException("uri format is unknown");
             }
 
             // Legacy Style? Convert to RawContact
@@ -281,11 +297,11 @@
             if (OBSOLETE_AUTHORITY.equals(authority)) {
                 // Legacy Format. Convert to RawContact-Uri and then lookup the contact
                 final long rawContactId = ContentUris.parseId(uri);
-                return RawContacts.getContactLookupUri(getContext().getContentResolver(),
+                return RawContacts.getContactLookupUri(resolver,
                         ContentUris.withAppendedId(RawContacts.CONTENT_URI, rawContactId));
             }
 
-            throw new IllegalArgumentException("uri format is unknown");
+            throw new IllegalArgumentException("uri authority is unknown");
         }
 
         /**
@@ -440,6 +456,7 @@
             }
 
             mContact = result;
+            mLookupUri = result.getLookupUri();
             if (result != null) {
                 if (mObserver == null) {
                     mObserver = new ForceLoadContentObserver();
@@ -455,7 +472,7 @@
         }
     }
 
-    public ContactDetailLoader(Context context, Uri lookupUri) {
+    public ContactLoader(Context context, Uri lookupUri) {
         super(context);
         mLookupUri = lookupUri;
     }
diff --git a/src/com/android/contacts/views/detail/ContactDetailFragment.java b/src/com/android/contacts/views/detail/ContactDetailFragment.java
index d85e7a3..0a967bd 100644
--- a/src/com/android/contacts/views/detail/ContactDetailFragment.java
+++ b/src/com/android/contacts/views/detail/ContactDetailFragment.java
@@ -17,7 +17,6 @@
 package com.android.contacts.views.detail;
 
 import com.android.contacts.Collapser;
-import com.android.contacts.ContactEntryAdapter;
 import com.android.contacts.ContactOptionsActivity;
 import com.android.contacts.ContactPresenceIconUtil;
 import com.android.contacts.ContactsUtils;
@@ -29,8 +28,8 @@
 import com.android.contacts.model.ContactsSource.DataKind;
 import com.android.contacts.util.Constants;
 import com.android.contacts.util.DataStatus;
+import com.android.contacts.views.ContactLoader;
 import com.android.internal.telephony.ITelephony;
-import com.android.internal.widget.ContactHeaderWidget;
 
 import android.app.Activity;
 import android.app.AlertDialog;
@@ -65,6 +64,7 @@
 import android.provider.ContactsContract.CommonDataKinds.Note;
 import android.provider.ContactsContract.CommonDataKinds.Organization;
 import android.provider.ContactsContract.CommonDataKinds.Phone;
+import android.provider.ContactsContract.CommonDataKinds.StructuredName;
 import android.provider.ContactsContract.CommonDataKinds.StructuredPostal;
 import android.provider.ContactsContract.CommonDataKinds.Website;
 import android.telephony.PhoneNumberUtils;
@@ -82,6 +82,7 @@
 import android.view.View.OnClickListener;
 import android.view.View.OnCreateContextMenuListener;
 import android.widget.AdapterView;
+import android.widget.BaseAdapter;
 import android.widget.ImageView;
 import android.widget.ListView;
 import android.widget.TextView;
@@ -90,10 +91,9 @@
 
 import java.util.ArrayList;
 
-public class ContactDetailFragment extends LoaderManagingFragment<ContactDetailLoader.Result>
+public class ContactDetailFragment extends LoaderManagingFragment<ContactLoader.Result>
         implements OnCreateContextMenuListener, OnItemClickListener {
-    private static final String TAG = "ContactDetailsView";
-    private static final boolean SHOW_SEPARATORS = false;
+    private static final String TAG = "ContactDetailFragment";
 
     private static final int MENU_ITEM_MAKE_DEFAULT = 3;
 
@@ -101,10 +101,10 @@
 
     private Context mContext;
     private Uri mLookupUri;
-    private Callbacks mCallbacks;
+    private Listener mListener;
 
-    private ContactDetailLoader.Result mContactData;
-    private ContactHeaderWidget mContactHeaderWidget;
+    private ContactLoader.Result mContactData;
+    private ContactDetailHeaderView mHeaderView;
     private ListView mListView;
     private boolean mShowSmsLinksForAllPhones;
     private ViewAdapter mAdapter;
@@ -137,6 +137,7 @@
     private ArrayList<ViewEntry> mGroupEntries = new ArrayList<ViewEntry>();
     private ArrayList<ViewEntry> mOtherEntries = new ArrayList<ViewEntry>();
     private ArrayList<ArrayList<ViewEntry>> mSections = new ArrayList<ArrayList<ViewEntry>>();
+    private LayoutInflater mInflater;
 
     public ContactDetailFragment() {
         // Explicit constructor for inflation
@@ -162,13 +163,16 @@
 
     @Override
     public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedState) {
-        final View view = inflater.inflate(R.layout.contact_detail, container, false);
+        final View view = inflater.inflate(R.layout.contact_detail_fragment, container, false);
 
-        mContactHeaderWidget = (ContactHeaderWidget) view.findViewById(R.id.contact_header_widget);
-        mContactHeaderWidget.showStar(true);
-        mContactHeaderWidget.setExcludeMimes(new String[] {
+        mInflater = inflater;
+
+        mHeaderView = (ContactDetailHeaderView) view.findViewById(R.id.contact_header_widget);
+        mHeaderView.showStar(true);
+        mHeaderView.setExcludeMimes(new String[] {
             Contacts.CONTENT_ITEM_TYPE
         });
+        mHeaderView.setListener(mHeaderViewListener);
 
         mListView = (ListView) view.findViewById(android.R.id.list);
         mListView.setOnCreateContextMenuListener(this);
@@ -183,25 +187,24 @@
         return view;
     }
 
-    public void setCallbacks(Callbacks value) {
-        mCallbacks = value;
+    public void setListener(Listener value) {
+        mListener = value;
     }
 
     public void loadUri(Uri lookupUri) {
         mLookupUri = lookupUri;
-        super.startLoading(LOADER_DETAILS, null);
+        startLoading(LOADER_DETAILS, null);
     }
 
     @Override
     protected void onInitializeLoaders() {
-//        startLoading(LOADER_DETAILS, null);
     }
 
     @Override
-    protected Loader<ContactDetailLoader.Result> onCreateLoader(int id, Bundle args) {
+    protected Loader<ContactLoader.Result> onCreateLoader(int id, Bundle args) {
         switch (id) {
             case LOADER_DETAILS: {
-                return new ContactDetailLoader(mContext, mLookupUri);
+                return new ContactLoader(mContext, mLookupUri);
             }
             default: {
                 Log.wtf(TAG, "Unknown ID in onCreateLoader: " + id);
@@ -211,15 +214,15 @@
     }
 
     @Override
-    protected void onLoadFinished(Loader<ContactDetailLoader.Result> loader,
-            ContactDetailLoader.Result data) {
+    protected void onLoadFinished(Loader<ContactLoader.Result> loader,
+            ContactLoader.Result data) {
         final int id = loader.getId();
         switch (id) {
             case LOADER_DETAILS:
-                if (data == ContactDetailLoader.Result.NOT_FOUND) {
+                if (data == ContactLoader.Result.NOT_FOUND) {
                     // Item has been deleted
                     Log.i(TAG, "No contact found. Closing activity");
-                    mCallbacks.closeBecauseContactNotFound();
+                    mListener.onContactNotFound();
                     return;
                 }
                 mContactData = data;
@@ -233,13 +236,13 @@
 
     private void bindData() {
         // Set the header
-        mContactHeaderWidget.setContactUri(mContactData.getLookupUri());
-        mContactHeaderWidget.setDisplayName(mContactData.getDisplayName(),
+        mHeaderView.setContactUri(mContactData.getLookupUri());
+        mHeaderView.setDisplayName(mContactData.getDisplayName(),
                 mContactData.getPhoneticName());
-        mContactHeaderWidget.setPhotoId(mContactData.getPhotoId(), mContactData.getLookupUri());
-        mContactHeaderWidget.setStared(mContactData.getStarred());
-        mContactHeaderWidget.setPresence(mContactData.getPresence());
-        mContactHeaderWidget.setStatus(
+        mHeaderView.setPhotoId(mContactData.getPhotoId(), mContactData.getLookupUri());
+        mHeaderView.setStared(mContactData.getStarred());
+        mHeaderView.setPresence(mContactData.getPresence());
+        mHeaderView.setStatus(
                 mContactData.getStatus(), mContactData.getStatusTimestamp(),
                 mContactData.getStatusLabel(), mContactData.getStatusResPackage());
 
@@ -254,10 +257,10 @@
         Collapser.collapseList(mImEntries);
 
         if (mAdapter == null) {
-            mAdapter = new ViewAdapter(mContext, mSections);
+            mAdapter = new ViewAdapter();
             mListView.setAdapter(mAdapter);
         } else {
-            mAdapter.setSections(mSections, SHOW_SEPARATORS);
+            mAdapter.notifyDataSetChanged();
         }
         mListView.setEmptyView(mEmptyView);
     }
@@ -282,6 +285,7 @@
 
         mWritableRawContactIds.clear();
 
+        // TODO: This should be done in the background thread
         final Sources sources = Sources.getInstance(mContext);
 
         // Build up method entries
@@ -330,7 +334,9 @@
                 final boolean isSuperPrimary = entryValues.getAsInteger(
                         Data.IS_SUPER_PRIMARY) != 0;
 
-                if (Phone.CONTENT_ITEM_TYPE.equals(mimeType) && hasData) {
+                if (StructuredName.CONTENT_ITEM_TYPE.equals(mimeType)) {
+                    // Always ignore the name. It is shown in the header if set
+                } else if (Phone.CONTENT_ITEM_TYPE.equals(mimeType) && hasData) {
                     // Build phone entries
                     mNumPhoneNumbers++;
 
@@ -460,7 +466,7 @@
         }
     }
 
-    /* package */ static String buildActionString(DataKind kind, ContentValues values,
+    private static String buildActionString(DataKind kind, ContentValues values,
             boolean lowerCase, Context context) {
         if (kind.actionHeader == null) {
             return null;
@@ -472,7 +478,7 @@
         return lowerCase ? actionHeader.toString().toLowerCase() : actionHeader.toString();
     }
 
-    /* package */ static String buildDataString(DataKind kind, ContentValues values,
+    private static String buildDataString(DataKind kind, ContentValues values,
             Context context) {
         if (kind.actionBody == null) {
             return null;
@@ -484,7 +490,16 @@
     /**
      * A basic structure with the data for a contact entry in the list.
      */
-    static class ViewEntry extends ContactEntryAdapter.Entry implements Collapsible<ViewEntry> {
+    private static class ViewEntry implements Collapsible<ViewEntry> {
+        // Copied from baseclass
+        public int type = -1;
+        public String label;
+        public String data;
+        public Uri uri;
+        public long id = 0;
+        public int maxLines = 1;
+        public String mimetype;
+
         public Context context = null;
         public String resPackageName = null;
         public int actionIcon = -1;
@@ -510,7 +525,6 @@
                 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;
@@ -610,19 +624,11 @@
         public ImageView primaryIcon;
         public ImageView secondaryActionButton;
         public View secondaryActionDivider;
-
-        // Need to keep track of this too
-        public ViewEntry entry;
     }
 
-    final class ViewAdapter extends ContactEntryAdapter<ViewEntry> implements OnClickListener {
-        ViewAdapter(Context context, ArrayList<ArrayList<ViewEntry>> sections) {
-            super(context, sections, SHOW_SEPARATORS);
-        }
-
-        @Override
+    private final class ViewAdapter extends BaseAdapter {
         public View getView(int position, View convertView, ViewGroup parent) {
-            final ViewEntry entry = getEntry(mSections, position, false);
+            final ViewEntry entry = getEntry(position);
             final View v;
             final ViewCache viewCache;
 
@@ -644,26 +650,16 @@
                 viewCache.presenceIcon = (ImageView) v.findViewById(R.id.presence_icon);
                 viewCache.secondaryActionButton = (ImageView) v.findViewById(
                         R.id.secondary_action_button);
-                viewCache.secondaryActionButton.setOnClickListener(this);
+                viewCache.secondaryActionButton.setOnClickListener(mSecondaryActionClickListener);
                 viewCache.secondaryActionDivider = v.findViewById(R.id.divider);
                 v.setTag(viewCache);
             }
 
-            // Update the entry in the view cache
-            viewCache.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();
@@ -697,7 +693,7 @@
             views.primaryIcon.setVisibility(entry.isPrimary ? View.VISIBLE : View.GONE);
 
             // Set the action icon
-            ImageView action = views.actionIcon;
+            final ImageView action = views.actionIcon;
             if (entry.actionIcon != -1) {
                 Drawable actionIcon;
                 if (entry.resPackageName != null) {
@@ -715,9 +711,9 @@
             }
 
             // Set the presence icon
-            Drawable presenceIcon = ContactPresenceIconUtil.getPresenceIcon(
+            final Drawable presenceIcon = ContactPresenceIconUtil.getPresenceIcon(
                     mContext, entry.presence);
-            ImageView presenceIconView = views.presenceIcon;
+            final ImageView presenceIconView = views.presenceIcon;
             if (presenceIcon != null) {
                 presenceIconView.setImageDrawable(presenceIcon);
                 presenceIconView.setVisibility(View.VISIBLE);
@@ -726,7 +722,7 @@
             }
 
             // Set the secondary action button
-            ImageView secondaryActionView = views.secondaryActionButton;
+            final ImageView secondaryActionView = views.secondaryActionButton;
             Drawable secondaryActionIcon = null;
             if (entry.secondaryActionIcon != -1) {
                 secondaryActionIcon = resources.getDrawable(entry.secondaryActionIcon);
@@ -753,14 +749,52 @@
             }
         }
 
-        public void onClick(View v) {
-            if (mCallbacks == null) return;
-            if (v == null) return;
-            final ViewEntry entry = (ViewEntry) v.getTag();
-            if (entry == null) return;
-            final Intent intent = entry.secondaryIntent;
-            if (intent == null) return;
-            mCallbacks.itemClicked(intent);
+        private OnClickListener mSecondaryActionClickListener = new OnClickListener() {
+            public void onClick(View v) {
+                if (mListener == null) return;
+                if (v == null) return;
+                final ViewEntry entry = (ViewEntry) v.getTag();
+                if (entry == null) return;
+                final Intent intent = entry.secondaryIntent;
+                if (intent == null) return;
+                mListener.onItemClicked(intent);
+            }
+        };
+
+        public int getCount() {
+            int count = 0;
+            final int numSections = mSections.size();
+            for (int i = 0; i < numSections; i++) {
+                final ArrayList<ViewEntry> section = mSections.get(i);
+                count += section.size();
+            }
+            return count;
+        }
+
+        public Object getItem(int position) {
+            return getEntry(position);
+        }
+
+        public long getItemId(int position) {
+            final ViewEntry entry = getEntry(position);
+            if (entry != null) {
+                return entry.id;
+            } else {
+                return -1;
+            }
+        }
+
+        private ViewEntry getEntry(int position) {
+            final int numSections = mSections.size();
+            for (int i = 0; i < numSections; i++) {
+                final ArrayList<ViewEntry> section = mSections.get(i);
+                final int sectionSize = section.size();
+                if (position < sectionSize) {
+                    return section.get(position);
+                }
+                position -= sectionSize;
+            }
+            return null;
         }
     }
 
@@ -783,16 +817,8 @@
     public boolean onOptionsItemSelected(MenuItem item) {
         switch (item.getItemId()) {
             case R.id.menu_edit: {
-                if (mRawContactIds.size() > 0) {
-                    final long rawContactIdToEdit = mRawContactIds.get(0);
-                    final Uri rawContactUri = ContentUris.withAppendedId(RawContacts.CONTENT_URI,
-                            rawContactIdToEdit);
-                    mCallbacks.editContact(rawContactUri);
-                    return true;
-                } else {
-                    // There is no rawContact to edit.
-                    return false;
-                }
+                mListener.onEditRequested(mLookupUri);
+                break;
             }
             case R.id.menu_delete: {
                 showDeleteConfirmationDialog();
@@ -830,15 +856,17 @@
     }
 
     private void showDeleteConfirmationDialog() {
+        final int id;
         if (mReadOnlySourcesCnt > 0 & mWritableSourcesCnt > 0) {
-            getActivity().showDialog(R.id.detail_dialog_confirm_readonly_delete);
+            id = R.id.detail_dialog_confirm_readonly_delete;
         } else if (mReadOnlySourcesCnt > 0 && mWritableSourcesCnt == 0) {
-            getActivity().showDialog(R.id.detail_dialog_confirm_readonly_hide);
+            id = R.id.detail_dialog_confirm_readonly_hide;
         } else if (mReadOnlySourcesCnt == 0 && mWritableSourcesCnt > 1) {
-            getActivity().showDialog(R.id.detail_dialog_confirm_multiple_delete);
+            id = R.id.detail_dialog_confirm_multiple_delete;
         } else {
-            getActivity().showDialog(R.id.detail_dialog_confirm_delete);
+            id = R.id.detail_dialog_confirm_delete;
         }
+        if (mListener != null) mListener.onDialogRequested(id, null);
     }
 
     public void onCreateContextMenu(ContextMenu menu, View v, ContextMenuInfo menuInfo) {
@@ -856,7 +884,7 @@
             return;
         }
 
-        ViewEntry entry = ContactEntryAdapter.getEntry(mSections, info.position, SHOW_SEPARATORS);
+        final ViewEntry entry = mAdapter.getEntry(info.position);
         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);
@@ -875,12 +903,12 @@
     }
 
     public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
-        if (mCallbacks == null) return;
-        final ViewEntry entry = ViewAdapter.getEntry(mSections, position, SHOW_SEPARATORS);
+        if (mListener == null) return;
+        final ViewEntry entry = mAdapter.getEntry(position);
         if (entry == null) return;
         final Intent intent = entry.intent;
         if (intent == null) return;
-        mCallbacks.itemClicked(intent);
+        mListener.onItemClicked(intent);
     }
 
     private final DialogInterface.OnClickListener mDeleteListener =
@@ -969,7 +997,7 @@
             return null;
         }
 
-        return ContactEntryAdapter.getEntry(mSections, info.position, SHOW_SEPARATORS);
+        return mAdapter.getEntry(info.position);
     }
 
     public boolean onKeyDown(int keyCode, KeyEvent event) {
@@ -988,7 +1016,7 @@
 
                 int index = mListView.getSelectedItemPosition();
                 if (index != -1) {
-                    final ViewEntry entry = ViewAdapter.getEntry(mSections, index, SHOW_SEPARATORS);
+                    final ViewEntry entry = mAdapter.getEntry(index);
                     if (entry != null &&
                             entry.intent.getAction() == Intent.ACTION_CALL_PRIVILEGED) {
                         mContext.startActivity(entry.intent);
@@ -1013,20 +1041,39 @@
         return false;
     }
 
-    public static interface Callbacks {
+    private ContactDetailHeaderView.Listener mHeaderViewListener =
+            new ContactDetailHeaderView.Listener() {
+        public void onDisplayNameClick(View view) {
+        }
+
+        public void onEditClicked() {
+            if (mListener != null) mListener.onEditRequested(mLookupUri);
+        }
+
+        public void onPhotoClick(View view) {
+        }
+    };
+
+
+    public static interface Listener {
         /**
          * Contact was not found, so somehow close this fragment.
          */
-        public void closeBecauseContactNotFound();
+        public void onContactNotFound();
 
         /**
          * User decided to go to Edit-Mode
          */
-        public void editContact(Uri rawContactUri);
+        public void onEditRequested(Uri lookupUri);
 
         /**
          * User clicked a single item (e.g. mail)
          */
-        public void itemClicked(Intent intent);
+        public void onItemClicked(Intent intent);
+
+        /**
+         * Show a dialog using the globally unique id
+         */
+        public void onDialogRequested(int id, Bundle bundle);
     }
 }
diff --git a/src/com/android/contacts/views/detail/ContactDetailHeaderView.java b/src/com/android/contacts/views/detail/ContactDetailHeaderView.java
new file mode 100644
index 0000000..fcff6c1
--- /dev/null
+++ b/src/com/android/contacts/views/detail/ContactDetailHeaderView.java
@@ -0,0 +1,728 @@
+/*
+ * 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.views.detail;
+
+import com.android.contacts.R;
+
+import android.content.AsyncQueryHandler;
+import android.content.ContentResolver;
+import android.content.ContentUris;
+import android.content.ContentValues;
+import android.content.Context;
+import android.content.pm.PackageManager;
+import android.content.pm.PackageManager.NameNotFoundException;
+import android.content.res.Resources;
+import android.content.res.Resources.NotFoundException;
+import android.database.Cursor;
+import android.graphics.Bitmap;
+import android.graphics.BitmapFactory;
+import android.net.Uri;
+import android.os.SystemClock;
+import android.provider.ContactsContract.Contacts;
+import android.provider.ContactsContract.Data;
+import android.provider.ContactsContract.PhoneLookup;
+import android.provider.ContactsContract.RawContacts;
+import android.provider.ContactsContract.StatusUpdates;
+import android.provider.ContactsContract.CommonDataKinds.Email;
+import android.provider.ContactsContract.CommonDataKinds.Photo;
+import android.text.TextUtils;
+import android.text.format.DateUtils;
+import android.util.AttributeSet;
+import android.util.Log;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.widget.CheckBox;
+import android.widget.ImageButton;
+import android.widget.QuickContactBadge;
+import android.widget.FrameLayout;
+import android.widget.ImageView;
+import android.widget.TextView;
+
+/**
+ * Header for displaying a title bar with contact info. You
+ * can bind specific values on the header
+ */
+public class ContactDetailHeaderView extends FrameLayout implements View.OnClickListener {
+
+    private static final String TAG = "ContactDetailHeaderView";
+
+    private TextView mDisplayNameView;
+    private View mAggregateBadge;
+    private TextView mPhoneticNameView;
+    private CheckBox mStarredView;
+    private QuickContactBadge mPhotoView;
+    private ImageView mPresenceView;
+    private TextView mStatusView;
+    private TextView mStatusAttributionView;
+    private int mNoPhotoResource;
+    private QueryHandler mQueryHandler;
+    private ImageButton mEditButton;
+
+    private Uri mContactUri;
+
+    protected String[] mExcludeMimes = null;
+
+    protected ContentResolver mContentResolver;
+
+    /**
+     * Interface for callbacks invoked when the user interacts with a header.
+     */
+    public interface Listener {
+        public void onPhotoClick(View view);
+        public void onDisplayNameClick(View view);
+        public void onEditClicked();
+    }
+
+    private Listener mListener;
+
+
+    private interface ContactQuery {
+        //Projection used for the summary info in the header.
+        String[] COLUMNS = new String[] {
+            Contacts._ID,
+            Contacts.LOOKUP_KEY,
+            Contacts.PHOTO_ID,
+            Contacts.DISPLAY_NAME,
+            Contacts.PHONETIC_NAME,
+            Contacts.STARRED,
+            Contacts.CONTACT_PRESENCE,
+            Contacts.CONTACT_STATUS,
+            Contacts.CONTACT_STATUS_TIMESTAMP,
+            Contacts.CONTACT_STATUS_RES_PACKAGE,
+            Contacts.CONTACT_STATUS_LABEL,
+        };
+        int _ID = 0;
+        int LOOKUP_KEY = 1;
+        int PHOTO_ID = 2;
+        int DISPLAY_NAME = 3;
+        int PHONETIC_NAME = 4;
+        //TODO: We need to figure out how we're going to get the phonetic name.
+        //static final int HEADER_PHONETIC_NAME_COLUMN_INDEX
+        int STARRED = 5;
+        int CONTACT_PRESENCE_STATUS = 6;
+        int CONTACT_STATUS = 7;
+        int CONTACT_STATUS_TIMESTAMP = 8;
+        int CONTACT_STATUS_RES_PACKAGE = 9;
+        int CONTACT_STATUS_LABEL = 10;
+    }
+
+    private interface PhotoQuery {
+        String[] COLUMNS = new String[] {
+            Photo.PHOTO
+        };
+
+        int PHOTO = 0;
+    }
+
+    //Projection used for looking up contact id from phone number
+    protected static final String[] PHONE_LOOKUP_PROJECTION = new String[] {
+        PhoneLookup._ID,
+        PhoneLookup.LOOKUP_KEY,
+    };
+    protected static final int PHONE_LOOKUP_CONTACT_ID_COLUMN_INDEX = 0;
+    protected static final int PHONE_LOOKUP_CONTACT_LOOKUP_KEY_COLUMN_INDEX = 1;
+
+    //Projection used for looking up contact id from email address
+    protected static final String[] EMAIL_LOOKUP_PROJECTION = new String[] {
+        RawContacts.CONTACT_ID,
+        Contacts.LOOKUP_KEY,
+    };
+    protected static final int EMAIL_LOOKUP_CONTACT_ID_COLUMN_INDEX = 0;
+    protected static final int EMAIL_LOOKUP_CONTACT_LOOKUP_KEY_COLUMN_INDEX = 1;
+
+    protected static final String[] CONTACT_LOOKUP_PROJECTION = new String[] {
+        Contacts._ID,
+    };
+    protected static final int CONTACT_LOOKUP_ID_COLUMN_INDEX = 0;
+
+    private static final int TOKEN_CONTACT_INFO = 0;
+    private static final int TOKEN_PHONE_LOOKUP = 1;
+    private static final int TOKEN_EMAIL_LOOKUP = 2;
+    private static final int TOKEN_PHOTO_QUERY = 3;
+
+    public ContactDetailHeaderView(Context context) {
+        this(context, null);
+    }
+
+    public ContactDetailHeaderView(Context context, AttributeSet attrs) {
+        this(context, attrs, 0);
+    }
+
+    public ContactDetailHeaderView(Context context, AttributeSet attrs, int defStyle) {
+        super(context, attrs, defStyle);
+
+        mContentResolver = mContext.getContentResolver();
+
+        final LayoutInflater inflater =
+            (LayoutInflater) mContext.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
+        inflater.inflate(R.layout.contact_detail_header_view, this);
+
+        mDisplayNameView = (TextView) findViewById(R.id.name);
+        mAggregateBadge = findViewById(R.id.aggregate_badge);
+
+        mPhoneticNameView = (TextView) findViewById(R.id.phonetic_name);
+
+        mStarredView = (CheckBox)findViewById(R.id.star);
+        mStarredView.setOnClickListener(this);
+
+        mEditButton = (ImageButton) findViewById(R.id.edit);
+        mEditButton.setOnClickListener(this);
+
+        mPhotoView = (QuickContactBadge) findViewById(R.id.photo);
+
+        mPresenceView = (ImageView) findViewById(R.id.presence);
+
+        mStatusView = (TextView)findViewById(R.id.status);
+        mStatusAttributionView = (TextView)findViewById(R.id.status_date);
+
+        // Set the photo with a random "no contact" image
+        long now = SystemClock.elapsedRealtime();
+        int num = (int) now & 0xf;
+        if (num < 9) {
+            // Leaning in from right, common
+            mNoPhotoResource = R.drawable.ic_contact_picture;
+        } else if (num < 14) {
+            // Leaning in from left uncommon
+            mNoPhotoResource = R.drawable.ic_contact_picture_2;
+        } else {
+            // Coming in from the top, rare
+            mNoPhotoResource = R.drawable.ic_contact_picture_3;
+        }
+
+        resetAsyncQueryHandler();
+    }
+
+    public void enableClickListeners() {
+        mDisplayNameView.setOnClickListener(this);
+        mPhotoView.setOnClickListener(this);
+    }
+
+    /**
+     * Set the given {@link Listener} to handle header events.
+     */
+    public void setListener(Listener listener) {
+        mListener = listener;
+    }
+
+    private void performPhotoClick() {
+        if (mListener != null) {
+            mListener.onPhotoClick(mPhotoView);
+        }
+    }
+
+    private void performEditClick() {
+        if (mListener != null) {
+            mListener.onEditClicked();
+        }
+    }
+
+    private void performDisplayNameClick() {
+        if (mListener != null) {
+            mListener.onDisplayNameClick(mDisplayNameView);
+        }
+    }
+
+    private class QueryHandler extends AsyncQueryHandler {
+
+        public QueryHandler(ContentResolver cr) {
+            super(cr);
+        }
+
+        @Override
+        protected void onQueryComplete(int token, Object cookie, Cursor cursor) {
+            try{
+                if (this != mQueryHandler) {
+                    Log.d(TAG, "onQueryComplete: discard result, the query handler is reset!");
+                    return;
+                }
+
+                switch (token) {
+                    case TOKEN_PHOTO_QUERY: {
+                        //Set the photo
+                        Bitmap photoBitmap = null;
+                        if (cursor != null && cursor.moveToFirst()
+                                && !cursor.isNull(PhotoQuery.PHOTO)) {
+                            byte[] photoData = cursor.getBlob(PhotoQuery.PHOTO);
+                            photoBitmap = BitmapFactory.decodeByteArray(photoData, 0,
+                                    photoData.length, null);
+                        }
+
+                        if (photoBitmap == null) {
+                            photoBitmap = loadPlaceholderPhoto(null);
+                        }
+                        setPhoto(photoBitmap);
+                        if (cookie != null && cookie instanceof Uri) {
+                            mPhotoView.assignContactUri((Uri) cookie);
+                        }
+                        invalidate();
+                        break;
+                    }
+                    case TOKEN_CONTACT_INFO: {
+                        if (cursor != null && cursor.moveToFirst()) {
+                            bindContactInfo(cursor);
+                            final Uri lookupUri = Contacts.getLookupUri(
+                                    cursor.getLong(ContactQuery._ID),
+                                    cursor.getString(ContactQuery.LOOKUP_KEY));
+
+                            final long photoId = cursor.getLong(ContactQuery.PHOTO_ID);
+
+                            setPhotoId(photoId, lookupUri);
+                        } else {
+                            // shouldn't really happen
+                            setDisplayName(null, null);
+                            setSocialSnippet(null);
+                            setPhoto(loadPlaceholderPhoto(null));
+                        }
+                        break;
+                    }
+                    case TOKEN_PHONE_LOOKUP: {
+                        if (cursor != null && cursor.moveToFirst()) {
+                            long contactId = cursor.getLong(PHONE_LOOKUP_CONTACT_ID_COLUMN_INDEX);
+                            String lookupKey = cursor.getString(
+                                    PHONE_LOOKUP_CONTACT_LOOKUP_KEY_COLUMN_INDEX);
+                            bindFromContactUriInternal(Contacts.getLookupUri(contactId, lookupKey),
+                                    false /* don't reset query handler */);
+                        } else {
+                            String phoneNumber = (String) cookie;
+                            setDisplayName(phoneNumber, null);
+                            setSocialSnippet(null);
+                            setPhoto(loadPlaceholderPhoto(null));
+                            mPhotoView.assignContactFromPhone(phoneNumber, true);
+                        }
+                        break;
+                    }
+                    case TOKEN_EMAIL_LOOKUP: {
+                        if (cursor != null && cursor.moveToFirst()) {
+                            long contactId = cursor.getLong(EMAIL_LOOKUP_CONTACT_ID_COLUMN_INDEX);
+                            String lookupKey = cursor.getString(
+                                    EMAIL_LOOKUP_CONTACT_LOOKUP_KEY_COLUMN_INDEX);
+                            bindFromContactUriInternal(Contacts.getLookupUri(contactId, lookupKey),
+                                    false /* don't reset query handler */);
+                        } else {
+                            String emailAddress = (String) cookie;
+                            setDisplayName(emailAddress, null);
+                            setSocialSnippet(null);
+                            setPhoto(loadPlaceholderPhoto(null));
+                            mPhotoView.assignContactFromEmail(emailAddress, true);
+                        }
+                        break;
+                    }
+                }
+            } finally {
+                if (cursor != null) {
+                    cursor.close();
+                }
+            }
+        }
+    }
+
+    /**
+     * Turn on/off showing of the aggregate badge element.
+     */
+    public void showAggregateBadge(boolean showBagde) {
+        mAggregateBadge.setVisibility(showBagde ? View.VISIBLE : View.GONE);
+    }
+
+    /**
+     * Turn on/off showing of the star element.
+     */
+    public void showStar(boolean showStar) {
+        mStarredView.setVisibility(showStar ? View.VISIBLE : View.GONE);
+    }
+
+    /**
+     * Manually set the starred state of this header widget. This doesn't change
+     * the underlying {@link Contacts} value, only the UI state.
+     */
+    public void setStared(boolean starred) {
+        mStarredView.setChecked(starred);
+    }
+
+    /**
+     * Manually set the presence.
+     */
+    public void setPresence(int presence) {
+        mPresenceView.setImageResource(StatusUpdates.getPresenceIconResourceId(presence));
+    }
+
+    /**
+     * Manually set the presence. If presence is null, it is hidden.
+     * This doesn't change the underlying {@link Contacts} value, only the UI state.
+     * @hide
+     */
+    public void setPresence(Integer presence) {
+        if (presence == null) {
+            showPresence(false);
+        } else {
+            showPresence(true);
+            setPresence(presence.intValue());
+        }
+    }
+
+    /**
+     * Turn on/off showing the presence.
+     * @hide this is here for consistency with setStared/showStar and should be public
+     */
+    public void showPresence(boolean showPresence) {
+        mPresenceView.setVisibility(showPresence ? View.VISIBLE : View.GONE);
+    }
+
+    /**
+     * Manually set the contact uri without loading any data
+     */
+    public void setContactUri(Uri uri) {
+        setContactUri(uri, true);
+    }
+
+    /**
+     * Manually set the contact uri without loading any data
+     */
+    public void setContactUri(Uri uri, boolean sendToQuickContact) {
+        mContactUri = uri;
+        if (sendToQuickContact) {
+            mPhotoView.assignContactUri(uri);
+        }
+    }
+
+    /**
+     * Manually set the photo to display in the header. This doesn't change the
+     * underlying {@link Contacts}, only the UI state.
+     */
+    public void setPhoto(Bitmap bitmap) {
+        mPhotoView.setImageBitmap(bitmap);
+    }
+
+    /**
+     * Manually set the photo given its id. If the id is 0, a placeholder picture will
+     * be loaded. For any other Id, an async query is started
+     * @hide
+     */
+    public void setPhotoId(final long photoId, final Uri lookupUri) {
+        if (photoId == 0) {
+            setPhoto(loadPlaceholderPhoto(null));
+            mPhotoView.assignContactUri(lookupUri);
+            invalidate();
+        } else {
+            startPhotoQuery(photoId, lookupUri,
+                    false /* don't reset query handler */);
+        }
+    }
+
+    /**
+     * Manually set the display name and phonetic name to show in the header.
+     * This doesn't change the underlying {@link Contacts}, only the UI state.
+     */
+    public void setDisplayName(CharSequence displayName, CharSequence phoneticName) {
+        mDisplayNameView.setText(displayName);
+        if (!TextUtils.isEmpty(phoneticName)) {
+            mPhoneticNameView.setText(phoneticName);
+            mPhoneticNameView.setVisibility(View.VISIBLE);
+        } else {
+            mPhoneticNameView.setVisibility(View.GONE);
+        }
+    }
+
+    /**
+     * Manually set the social snippet text to display in the header. This doesn't change the
+     * underlying {@link Contacts}, only the UI state.
+     */
+    public void setSocialSnippet(CharSequence snippet) {
+        if (snippet == null) {
+            mStatusView.setVisibility(View.GONE);
+            mStatusAttributionView.setVisibility(View.GONE);
+        } else {
+            mStatusView.setText(snippet);
+            mStatusView.setVisibility(View.VISIBLE);
+        }
+    }
+
+    /**
+     * Manually set the status attribution text to display in the header.
+     * This doesn't change the underlying {@link Contacts}, only the UI state.
+     * @hide
+     */
+    public void setStatusAttribution(CharSequence attribution) {
+        if (attribution != null) {
+            mStatusAttributionView.setText(attribution);
+            mStatusAttributionView.setVisibility(View.VISIBLE);
+        } else {
+            mStatusAttributionView.setVisibility(View.GONE);
+        }
+    }
+
+    /**
+     * Set a list of specific MIME-types to exclude and not display. For
+     * example, this can be used to hide the {@link Contacts#CONTENT_ITEM_TYPE}
+     * profile icon.
+     */
+    public void setExcludeMimes(String[] excludeMimes) {
+        mExcludeMimes = excludeMimes;
+        mPhotoView.setExcludeMimes(excludeMimes);
+    }
+
+    /**
+     * Manually set all the status values to display in the header.
+     * This doesn't change the underlying {@link Contacts}, only the UI state.
+     * @hide
+     * @param status             The status of the contact. If this is either null or empty,
+     *                           the status is cleared and the other parameters are ignored.
+     * @param statusTimestamp    The timestamp (retrieved via a call to
+     *                           {@link System#currentTimeMillis()}) of the last status update.
+     *                           This value can be null if it is not known.
+     * @param statusLabel        The id of a resource string that specifies the current
+     *                           status. This value can be null if no Label should be used.
+     * @param statusResPackage   The name of the resource package containing the resource string
+     *                           referenced in the parameter statusLabel.
+     */
+    public void setStatus(final String status, final Long statusTimestamp,
+            final Integer statusLabel, final String statusResPackage) {
+        if (TextUtils.isEmpty(status)) {
+            setSocialSnippet(null);
+            return;
+        }
+
+        setSocialSnippet(status);
+
+        final CharSequence timestampDisplayValue;
+
+        if (statusTimestamp != null) {
+            // Set the date/time field by mixing relative and absolute
+            // times.
+            int flags = DateUtils.FORMAT_ABBREV_RELATIVE;
+
+            timestampDisplayValue = DateUtils.getRelativeTimeSpanString(
+                    statusTimestamp.longValue(), System.currentTimeMillis(),
+                    DateUtils.MINUTE_IN_MILLIS, flags);
+        } else {
+            timestampDisplayValue = null;
+        }
+
+
+        String labelDisplayValue = null;
+
+        if (statusLabel != null) {
+            Resources resources;
+            if (TextUtils.isEmpty(statusResPackage)) {
+                resources = getResources();
+            } else {
+                PackageManager pm = getContext().getPackageManager();
+                try {
+                    resources = pm.getResourcesForApplication(statusResPackage);
+                } catch (NameNotFoundException e) {
+                    Log.w(TAG, "Contact status update resource package not found: "
+                            + statusResPackage);
+                    resources = null;
+                }
+            }
+
+            if (resources != null) {
+                try {
+                    labelDisplayValue = resources.getString(statusLabel.intValue());
+                } catch (NotFoundException e) {
+                    Log.w(TAG, "Contact status update resource not found: " + statusResPackage + "@"
+                            + statusLabel.intValue());
+                }
+            }
+        }
+
+        final CharSequence attribution;
+        if (timestampDisplayValue != null && labelDisplayValue != null) {
+            attribution = getContext().getString(
+                    R.string.contact_status_update_attribution_with_date,
+                    timestampDisplayValue, labelDisplayValue);
+        } else if (timestampDisplayValue == null && labelDisplayValue != null) {
+            attribution = getContext().getString(
+                    R.string.contact_status_update_attribution,
+                    labelDisplayValue);
+        } else if (timestampDisplayValue != null) {
+            attribution = timestampDisplayValue;
+        } else {
+            attribution = null;
+        }
+        setStatusAttribution(attribution);
+    }
+
+    /**
+     * Convenience method for binding all available data from an existing
+     * contact.
+     *
+     * @param contactUri a {Contacts.CONTENT_URI} style URI.
+     * @param resetQueryHandler whether to use a new AsyncQueryHandler or not.
+     */
+    private void bindFromContactUriInternal(Uri contactUri, boolean resetQueryHandler) {
+        mContactUri = contactUri;
+        startContactQuery(contactUri, resetQueryHandler);
+    }
+
+    /**
+     * Convenience method for binding all available data from an existing
+     * contact.
+     *
+     * @param emailAddress The email address used to do a reverse lookup in
+     * the contacts database. If more than one contact contains this email
+     * address, one of them will be chosen to bind to.
+     */
+    public void bindFromEmail(String emailAddress) {
+        resetAsyncQueryHandler();
+
+        mQueryHandler.startQuery(TOKEN_EMAIL_LOOKUP, emailAddress,
+                Uri.withAppendedPath(Email.CONTENT_LOOKUP_URI, Uri.encode(emailAddress)),
+                EMAIL_LOOKUP_PROJECTION, null, null, null);
+    }
+
+    /**
+     * Convenience method for binding all available data from an existing
+     * contact.
+     *
+     * @param number The phone number used to do a reverse lookup in
+     * the contacts database. If more than one contact contains this phone
+     * number, one of them will be chosen to bind to.
+     */
+    public void bindFromPhoneNumber(String number) {
+        resetAsyncQueryHandler();
+
+        mQueryHandler.startQuery(TOKEN_PHONE_LOOKUP, number,
+                Uri.withAppendedPath(PhoneLookup.CONTENT_FILTER_URI, Uri.encode(number)),
+                PHONE_LOOKUP_PROJECTION, null, null, null);
+    }
+
+    /**
+     * startContactQuery
+     *
+     * internal method to query contact by Uri.
+     *
+     * @param contactUri the contact uri
+     * @param resetQueryHandler whether to use a new AsyncQueryHandler or not
+     */
+    private void startContactQuery(Uri contactUri, boolean resetQueryHandler) {
+        if (resetQueryHandler) {
+            resetAsyncQueryHandler();
+        }
+
+        mQueryHandler.startQuery(TOKEN_CONTACT_INFO, contactUri, contactUri, ContactQuery.COLUMNS,
+                null, null, null);
+    }
+
+    /**
+     * startPhotoQuery
+     *
+     * internal method to query contact photo by photo id and uri.
+     *
+     * @param photoId the photo id.
+     * @param lookupKey the lookup uri.
+     * @param resetQueryHandler whether to use a new AsyncQueryHandler or not.
+     */
+    protected void startPhotoQuery(long photoId, Uri lookupKey, boolean resetQueryHandler) {
+        if (resetQueryHandler) {
+            resetAsyncQueryHandler();
+        }
+
+        mQueryHandler.startQuery(TOKEN_PHOTO_QUERY, lookupKey,
+                ContentUris.withAppendedId(Data.CONTENT_URI, photoId), PhotoQuery.COLUMNS,
+                null, null, null);
+    }
+
+    /**
+     * Method to force this widget to forget everything it knows about the contact.
+     * We need to stop any existing async queries for phone, email, contact, and photos.
+     */
+    public void wipeClean() {
+        resetAsyncQueryHandler();
+
+        setDisplayName(null, null);
+        setPhoto(loadPlaceholderPhoto(null));
+        setSocialSnippet(null);
+        setPresence(0);
+        mContactUri = null;
+        mExcludeMimes = null;
+    }
+
+
+    private void resetAsyncQueryHandler() {
+        // the api AsyncQueryHandler.cancelOperation() doesn't really work. Since we really
+        // need the old async queries to be cancelled, let's do it the hard way.
+        mQueryHandler = new QueryHandler(mContentResolver);
+    }
+
+    /**
+     * Bind the contact details provided by the given {@link Cursor}.
+     */
+    protected void bindContactInfo(Cursor c) {
+        final String displayName = c.getString(ContactQuery.DISPLAY_NAME);
+        final String phoneticName = c.getString(ContactQuery.PHONETIC_NAME);
+        this.setDisplayName(displayName, phoneticName);
+
+        final boolean starred = c.getInt(ContactQuery.STARRED) != 0;
+        setStared(starred);
+
+        //Set the presence status
+        if (!c.isNull(ContactQuery.CONTACT_PRESENCE_STATUS)) {
+            int presence = c.getInt(ContactQuery.CONTACT_PRESENCE_STATUS);
+            setPresence(presence);
+            showPresence(true);
+        } else {
+            showPresence(false);
+        }
+
+        //Set the status update
+        final String status = c.getString(ContactQuery.CONTACT_STATUS);
+        final Long statusTimestamp = c.isNull(ContactQuery.CONTACT_STATUS_TIMESTAMP)
+                ? null
+                : c.getLong(ContactQuery.CONTACT_STATUS_TIMESTAMP);
+        final Integer statusLabel = c.isNull(ContactQuery.CONTACT_STATUS_LABEL)
+                ? null
+                : c.getInt(ContactQuery.CONTACT_STATUS_LABEL);
+        final String statusResPackage = c.getString(ContactQuery.CONTACT_STATUS_RES_PACKAGE);
+
+        setStatus(status, statusTimestamp, statusLabel, statusResPackage);
+    }
+
+    public void onClick(View view) {
+        switch (view.getId()) {
+            case R.id.edit: {
+                performEditClick();
+                break;
+            }
+            case R.id.star: {
+                // Toggle "starred" state
+                // Make sure there is a contact
+                if (mContactUri != null) {
+                    final ContentValues values = new ContentValues(1);
+                    values.put(Contacts.STARRED, mStarredView.isChecked());
+                    mContentResolver.update(mContactUri, values, null, null);
+                }
+                break;
+            }
+            case R.id.photo: {
+                performPhotoClick();
+                break;
+            }
+            case R.id.name: {
+                performDisplayNameClick();
+                break;
+            }
+        }
+    }
+
+    private Bitmap loadPlaceholderPhoto(BitmapFactory.Options options) {
+        if (mNoPhotoResource == 0) {
+            return null;
+        }
+        return BitmapFactory.decodeResource(mContext.getResources(),
+                mNoPhotoResource, options);
+    }
+}
diff --git a/src/com/android/contacts/views/edit/ContactEditFragment.java b/src/com/android/contacts/views/edit/ContactEditFragment.java
deleted file mode 100644
index 488a1a5..0000000
--- a/src/com/android/contacts/views/edit/ContactEditFragment.java
+++ /dev/null
@@ -1,1324 +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.views.edit;
-
-import com.android.contacts.JoinContactActivity;
-import com.android.contacts.R;
-import com.android.contacts.model.ContactsSource;
-import com.android.contacts.model.Editor;
-import com.android.contacts.model.EntityDelta;
-import com.android.contacts.model.EntityModifier;
-import com.android.contacts.model.EntitySet;
-import com.android.contacts.model.GoogleSource;
-import com.android.contacts.model.Sources;
-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.ui.ViewIdGenerator;
-import com.android.contacts.ui.widget.BaseContactEditorView;
-import com.android.contacts.ui.widget.PhotoEditorView;
-import com.android.contacts.util.EmptyService;
-import com.android.contacts.util.WeakAsyncTask;
-
-import android.accounts.Account;
-import android.app.Activity;
-import android.app.AlertDialog;
-import android.app.Dialog;
-import android.app.LoaderManagingFragment;
-import android.app.ProgressDialog;
-import android.content.ActivityNotFoundException;
-import android.content.ContentProviderOperation;
-import android.content.ContentProviderResult;
-import android.content.ContentResolver;
-import android.content.ContentUris;
-import android.content.ContentValues;
-import android.content.Context;
-import android.content.DialogInterface;
-import android.content.Intent;
-import android.content.Loader;
-import android.content.OperationApplicationException;
-import android.content.ContentProviderOperation.Builder;
-import android.content.DialogInterface.OnDismissListener;
-import android.database.Cursor;
-import android.graphics.Bitmap;
-import android.media.MediaScannerConnection;
-import android.net.Uri;
-import android.os.AsyncTask;
-import android.os.Bundle;
-import android.os.Environment;
-import android.os.RemoteException;
-import android.provider.ContactsContract;
-import android.provider.MediaStore;
-import android.provider.ContactsContract.AggregationExceptions;
-import android.provider.ContactsContract.Contacts;
-import android.provider.ContactsContract.RawContacts;
-import android.provider.ContactsContract.CommonDataKinds.Email;
-import android.provider.ContactsContract.CommonDataKinds.Phone;
-import android.util.Log;
-import android.view.ContextThemeWrapper;
-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.View.OnClickListener;
-import android.widget.ArrayAdapter;
-import android.widget.LinearLayout;
-import android.widget.ListAdapter;
-import android.widget.TextView;
-import android.widget.Toast;
-
-import java.io.File;
-import java.lang.ref.WeakReference;
-import java.text.SimpleDateFormat;
-import java.util.ArrayList;
-import java.util.Collections;
-import java.util.Comparator;
-import java.util.Date;
-
-//Here are the open TODOs for the Fragment transition
-//TODO How to save data? Service?
-//TODO Do account-list lookup always in a thread
-//TODO Cleanup state handling (orientation changes etc).
-//TODO Cleanup the load function. It can currenlty also do insert, which is awkward
-//TODO Watch for background changes...How?
-
-public class ContactEditFragment extends LoaderManagingFragment<ContactEditLoader.Result> {
-
-    private static final String TAG = "ContactEditFragment";
-
-    private static final int LOADER_DATA = 1;
-
-    private static final String KEY_EDIT_STATE = "state";
-    private static final String KEY_RAW_CONTACT_ID_REQUESTING_PHOTO = "photorequester";
-    private static final String KEY_VIEW_ID_GENERATOR = "viewidgenerator";
-    private static final String KEY_CURRENT_PHOTO_FILE = "currentphotofile";
-    private static final String KEY_QUERY_SELECTION = "queryselection";
-    private static final String KEY_QUERY_SELECTION_ARGS = "queryselectionargs";
-    private static final String KEY_CONTACT_ID_FOR_JOIN = "contactidforjoin";
-
-    public static final int SAVE_MODE_DEFAULT = 0;
-    public static final int SAVE_MODE_SPLIT = 1;
-    public static final int SAVE_MODE_JOIN = 2;
-
-    private long mRawContactIdRequestingPhoto = -1;
-
-    private final EntityDeltaComparator mComparator = new EntityDeltaComparator();
-
-    private static final String BUNDLE_SELECT_ACCOUNT_LIST = "account_list";
-
-    private static final int ICON_SIZE = 96;
-
-    private static final File PHOTO_DIR = new File(
-            Environment.getExternalStorageDirectory() + "/DCIM/Camera");
-
-    private File mCurrentPhotoFile;
-
-    private Context mContext;
-    private String mAction;
-    private Uri mUri;
-    private String mMimeType;
-    private Bundle mIntentExtras;
-    private Callbacks mCallbacks;
-
-    private String mQuerySelection;
-    private String[] mQuerySelectionArgs;
-
-    private long mContactIdForJoin;
-
-    private LinearLayout mContent;
-    private EntitySet mState;
-
-    private ViewIdGenerator mViewIdGenerator;
-
-    public ContactEditFragment() {
-    }
-
-    @Override
-    public void onAttach(Activity activity) {
-        super.onAttach(activity);
-        mContext = activity;
-    }
-
-    @Override
-    public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedState) {
-        final View view = inflater.inflate(R.layout.contact_edit, container, false);
-
-        mContent = (LinearLayout) view.findViewById(R.id.editors);
-
-        view.findViewById(R.id.btn_done).setOnClickListener(new OnClickListener() {
-            public void onClick(View v) {
-                doSaveAction(SAVE_MODE_DEFAULT);
-            }
-        });
-        view.findViewById(R.id.btn_discard).setOnClickListener(new OnClickListener() {
-            public void onClick(View v) {
-                doRevertAction();
-            }
-        });
-
-        return view;
-    }
-
-    // TODO: Think about splitting this. Doing INSERT via load is kinda weird
-    public void load(String action, Uri uri, String mimeType, Bundle intentExtras) {
-        mAction = action;
-        mUri = uri;
-        mMimeType = mimeType;
-        mIntentExtras = intentExtras;
-
-        if (Intent.ACTION_EDIT.equals(mAction)) {
-            // Read initial state from database
-            if (mCallbacks != null) mCallbacks.setTitleTo(R.string.editContact_title_edit);
-            startLoading(LOADER_DATA, null);
-        } else if (Intent.ACTION_INSERT.equals(mAction)) {
-            if (mCallbacks != null) mCallbacks.setTitleTo(R.string.editContact_title_insert);
-
-            doAddAction();
-        } else throw new IllegalArgumentException("Unknown Action String " + mAction +
-                ". Only support " + Intent.ACTION_EDIT + " or " + Intent.ACTION_INSERT);
-    }
-
-    public void setCallbacks(Callbacks callbacks) {
-        mCallbacks = callbacks;
-    }
-
-    @Override
-    public void onCreate(Bundle savedState) {
-        // TODO: Currently savedState is always null (framework issue). Test once this is fixed
-        super.onCreate(savedState);
-
-        if (savedState == null) {
-            // If savedState is non-null, onRestoreInstanceState() will restore the generator.
-            mViewIdGenerator = new ViewIdGenerator();
-        } else {
-            // Read modifications from instance
-            mState = savedState.<EntitySet> getParcelable(KEY_EDIT_STATE);
-            mRawContactIdRequestingPhoto = savedState.getLong(
-                    KEY_RAW_CONTACT_ID_REQUESTING_PHOTO);
-            mViewIdGenerator = savedState.getParcelable(KEY_VIEW_ID_GENERATOR);
-            String fileName = savedState.getString(KEY_CURRENT_PHOTO_FILE);
-            if (fileName != null) {
-                mCurrentPhotoFile = new File(fileName);
-            }
-            mQuerySelection = savedState.getString(KEY_QUERY_SELECTION);
-            mQuerySelectionArgs = savedState.getStringArray(KEY_QUERY_SELECTION_ARGS);
-            mContactIdForJoin = savedState.getLong(KEY_CONTACT_ID_FOR_JOIN);
-        }
-    }
-
-    @Override
-    protected Loader<ContactEditLoader.Result> onCreateLoader(int id, Bundle args) {
-        return new ContactEditLoader(mContext, mUri, mMimeType, mIntentExtras);
-    }
-
-    @Override
-    protected void onInitializeLoaders() {
-    }
-
-    @Override
-    protected void onLoadFinished(Loader<ContactEditLoader.Result> loader,
-            ContactEditLoader.Result data) {
-        if (data == ContactEditLoader.Result.NOT_FOUND) {
-            // Item has been deleted
-            Log.i(TAG, "No contact found. Closing fragment");
-            if (mCallbacks != null) mCallbacks.closeBecauseContactNotFound();
-            return;
-        }
-        setData(data);
-    }
-
-    public void setData(ContactEditLoader.Result data) {
-        mState = data.getEntitySet();
-        bindEditors();
-    }
-
-    public void selectAccountAndCreateContact(ArrayList<Account> accounts, boolean isNewContact) {
-        // No Accounts available.  Create a phone-local contact.
-        if (accounts.isEmpty()) {
-            createContact(null, isNewContact);
-            return;  // Don't show a dialog.
-        }
-
-        // In the common case of a single account being writable, auto-select
-        // it without showing a dialog.
-        if (accounts.size() == 1) {
-            createContact(accounts.get(0), isNewContact);
-            return;  // Don't show a dialog.
-        }
-
-        Bundle bundle = new Bundle();
-        bundle.putParcelableArrayList(BUNDLE_SELECT_ACCOUNT_LIST, accounts);
-        getActivity().showDialog(R.id.edit_dialog_select_account, bundle);
-    }
-
-    /**
-     * @param account may be null to signal a device-local contact should
-     *     be created.
-     * @param prefillFromIntent If this is set, the intent extras will be used to prefill the fields
-     */
-    private void createContact(Account account, boolean prefillFromIntent) {
-        final Sources sources = Sources.getInstance(mContext);
-        final ContentValues values = new ContentValues();
-        if (account != null) {
-            values.put(RawContacts.ACCOUNT_NAME, account.name);
-            values.put(RawContacts.ACCOUNT_TYPE, account.type);
-        } else {
-            values.putNull(RawContacts.ACCOUNT_NAME);
-            values.putNull(RawContacts.ACCOUNT_TYPE);
-        }
-
-        // Parse any values from incoming intent
-        EntityDelta insert = new EntityDelta(ValuesDelta.fromAfter(values));
-        final ContactsSource source = sources.getInflatedSource(
-                account != null ? account.type : null,
-                ContactsSource.LEVEL_CONSTRAINTS);
-        EntityModifier.parseExtras(mContext, source, insert,
-                prefillFromIntent ? mIntentExtras : null);
-
-        // Ensure we have some default fields
-        EntityModifier.ensureKindExists(insert, source, Phone.CONTENT_ITEM_TYPE);
-        EntityModifier.ensureKindExists(insert, source, Email.CONTENT_ITEM_TYPE);
-
-        if (mState == null) {
-            // Create state if none exists yet
-            mState = EntitySet.fromSingle(insert);
-        } else {
-            // Add contact onto end of existing state
-            mState.add(insert);
-        }
-
-        bindEditors();
-    }
-
-    private void bindEditors() {
-        // Sort the editors
-        Collections.sort(mState, mComparator);
-
-        // Remove any existing editors and rebuild any visible
-        mContent.removeAllViews();
-
-        final LayoutInflater inflater = (LayoutInflater) mContext.getSystemService(
-                Context.LAYOUT_INFLATER_SERVICE);
-        final Sources sources = Sources.getInstance(mContext);
-        int size = mState.size();
-        for (int i = 0; i < size; i++) {
-            // TODO ensure proper ordering of entities in the list
-            final EntityDelta entity = mState.get(i);
-            final ValuesDelta values = entity.getValues();
-            if (!values.isVisible()) continue;
-
-            final String accountType = values.getAsString(RawContacts.ACCOUNT_TYPE);
-            final ContactsSource source = sources.getInflatedSource(accountType,
-                    ContactsSource.LEVEL_CONSTRAINTS);
-            final long rawContactId = values.getAsLong(RawContacts._ID);
-
-            final BaseContactEditorView editor;
-            if (!source.readOnly) {
-                editor = (BaseContactEditorView) inflater.inflate(R.layout.item_contact_editor,
-                        mContent, false);
-            } else {
-                editor = (BaseContactEditorView) inflater.inflate(
-                        R.layout.item_read_only_contact_editor, mContent, false);
-            }
-            final PhotoEditorView photoEditor = editor.getPhotoEditor();
-            photoEditor.setEditorListener(new PhotoListener(rawContactId, source.readOnly,
-                    photoEditor));
-
-            mContent.addView(editor);
-            editor.setState(entity, source, mViewIdGenerator);
-        }
-
-        // Show editor now that we've loaded state
-        mContent.setVisibility(View.VISIBLE);
-    }
-
-    public boolean onCreateOptionsMenu(Menu menu, final MenuInflater inflater) {
-        inflater.inflate(R.menu.edit, menu);
-
-        return true;
-    }
-
-    public boolean onPrepareOptionsMenu(Menu menu) {
-        menu.findItem(R.id.menu_split).setVisible(mState != null && mState.size() > 1);
-        return true;
-    }
-
-    public boolean onOptionsItemSelected(MenuItem item) {
-        switch (item.getItemId()) {
-            case R.id.menu_done:
-                return doSaveAction(SAVE_MODE_DEFAULT);
-            case R.id.menu_discard:
-                return doRevertAction();
-            case R.id.menu_add:
-                return doAddAction();
-            case R.id.menu_delete:
-                return doDeleteAction();
-            case R.id.menu_split:
-                return doSplitContactAction();
-            case R.id.menu_join:
-                return doJoinContactAction();
-        }
-        return false;
-    }
-
-    private boolean doAddAction() {
-        // Load Accounts async so that we can present them
-        AsyncTask<Void, Void, ArrayList<Account>> loadAccountsTask =
-                new AsyncTask<Void, Void, ArrayList<Account>>() {
-                    @Override
-                    protected ArrayList<Account> doInBackground(Void... params) {
-                        return Sources.getInstance(mContext).getAccounts(true);
-                    }
-                    @Override
-                    protected void onPostExecute(ArrayList<Account> result) {
-                        selectAccountAndCreateContact(result, true);
-                    }
-        };
-        loadAccountsTask.execute();
-
-        return true;
-    }
-
-    /**
-     * Delete the entire contact currently being edited, which usually asks for
-     * user confirmation before continuing.
-     */
-    private boolean doDeleteAction() {
-        if (!hasValidState())
-            return false;
-        int readOnlySourcesCnt = 0;
-        int writableSourcesCnt = 0;
-        // TODO: This shouldn't be called from the UI thread
-        final Sources sources = Sources.getInstance(mContext);
-        for (EntityDelta delta : mState) {
-            final String accountType = delta.getValues().getAsString(RawContacts.ACCOUNT_TYPE);
-            final ContactsSource contactsSource = sources.getInflatedSource(accountType,
-                    ContactsSource.LEVEL_CONSTRAINTS);
-            if (contactsSource != null && contactsSource.readOnly) {
-                readOnlySourcesCnt += 1;
-            } else {
-                writableSourcesCnt += 1;
-            }
-        }
-
-        if (readOnlySourcesCnt > 0 && writableSourcesCnt > 0) {
-            getActivity().showDialog(R.id.edit_dialog_confirm_readonly_delete);
-        } else if (readOnlySourcesCnt > 0 && writableSourcesCnt == 0) {
-            getActivity().showDialog(R.id.edit_dialog_confirm_readonly_hide);
-        } else if (readOnlySourcesCnt == 0 && writableSourcesCnt > 1) {
-            getActivity().showDialog(R.id.edit_dialog_confirm_multiple_delete);
-        } else {
-            getActivity().showDialog(R.id.edit_dialog_confirm_delete);
-        }
-        return true;
-    }
-
-    /**
-     * Pick a specific photo to be added under the currently selected tab.
-     */
-    /* package */ boolean doPickPhotoAction(long rawContactId) {
-        if (!hasValidState()) return false;
-
-        mRawContactIdRequestingPhoto = rawContactId;
-
-        getActivity().showDialog(R.id.edit_dialog_pick_photo);
-        return false;
-    }
-
-    public Dialog onCreateDialog(int id, Bundle bundle) {
-        switch (id) {
-            case R.id.edit_dialog_confirm_delete:
-                return new AlertDialog.Builder(mContext)
-                        .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, new DeleteClickListener())
-                        .setCancelable(false)
-                        .create();
-            case R.id.edit_dialog_confirm_readonly_delete:
-                return new AlertDialog.Builder(mContext)
-                        .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, new DeleteClickListener())
-                        .setCancelable(false)
-                        .create();
-            case R.id.edit_dialog_confirm_multiple_delete:
-                return new AlertDialog.Builder(mContext)
-                        .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, new DeleteClickListener())
-                        .setCancelable(false)
-                        .create();
-            case R.id.edit_dialog_confirm_readonly_hide:
-                return new AlertDialog.Builder(mContext)
-                        .setTitle(R.string.deleteConfirmation_title)
-                        .setIcon(android.R.drawable.ic_dialog_alert)
-                        .setMessage(R.string.readOnlyContactWarning)
-                        .setPositiveButton(android.R.string.ok, new DeleteClickListener())
-                        .setCancelable(false)
-                        .create();
-            case R.id.edit_dialog_pick_photo:
-                return createPickPhotoDialog();
-            case R.id.edit_dialog_split:
-                return createSplitDialog();
-            case R.id.edit_dialog_select_account:
-                return createSelectAccountDialog(bundle);
-            default:
-                return null;
-        }
-    }
-
-    private Dialog createSelectAccountDialog(Bundle bundle) {
-        final ArrayList<Account> accounts = bundle.getParcelableArrayList(
-                BUNDLE_SELECT_ACCOUNT_LIST);
-        // Wrap our context to inflate list items using correct theme
-        final Context dialogContext = new ContextThemeWrapper(mContext, android.R.style.Theme_Light);
-        final LayoutInflater dialogInflater =
-            (LayoutInflater)dialogContext.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
-
-        final Sources sources = Sources.getInstance(mContext);
-
-        final ArrayAdapter<Account> accountAdapter = new ArrayAdapter<Account>(mContext,
-                android.R.layout.simple_list_item_2, accounts) {
-            @Override
-            public View getView(int position, View convertView, ViewGroup parent) {
-                if (convertView == null) {
-                    convertView = dialogInflater.inflate(android.R.layout.simple_list_item_2,
-                            parent, false);
-                }
-
-                // TODO: show icon along with title
-                final TextView text1 = (TextView)convertView.findViewById(android.R.id.text1);
-                final TextView text2 = (TextView)convertView.findViewById(android.R.id.text2);
-
-                final Account account = this.getItem(position);
-                final ContactsSource source = sources.getInflatedSource(account.type,
-                        ContactsSource.LEVEL_SUMMARY);
-
-                text1.setText(account.name);
-                text2.setText(source.getDisplayLabel(mContext));
-
-                return convertView;
-            }
-        };
-
-        final DialogInterface.OnClickListener clickListener =
-                new DialogInterface.OnClickListener() {
-            public void onClick(DialogInterface dialog, int which) {
-                dialog.dismiss();
-
-                // Create new contact based on selected source
-                final Account account = accountAdapter.getItem(which);
-                createContact(account, false);
-            }
-        };
-
-        final DialogInterface.OnCancelListener cancelListener =
-                new DialogInterface.OnCancelListener() {
-            public void onCancel(DialogInterface dialog) {
-                // If nothing remains, close activity
-                if (!hasValidState()) {
-                    mCallbacks.closeBecauseAccountSelectorAborted();
-                }
-            }
-        };
-
-        final AlertDialog.Builder builder = new AlertDialog.Builder(mContext);
-        builder.setTitle(R.string.dialog_new_contact_account);
-        builder.setSingleChoiceItems(accountAdapter, 0, clickListener);
-        builder.setOnCancelListener(cancelListener);
-        final Dialog result = builder.create();
-        result.setOnDismissListener(new OnDismissListener() {
-            public void onDismiss(DialogInterface dialog) {
-                // TODO: Check if we even need this...seems useless
-                //removeDialog(DIALOG_SELECT_ACCOUNT);
-            }
-        });
-        return result;
-    }
-
-    private boolean doSplitContactAction() {
-        if (!hasValidState()) return false;
-
-        getActivity().showDialog(R.id.edit_dialog_split);
-        return true;
-    }
-
-    private Dialog createSplitDialog() {
-        final AlertDialog.Builder builder = new AlertDialog.Builder(mContext);
-        builder.setTitle(R.string.splitConfirmation_title);
-        builder.setIcon(android.R.drawable.ic_dialog_alert);
-        builder.setMessage(R.string.splitConfirmation);
-        builder.setPositiveButton(android.R.string.ok, new DialogInterface.OnClickListener() {
-            public void onClick(DialogInterface dialog, int which) {
-                // Split the contacts
-                mState.splitRawContacts();
-                doSaveAction(SAVE_MODE_SPLIT);
-            }
-        });
-        builder.setNegativeButton(android.R.string.cancel, null);
-        builder.setCancelable(false);
-        return builder.create();
-    }
-
-    private boolean doJoinContactAction() {
-        return doSaveAction(SAVE_MODE_JOIN);
-    }
-
-    /**
-     * Creates a dialog offering two options: take a photo or pick a photo from the gallery.
-     */
-    private Dialog createPickPhotoDialog() {
-        // Wrap our context to inflate list items using correct theme
-        final Context dialogContext = new ContextThemeWrapper(mContext,
-                android.R.style.Theme_Light);
-
-        String[] choices = new String[2];
-        choices[0] = mContext.getString(R.string.take_photo);
-        choices[1] = mContext.getString(R.string.pick_photo);
-        final ListAdapter adapter = new ArrayAdapter<String>(dialogContext,
-                android.R.layout.simple_list_item_1, choices);
-
-        final AlertDialog.Builder builder = new AlertDialog.Builder(dialogContext);
-        builder.setTitle(R.string.attachToContact);
-        builder.setSingleChoiceItems(adapter, -1, new DialogInterface.OnClickListener() {
-            public void onClick(DialogInterface dialog, int which) {
-                dialog.dismiss();
-                switch(which) {
-                    case 0:
-                        doTakePhoto();
-                        break;
-                    case 1:
-                        doPickPhotoFromGallery();
-                        break;
-                }
-            }
-        });
-        return builder.create();
-    }
-
-
-    /**
-     * Launches Gallery to pick a photo.
-     */
-    protected void doPickPhotoFromGallery() {
-        try {
-            // Launch picker to choose photo for selected contact
-            final Intent intent = getPhotoPickIntent();
-            getActivity().startActivityForResult(intent,
-                    R.id.edit_request_code_photo_picked_with_data);
-        } catch (ActivityNotFoundException e) {
-            Toast.makeText(mContext, R.string.photoPickerNotFoundText, Toast.LENGTH_LONG).show();
-        }
-    }
-
-    /**
-     * Constructs an intent for picking a photo from Gallery, cropping it and returning the bitmap.
-     */
-    public static Intent getPhotoPickIntent() {
-        Intent intent = new Intent(Intent.ACTION_GET_CONTENT, null);
-        intent.setType("image/*");
-        intent.putExtra("crop", "true");
-        intent.putExtra("aspectX", 1);
-        intent.putExtra("aspectY", 1);
-        intent.putExtra("outputX", ICON_SIZE);
-        intent.putExtra("outputY", ICON_SIZE);
-        intent.putExtra("return-data", true);
-        return intent;
-    }
-
-    /**
-     * Check if our internal {@link #mState} is valid, usually checked before
-     * performing user actions.
-     */
-    private boolean hasValidState() {
-        return mState != null && mState.size() > 0;
-    }
-
-    /**
-     * Create a file name for the icon photo using current time.
-     */
-    private String getPhotoFileName() {
-        Date date = new Date(System.currentTimeMillis());
-        SimpleDateFormat dateFormat = new SimpleDateFormat("'IMG'_yyyyMMdd_HHmmss");
-        return dateFormat.format(date) + ".jpg";
-    }
-
-    /**
-     * Launches Camera to take a picture and store it in a file.
-     */
-    protected void doTakePhoto() {
-        try {
-            // Launch camera to take photo for selected contact
-            PHOTO_DIR.mkdirs();
-            mCurrentPhotoFile = new File(PHOTO_DIR, getPhotoFileName());
-            final Intent intent = getTakePickIntent(mCurrentPhotoFile);
-
-            getActivity().startActivityForResult(intent, R.id.edit_request_code_camera_with_data);
-        } catch (ActivityNotFoundException e) {
-            Toast.makeText(mContext, R.string.photoPickerNotFoundText, Toast.LENGTH_LONG).show();
-        }
-    }
-
-    /**
-     * Constructs an intent for capturing a photo and storing it in a temporary file.
-     */
-    public static Intent getTakePickIntent(File f) {
-        Intent intent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE, null);
-        intent.putExtra(MediaStore.EXTRA_OUTPUT, Uri.fromFile(f));
-        return intent;
-    }
-
-    /**
-     * Sends a newly acquired photo to Gallery for cropping
-     */
-    protected void doCropPhoto(File f) {
-        try {
-            // Add the image to the media store
-            MediaScannerConnection.scanFile(
-                    mContext,
-                    new String[] { f.getAbsolutePath() },
-                    new String[] { null },
-                    null);
-
-            // Launch gallery to crop the photo
-            final Intent intent = getCropImageIntent(Uri.fromFile(f));
-            getActivity().startActivityForResult(intent,
-                    R.id.edit_request_code_photo_picked_with_data);
-        } catch (Exception e) {
-            Log.e(TAG, "Cannot crop image", e);
-            Toast.makeText(mContext, R.string.photoPickerNotFoundText, Toast.LENGTH_LONG).show();
-        }
-    }
-
-    /**
-     * Constructs an intent for image cropping.
-     */
-    public static Intent getCropImageIntent(Uri photoUri) {
-        Intent intent = new Intent("com.android.camera.action.CROP");
-        intent.setDataAndType(photoUri, "image/*");
-        intent.putExtra("crop", "true");
-        intent.putExtra("aspectX", 1);
-        intent.putExtra("aspectY", 1);
-        intent.putExtra("outputX", ICON_SIZE);
-        intent.putExtra("outputY", ICON_SIZE);
-        intent.putExtra("return-data", true);
-        return intent;
-    }
-
-    /**
-     * Saves or creates the contact based on the mode, and if successful
-     * finishes the activity.
-     */
-    private boolean doSaveAction(int saveMode) {
-        if (!hasValidState()) {
-            return false;
-        }
-
-        // TODO: Status still needed?
-        //mStatus = STATUS_SAVING;
-        final PersistTask task = new PersistTask(this, saveMode);
-        task.execute(mState);
-
-        return true;
-    }
-
-    private boolean doRevertAction() {
-        if (mCallbacks != null) mCallbacks.closeAfterRevert();
-
-        return true;
-    }
-
-    private void onSaveCompleted(boolean success, int saveMode, Uri contactLookupUri) {
-        switch (saveMode) {
-            case SAVE_MODE_DEFAULT:
-                final Intent resultIntent;
-                final int resultCode;
-                if (success && contactLookupUri != null) {
-                    final String requestAuthority = mUri == null ? null : mUri.getAuthority();
-
-                    final String legacyAuthority = "contacts";
-
-                    resultIntent = new Intent();
-                    if (legacyAuthority.equals(requestAuthority)) {
-                        // Build legacy Uri when requested by caller
-                        final long contactId = ContentUris.parseId(Contacts.lookupContact(
-                                mContext.getContentResolver(), contactLookupUri));
-                        final Uri legacyContentUri = Uri.parse("content://contacts/people");
-                        final Uri legacyUri = ContentUris.withAppendedId(
-                                legacyContentUri, contactId);
-                        resultIntent.setData(legacyUri);
-                    } else {
-                        // Otherwise pass back a lookup-style Uri
-                        resultIntent.setData(contactLookupUri);
-                    }
-
-                    resultCode = Activity.RESULT_OK;
-                } else {
-                    resultCode = Activity.RESULT_CANCELED;
-                    resultIntent = null;
-                }
-                if (mCallbacks != null) mCallbacks.closeAfterSaving(resultCode, resultIntent);
-                break;
-            case SAVE_MODE_SPLIT:
-                if (mCallbacks != null) mCallbacks.closeAfterSplit();
-                break;
-
-            case SAVE_MODE_JOIN:
-                //mStatus = STATUS_EDITING;
-                if (success) {
-                    showJoinAggregateActivity(contactLookupUri);
-                }
-                break;
-        }
-    }
-
-    /**
-     * Shows a list of aggregates that can be joined into the currently viewed aggregate.
-     *
-     * @param contactLookupUri the fresh URI for the currently edited contact (after saving it)
-     */
-    private void showJoinAggregateActivity(Uri contactLookupUri) {
-        if (contactLookupUri == null) {
-            return;
-        }
-
-        mContactIdForJoin = ContentUris.parseId(contactLookupUri);
-        final Intent intent = new Intent(JoinContactActivity.JOIN_CONTACT);
-        intent.putExtra(JoinContactActivity.EXTRA_TARGET_CONTACT_ID, mContactIdForJoin);
-        getActivity().startActivityForResult(intent, R.id.edit_request_code_join);
-    }
-
-    private interface JoinContactQuery {
-        String[] PROJECTION = {
-                RawContacts._ID,
-                RawContacts.CONTACT_ID,
-                RawContacts.NAME_VERIFIED,
-        };
-
-        String SELECTION = RawContacts.CONTACT_ID + "=? OR " + RawContacts.CONTACT_ID + "=?";
-
-        int _ID = 0;
-        int CONTACT_ID = 1;
-        int NAME_VERIFIED = 2;
-    }
-
-    /**
-     * Performs aggregation with the contact selected by the user from suggestions or A-Z list.
-     */
-    private void joinAggregate(final long contactId) {
-        final ContentResolver resolver = mContext.getContentResolver();
-
-        // Load raw contact IDs for all raw contacts involved - currently edited and selected
-        // in the join UIs
-        Cursor c = resolver.query(RawContacts.CONTENT_URI,
-                JoinContactQuery.PROJECTION,
-                JoinContactQuery.SELECTION,
-                new String[]{String.valueOf(contactId), String.valueOf(mContactIdForJoin)}, null);
-
-        long rawContactIds[];
-        long verifiedNameRawContactId = -1;
-        try {
-            rawContactIds = new long[c.getCount()];
-            for (int i = 0; i < rawContactIds.length; i++) {
-                c.moveToNext();
-                long rawContactId = c.getLong(JoinContactQuery._ID);
-                rawContactIds[i] = rawContactId;
-                if (c.getLong(JoinContactQuery.CONTACT_ID) == mContactIdForJoin) {
-                    if (verifiedNameRawContactId == -1
-                            || c.getInt(JoinContactQuery.NAME_VERIFIED) != 0) {
-                        verifiedNameRawContactId = rawContactId;
-                    }
-                }
-            }
-        } finally {
-            c.close();
-        }
-
-        // For each pair of raw contacts, insert an aggregation exception
-        ArrayList<ContentProviderOperation> operations = new ArrayList<ContentProviderOperation>();
-        for (int i = 0; i < rawContactIds.length; i++) {
-            for (int j = 0; j < rawContactIds.length; j++) {
-                if (i != j) {
-                    buildJoinContactDiff(operations, rawContactIds[i], rawContactIds[j]);
-                }
-            }
-        }
-
-        // Mark the original contact as "name verified" to make sure that the contact
-        // display name does not change as a result of the join
-        Builder builder = ContentProviderOperation.newUpdate(
-                    ContentUris.withAppendedId(RawContacts.CONTENT_URI, verifiedNameRawContactId));
-        builder.withValue(RawContacts.NAME_VERIFIED, 1);
-        operations.add(builder.build());
-
-        // Apply all aggregation exceptions as one batch
-        try {
-            resolver.applyBatch(ContactsContract.AUTHORITY, operations);
-
-            // We can use any of the constituent raw contacts to refresh the UI - why not the first
-            final Intent intent = new Intent();
-            intent.setData(ContentUris.withAppendedId(RawContacts.CONTENT_URI, rawContactIds[0]));
-
-            // Reload the new state from database
-            // TODO: Reload necessary or do we have a listener?
-            //new QueryEntitiesTask(this).execute(intent);
-
-            Toast.makeText(mContext, R.string.contactsJoinedMessage, Toast.LENGTH_LONG).show();
-        } catch (RemoteException e) {
-            Log.e(TAG, "Failed to apply aggregation exception batch", e);
-            Toast.makeText(mContext, R.string.contactSavedErrorToast, Toast.LENGTH_LONG).show();
-        } catch (OperationApplicationException e) {
-            Log.e(TAG, "Failed to apply aggregation exception batch", e);
-            Toast.makeText(mContext, R.string.contactSavedErrorToast, Toast.LENGTH_LONG).show();
-        }
-    }
-
-    /**
-     * Construct a {@link AggregationExceptions#TYPE_KEEP_TOGETHER} ContentProviderOperation.
-     */
-    private void buildJoinContactDiff(ArrayList<ContentProviderOperation> operations,
-            long rawContactId1, long rawContactId2) {
-        Builder builder =
-                ContentProviderOperation.newUpdate(AggregationExceptions.CONTENT_URI);
-        builder.withValue(AggregationExceptions.TYPE, AggregationExceptions.TYPE_KEEP_TOGETHER);
-        builder.withValue(AggregationExceptions.RAW_CONTACT_ID1, rawContactId1);
-        builder.withValue(AggregationExceptions.RAW_CONTACT_ID2, rawContactId2);
-        operations.add(builder.build());
-    }
-
-    public static interface Callbacks {
-        /**
-         * Contact was not found, so somehow close this fragment.
-         */
-        void closeBecauseContactNotFound();
-
-        /**
-         * Contact was split, so we can close now
-         */
-        void closeAfterSplit();
-
-        /**
-         * User was presented with an account selection and couldn't decide.
-         */
-        void closeBecauseAccountSelectorAborted();
-
-        /**
-         * User has tapped Revert, close the fragment now.
-         */
-        void closeAfterRevert();
-
-        /**
-         * User has removed the contact, close the fragment now.
-         */
-        void closeAfterDelete();
-
-        /**
-         * Set the Title (e.g. of the Activity)
-         */
-        void setTitleTo(int resourceId);
-
-        /**
-         * Contact was
-         * @param resultCode
-         * @param resultIntent
-         */
-        void closeAfterSaving(int resultCode, Intent resultIntent);
-    }
-
-    private class EntityDeltaComparator implements Comparator<EntityDelta> {
-        /**
-         * Compare EntityDeltas for sorting the stack of editors.
-         */
-        public int compare(EntityDelta one, EntityDelta two) {
-            // Check direct equality
-            if (one.equals(two)) {
-                return 0;
-            }
-
-            final Sources sources = Sources.getInstance(mContext);
-            String accountType = one.getValues().getAsString(RawContacts.ACCOUNT_TYPE);
-            final ContactsSource oneSource = sources.getInflatedSource(accountType,
-                    ContactsSource.LEVEL_SUMMARY);
-            accountType = two.getValues().getAsString(RawContacts.ACCOUNT_TYPE);
-            final ContactsSource twoSource = sources.getInflatedSource(accountType,
-                    ContactsSource.LEVEL_SUMMARY);
-
-            // Check read-only
-            if (oneSource.readOnly && !twoSource.readOnly) {
-                return 1;
-            } else if (twoSource.readOnly && !oneSource.readOnly) {
-                return -1;
-            }
-
-            // Check account type
-            boolean skipAccountTypeCheck = false;
-            boolean oneIsGoogle = oneSource instanceof GoogleSource;
-            boolean twoIsGoogle = twoSource instanceof GoogleSource;
-            if (oneIsGoogle && !twoIsGoogle) {
-                return -1;
-            } else if (twoIsGoogle && !oneIsGoogle) {
-                return 1;
-            } else if (oneIsGoogle && twoIsGoogle){
-                skipAccountTypeCheck = true;
-            }
-
-            int value;
-            if (!skipAccountTypeCheck) {
-                value = oneSource.accountType.compareTo(twoSource.accountType);
-                if (value != 0) {
-                    return value;
-                }
-            }
-
-            // Check account name
-            ValuesDelta oneValues = one.getValues();
-            String oneAccount = oneValues.getAsString(RawContacts.ACCOUNT_NAME);
-            if (oneAccount == null) oneAccount = "";
-            ValuesDelta twoValues = two.getValues();
-            String twoAccount = twoValues.getAsString(RawContacts.ACCOUNT_NAME);
-            if (twoAccount == null) twoAccount = "";
-            value = oneAccount.compareTo(twoAccount);
-            if (value != 0) {
-                return value;
-            }
-
-            // Both are in the same account, fall back to contact ID
-            Long oneId = oneValues.getAsLong(RawContacts._ID);
-            Long twoId = twoValues.getAsLong(RawContacts._ID);
-            if (oneId == null) {
-                return -1;
-            } else if (twoId == null) {
-                return 1;
-            }
-
-            return (int)(oneId - twoId);
-        }
-    }
-
-    /**
-     * Class that listens to requests coming from photo editors
-     */
-    private class PhotoListener implements EditorListener, DialogInterface.OnClickListener {
-        private long mRawContactId;
-        private boolean mReadOnly;
-        private PhotoEditorView mEditor;
-
-        public PhotoListener(long rawContactId, boolean readOnly, PhotoEditorView editor) {
-            mRawContactId = rawContactId;
-            mReadOnly = readOnly;
-            mEditor = editor;
-        }
-
-        public void onDeleted(Editor editor) {
-            // Do nothing
-        }
-
-        public void onRequest(int request) {
-            if (!hasValidState()) return;
-
-            if (request == EditorListener.REQUEST_PICK_PHOTO) {
-                if (mEditor.hasSetPhoto()) {
-                    // There is an existing photo, offer to remove, replace, or promoto to primary
-                    createPhotoDialog().show();
-                } else if (!mReadOnly) {
-                    // No photo set and not read-only, try to set the photo
-                    doPickPhotoAction(mRawContactId);
-                }
-            }
-        }
-
-        /**
-         * Prepare dialog for picking a new {@link EditType} or entering a
-         * custom label. This dialog is limited to the valid types as determined
-         * by {@link EntityModifier}.
-         */
-        public Dialog createPhotoDialog() {
-            // Wrap our context to inflate list items using correct theme
-            final Context dialogContext = new ContextThemeWrapper(mContext,
-                    android.R.style.Theme_Light);
-
-            String[] choices;
-            if (mReadOnly) {
-                choices = new String[1];
-                choices[0] = mContext.getString(R.string.use_photo_as_primary);
-            } else {
-                choices = new String[3];
-                choices[0] = mContext.getString(R.string.use_photo_as_primary);
-                choices[1] = mContext.getString(R.string.removePicture);
-                choices[2] = mContext.getString(R.string.changePicture);
-            }
-            final ListAdapter adapter = new ArrayAdapter<String>(dialogContext,
-                    android.R.layout.simple_list_item_1, choices);
-
-            final AlertDialog.Builder builder = new AlertDialog.Builder(dialogContext);
-            builder.setTitle(R.string.attachToContact);
-            builder.setSingleChoiceItems(adapter, -1, this);
-            return builder.create();
-        }
-
-        /**
-         * Called when something in the dialog is clicked
-         */
-        public void onClick(DialogInterface dialog, int which) {
-            dialog.dismiss();
-
-            switch (which) {
-                case 0:
-                    // Set the photo as super primary
-                    mEditor.setSuperPrimary(true);
-
-                    // And set all other photos as not super primary
-                    int count = mContent.getChildCount();
-                    for (int i = 0; i < count; i++) {
-                        View childView = mContent.getChildAt(i);
-                        if (childView instanceof BaseContactEditorView) {
-                            BaseContactEditorView editor = (BaseContactEditorView) childView;
-                            PhotoEditorView photoEditor = editor.getPhotoEditor();
-                            if (!photoEditor.equals(mEditor)) {
-                                photoEditor.setSuperPrimary(false);
-                            }
-                        }
-                    }
-                    break;
-
-                case 1:
-                    // Remove the photo
-                    mEditor.setPhotoBitmap(null);
-                    break;
-
-                case 2:
-                    // Pick a new photo for the contact
-                    doPickPhotoAction(mRawContactId);
-                    break;
-            }
-        }
-    }
-
-
-    private class DeleteClickListener implements DialogInterface.OnClickListener {
-        public void onClick(DialogInterface dialog, int which) {
-            // TODO: Don't do this from the UI thread
-            final Sources sources = Sources.getInstance(mContext);
-            // Mark all raw contacts for deletion
-            for (EntityDelta delta : mState) {
-                delta.markDeleted();
-            }
-            // Save the deletes
-            doSaveAction(SAVE_MODE_DEFAULT);
-            mCallbacks.closeAfterDelete();
-        }
-    }
-
-    // TODO: There has to be a nicer way than this WeakAsyncTask...? Maybe call a service?
-    /**
-     * Background task for persisting edited contact data, using the changes
-     * defined by a set of {@link EntityDelta}. This task starts
-     * {@link EmptyService} to make sure the background thread can finish
-     * persisting in cases where the system wants to reclaim our process.
-     */
-    public static class PersistTask extends
-            WeakAsyncTask<EntitySet, Void, Integer, ContactEditFragment> {
-        private static final int PERSIST_TRIES = 3;
-
-        private static final int RESULT_UNCHANGED = 0;
-        private static final int RESULT_SUCCESS = 1;
-        private static final int RESULT_FAILURE = 2;
-
-        private WeakReference<ProgressDialog> mProgress;
-        private final Context mContext;
-
-        private int mSaveMode;
-        private Uri mContactLookupUri = null;
-
-        public PersistTask(ContactEditFragment target, int saveMode) {
-            super(target);
-            mSaveMode = saveMode;
-            mContext = target.mContext;
-        }
-
-        /** {@inheritDoc} */
-        @Override
-        protected void onPreExecute(ContactEditFragment target) {
-            mProgress = new WeakReference<ProgressDialog>(ProgressDialog.show(mContext, null,
-                    mContext.getText(R.string.savingContact)));
-
-            // Before starting this task, start an empty service to protect our
-            // process from being reclaimed by the system.
-            mContext.startService(new Intent(mContext, EmptyService.class));
-        }
-
-        /** {@inheritDoc} */
-        @Override
-        protected Integer doInBackground(ContactEditFragment target, EntitySet... params) {
-            final ContentResolver resolver = mContext.getContentResolver();
-
-            EntitySet state = params[0];
-
-            // Trim any empty fields, and RawContacts, before persisting
-            final Sources sources = Sources.getInstance(mContext);
-            EntityModifier.trimEmpty(state, sources);
-
-            // Attempt to persist changes
-            int tries = 0;
-            Integer result = RESULT_FAILURE;
-            while (tries++ < PERSIST_TRIES) {
-                try {
-                    // Build operations and try applying
-                    final ArrayList<ContentProviderOperation> diff = state.buildDiff();
-                    ContentProviderResult[] results = null;
-                    if (!diff.isEmpty()) {
-                         results = resolver.applyBatch(ContactsContract.AUTHORITY, diff);
-                    }
-
-                    final long rawContactId = getRawContactId(state, diff, results);
-                    if (rawContactId != -1) {
-                        final Uri rawContactUri = ContentUris.withAppendedId(
-                                RawContacts.CONTENT_URI, rawContactId);
-
-                        // convert the raw contact URI to a contact URI
-                        mContactLookupUri = RawContacts.getContactLookupUri(resolver,
-                                rawContactUri);
-                    }
-                    result = (diff.size() > 0) ? RESULT_SUCCESS : RESULT_UNCHANGED;
-                    break;
-
-                } catch (RemoteException e) {
-                    // Something went wrong, bail without success
-                    Log.e(TAG, "Problem persisting user edits", e);
-                    break;
-
-                } catch (OperationApplicationException e) {
-                    // Version consistency failed, re-parent change and try again
-                    Log.w(TAG, "Version consistency failed, re-parenting: " + e.toString());
-                    final EntitySet newState = EntitySet.fromQuery(resolver,
-                            target.mQuerySelection, target.mQuerySelectionArgs, null);
-                    state = EntitySet.mergeAfter(newState, state);
-                }
-            }
-
-            return result;
-        }
-
-        private long getRawContactId(EntitySet state,
-                final ArrayList<ContentProviderOperation> diff,
-                final ContentProviderResult[] results) {
-            long rawContactId = state.findRawContactId();
-            if (rawContactId != -1) {
-                return rawContactId;
-            }
-
-            // we gotta do some searching for the id
-            final int diffSize = diff.size();
-            for (int i = 0; i < diffSize; i++) {
-                ContentProviderOperation operation = diff.get(i);
-                if (operation.getType() == ContentProviderOperation.TYPE_INSERT
-                        && operation.getUri().getEncodedPath().contains(
-                                RawContacts.CONTENT_URI.getEncodedPath())) {
-                    return ContentUris.parseId(results[i].uri);
-                }
-            }
-            return -1;
-        }
-
-        /** {@inheritDoc} */
-        @Override
-        protected void onPostExecute(ContactEditFragment target, Integer result) {
-            final ProgressDialog progress = mProgress.get();
-
-            if (result == RESULT_SUCCESS && mSaveMode != SAVE_MODE_JOIN) {
-                Toast.makeText(mContext, R.string.contactSavedToast, Toast.LENGTH_SHORT).show();
-            } else if (result == RESULT_FAILURE) {
-                Toast.makeText(mContext, R.string.contactSavedErrorToast, Toast.LENGTH_LONG).show();
-            }
-
-            if (progress != null) progress.dismiss();
-
-            // Stop the service that was protecting us
-            mContext.stopService(new Intent(mContext, EmptyService.class));
-
-            target.onSaveCompleted(result != RESULT_FAILURE, mSaveMode, mContactLookupUri);
-        }
-    }
-
-    @Override
-    public void onSaveInstanceState(Bundle outState) {
-        if (hasValidState()) {
-            // Store entities with modifications
-            outState.putParcelable(KEY_EDIT_STATE, mState);
-        }
-
-        outState.putLong(KEY_RAW_CONTACT_ID_REQUESTING_PHOTO, mRawContactIdRequestingPhoto);
-        outState.putParcelable(KEY_VIEW_ID_GENERATOR, mViewIdGenerator);
-        if (mCurrentPhotoFile != null) {
-            outState.putString(KEY_CURRENT_PHOTO_FILE, mCurrentPhotoFile.toString());
-        }
-        outState.putString(KEY_QUERY_SELECTION, mQuerySelection);
-        outState.putStringArray(KEY_QUERY_SELECTION_ARGS, mQuerySelectionArgs);
-        outState.putLong(KEY_CONTACT_ID_FOR_JOIN, mContactIdForJoin);
-        super.onSaveInstanceState(outState);
-    }
-
-    public void onActivityResult(int requestCode, int resultCode, Intent data) {
-        // Ignore failed requests
-        if (resultCode != Activity.RESULT_OK) return;
-        switch (requestCode) {
-            case R.id.edit_request_code_photo_picked_with_data: {
-                BaseContactEditorView requestingEditor = null;
-                for (int i = 0; i < mContent.getChildCount(); i++) {
-                    View childView = mContent.getChildAt(i);
-                    if (childView instanceof BaseContactEditorView) {
-                        BaseContactEditorView editor = (BaseContactEditorView) childView;
-                        if (editor.getRawContactId() == mRawContactIdRequestingPhoto) {
-                            requestingEditor = editor;
-                            break;
-                        }
-                    }
-                }
-
-                if (requestingEditor != null) {
-                    final Bitmap photo = data.getParcelableExtra("data");
-                    requestingEditor.setPhotoBitmap(photo);
-                    mRawContactIdRequestingPhoto = -1;
-                } else {
-                    // The contact that requested the photo is no longer present.
-                    // TODO: Show error message
-                }
-
-                break;
-            }
-
-            case R.id.edit_request_code_camera_with_data: {
-                doCropPhoto(mCurrentPhotoFile);
-                break;
-            }
-            case R.id.edit_request_code_join: {
-                if (data != null) {
-                    final long contactId = ContentUris.parseId(data.getData());
-                    joinAggregate(contactId);
-                }
-            }
-        }
-    }
-}
diff --git a/src/com/android/contacts/views/edit/ContactEditLoader.java b/src/com/android/contacts/views/edit/ContactEditLoader.java
deleted file mode 100644
index b89273c..0000000
--- a/src/com/android/contacts/views/edit/ContactEditLoader.java
+++ /dev/null
@@ -1,177 +0,0 @@
-package com.android.contacts.views.edit;
-
-import com.android.contacts.ContactsUtils;
-import com.android.contacts.model.ContactsSource;
-import com.android.contacts.model.EntityDelta;
-import com.android.contacts.model.EntityModifier;
-import com.android.contacts.model.EntitySet;
-import com.android.contacts.model.Sources;
-
-import android.content.ContentResolver;
-import android.content.ContentUris;
-import android.content.Context;
-import android.content.Loader;
-import android.net.Uri;
-import android.os.AsyncTask;
-import android.os.Bundle;
-import android.provider.ContactsContract;
-import android.provider.ContactsContract.Contacts;
-import android.provider.ContactsContract.RawContacts;
-
-public class ContactEditLoader extends Loader<ContactEditLoader.Result> {
-    private static final String TAG = "ContactEditLoader";
-
-    private final Uri mLookupUri;
-    private final String mMimeType;
-    private Result mContact;
-    private boolean mDestroyed;
-    private ForceLoadContentObserver mObserver;
-    private final Bundle mIntentExtras;
-
-    public ContactEditLoader(Context context, Uri lookupUri, String mimeType,
-            Bundle intentExtras) {
-        super(context);
-        mLookupUri = lookupUri;
-        mMimeType = mimeType;
-        mIntentExtras = intentExtras;
-    }
-
-    /**
-     * The result of a load operation. Contains all data necessary to display the contact for
-     * editing.
-     */
-    public static class Result {
-        /**
-         * Singleton instance that represents "No Contact Found"
-         */
-        public static final Result NOT_FOUND = new Result(null);
-
-        private final EntitySet mEntitySet;
-
-        private Result(EntitySet entitySet) {
-            mEntitySet = entitySet;
-        }
-
-        public EntitySet getEntitySet() {
-            return mEntitySet;
-        }
-    }
-
-    private final class LoadContactTask extends AsyncTask<Void, Void, Result> {
-        @Override
-        protected Result doInBackground(Void... params) {
-            final ContentResolver resolver = getContext().getContentResolver();
-            final Uri uriCurrentFormat = convertLegacyIfNecessary(mLookupUri);
-
-            // Handle both legacy and new authorities
-
-            final long contactId;
-            final String selection = "0";
-            if (Contacts.CONTENT_ITEM_TYPE.equals(mMimeType)) {
-                // Handle selected aggregate
-                contactId = ContentUris.parseId(uriCurrentFormat);
-            } else if (RawContacts.CONTENT_ITEM_TYPE.equals(mMimeType)) {
-                // Get id of corresponding aggregate
-                final long rawContactId = ContentUris.parseId(uriCurrentFormat);
-                contactId = ContactsUtils.queryForContactId(resolver, rawContactId);
-            } else throw new IllegalStateException();
-
-            return new Result(EntitySet.fromQuery(resolver, RawContacts.CONTACT_ID + "=?",
-                    new String[] { String.valueOf(contactId) }, null));
-        }
-
-        /**
-         * Transforms the given Uri and returns a Lookup-Uri that represents the contact.
-         * For legacy contacts, a raw-contact lookup is performed.
-         */
-        private Uri convertLegacyIfNecessary(Uri uri) {
-            if (uri == null) throw new IllegalArgumentException("uri must not be null");
-
-            final String authority = uri.getAuthority();
-
-            // Current Style Uri? Just return it
-            if (ContactsContract.AUTHORITY.equals(authority)) {
-                return uri;
-            }
-
-            // Legacy Style? Convert to RawContact
-            final String OBSOLETE_AUTHORITY = "contacts";
-            if (OBSOLETE_AUTHORITY.equals(authority)) {
-                // Legacy Format. Convert to RawContact-Uri and then lookup the contact
-                final long rawContactId = ContentUris.parseId(uri);
-                return RawContacts.getContactLookupUri(getContext().getContentResolver(),
-                        ContentUris.withAppendedId(RawContacts.CONTENT_URI, rawContactId));
-            }
-
-            throw new IllegalArgumentException("uri format is unknown");
-        }
-
-        @Override
-        protected void onPostExecute(Result result) {
-            super.onPostExecute(result);
-
-            // TODO: This merging of extras is probably wrong on subsequent loads
-
-            // Load edit details in background
-            final Sources sources = Sources.getInstance(getContext());
-
-            // Handle any incoming values that should be inserted
-            final boolean hasExtras = mIntentExtras != null && mIntentExtras.size() > 0;
-            final boolean hasState = result.getEntitySet().size() > 0;
-            if (hasExtras && hasState) {
-                // Find source defining the first RawContact found
-                final EntityDelta state = result.getEntitySet().get(0);
-                final String accountType = state.getValues().getAsString(RawContacts.ACCOUNT_TYPE);
-                final ContactsSource source = sources.getInflatedSource(accountType,
-                        ContactsSource.LEVEL_CONSTRAINTS);
-                EntityModifier.parseExtras(getContext(), source, state, mIntentExtras);
-            }
-
-            // The creator isn't interested in any further updates
-            if (mDestroyed) {
-                return;
-            }
-
-            mContact = result;
-            if (result != null) {
-                if (mObserver == null) {
-                    mObserver = new ForceLoadContentObserver();
-                }
-                // TODO: Do we want a content observer here?
-//                Log.i(TAG, "Registering content observer for " + mLookupUri);
-//                getContext().getContentResolver().registerContentObserver(mLookupUri, true,
-//                        mObserver);
-                deliverResult(result);
-            }
-        }
-    }
-
-    @Override
-    public void startLoading() {
-        if (mContact != null) {
-            deliverResult(mContact);
-        } else {
-            forceLoad();
-        }
-    }
-
-    @Override
-    public void forceLoad() {
-        LoadContactTask task = new LoadContactTask();
-        task.execute((Void[])null);
-    }
-
-    @Override
-    public void stopLoading() {
-        mContact = null;
-        if (mObserver != null) {
-            getContext().getContentResolver().unregisterContentObserver(mObserver);
-        }
-    }
-
-    @Override
-    public void destroy() {
-        mContact = null;
-        mDestroyed = true;
-    }
-}
diff --git a/src/com/android/contacts/views/editor/ContactEditorFragment.java b/src/com/android/contacts/views/editor/ContactEditorFragment.java
new file mode 100644
index 0000000..8626939
--- /dev/null
+++ b/src/com/android/contacts/views/editor/ContactEditorFragment.java
@@ -0,0 +1,1041 @@
+/*
+ * 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.views.editor;
+
+import com.android.contacts.ContactOptionsActivity;
+import com.android.contacts.R;
+import com.android.contacts.TypePrecedence;
+import com.android.contacts.activities.ContactFieldEditorActivity;
+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.views.ContactLoader;
+
+import android.app.Activity;
+import android.app.AlertDialog;
+import android.app.Dialog;
+import android.app.LoaderManagingFragment;
+import android.content.ActivityNotFoundException;
+import android.content.ContentUris;
+import android.content.ContentValues;
+import android.content.Context;
+import android.content.DialogInterface;
+import android.content.Entity;
+import android.content.Intent;
+import android.content.Loader;
+import android.content.Entity.NamedContentValues;
+import android.content.res.Resources;
+import android.graphics.Bitmap;
+import android.graphics.BitmapFactory;
+import android.net.Uri;
+import android.os.Bundle;
+import android.provider.ContactsContract.CommonDataKinds;
+import android.provider.ContactsContract.Contacts;
+import android.provider.ContactsContract.Data;
+import android.provider.ContactsContract.RawContacts;
+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.Photo;
+import android.provider.ContactsContract.CommonDataKinds.StructuredName;
+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.LayoutInflater;
+import android.view.Menu;
+import android.view.MenuInflater;
+import android.view.MenuItem;
+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.BaseAdapter;
+import android.widget.Button;
+import android.widget.ImageView;
+import android.widget.ListView;
+import android.widget.TextView;
+import android.widget.Toast;
+import android.widget.AdapterView.OnItemClickListener;
+
+import java.util.ArrayList;
+import java.util.zip.Inflater;
+
+public class ContactEditorFragment extends LoaderManagingFragment<ContactLoader.Result>
+        implements OnCreateContextMenuListener, OnItemClickListener {
+    private static final String TAG = "ContactEditorFragment";
+
+    private static final String BUNDLE_RAW_CONTACT_ID = "rawContactId";
+
+    private static final int MENU_ITEM_MAKE_DEFAULT = 3;
+
+    private static final int LOADER_DETAILS = 1;
+
+    private Context mContext;
+    private Uri mLookupUri;
+    private Listener mListener;
+
+    private ContactLoader.Result mContactData;
+    private ContactEditorHeaderView mHeaderView;
+    private ListView mListView;
+    private ViewAdapter mAdapter;
+
+    private int mReadOnlySourcesCnt;
+    private int mWritableSourcesCnt;
+    private boolean mAllRestricted;
+
+    /**
+     * A list of RawContacts included in this Contact.
+     */
+    private ArrayList<RawContact> mRawContacts = new ArrayList<RawContact>();
+
+    private LayoutInflater mInflater;
+
+    public ContactEditorFragment() {
+        // Explicit constructor for inflation
+    }
+
+    @Override
+    public void onAttach(Activity activity) {
+        super.onAttach(activity);
+        mContext = activity;
+    }
+
+    @Override
+    public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedState) {
+        final View view = inflater.inflate(R.layout.contact_editor_fragment, container, false);
+
+        mInflater = inflater;
+
+        mHeaderView =
+                (ContactEditorHeaderView) view.findViewById(R.id.contact_header_widget);
+
+        mListView = (ListView) view.findViewById(android.R.id.list);
+        mListView.setOnCreateContextMenuListener(this);
+        mListView.setScrollBarStyle(ListView.SCROLLBARS_OUTSIDE_OVERLAY);
+        mListView.setOnItemClickListener(this);
+
+        return view;
+    }
+
+    public void setListener(Listener value) {
+        mListener = value;
+    }
+
+    public void loadUri(Uri lookupUri) {
+        mLookupUri = lookupUri;
+        startLoading(LOADER_DETAILS, null);
+    }
+
+    @Override
+    protected void onInitializeLoaders() {
+    }
+
+    @Override
+    protected Loader<ContactLoader.Result> onCreateLoader(int id, Bundle args) {
+        switch (id) {
+            case LOADER_DETAILS: {
+                return new ContactLoader(mContext, mLookupUri);
+            }
+            default: {
+                Log.wtf(TAG, "Unknown ID in onCreateLoader: " + id);
+            }
+        }
+        return null;
+    }
+
+    @Override
+    protected void onLoadFinished(Loader<ContactLoader.Result> loader,
+            ContactLoader.Result data) {
+        final int id = loader.getId();
+        switch (id) {
+            case LOADER_DETAILS:
+                if (data == ContactLoader.Result.NOT_FOUND) {
+                    // Item has been deleted
+                    Log.i(TAG, "No contact found. Closing activity");
+                    mListener.onContactNotFound();
+                    return;
+                }
+                if (data == ContactLoader.Result.ERROR) {
+                    // Item has been deleted
+                    Log.i(TAG, "Error fetching contact. Closing activity");
+                    mListener.onError();
+                    return;
+                }
+                mContactData = data;
+                bindData();
+                break;
+            default: {
+                Log.wtf(TAG, "Unknown ID in onLoadFinished: " + id);
+            }
+        }
+    }
+
+    private void bindData() {
+        // Build up the contact entries
+        buildEntries();
+
+        mHeaderView.setMergeInfo(mRawContacts.size());
+
+        if (mAdapter == null) {
+            mAdapter = new ViewAdapter();
+            mListView.setAdapter(mAdapter);
+        } else {
+            mAdapter.notifyDataSetChanged();
+        }
+    }
+
+    /**
+     * Build up the entries to display on the screen.
+     */
+    private final void buildEntries() {
+        // Clear out the old entries
+        mRawContacts.clear();
+
+        mReadOnlySourcesCnt = 0;
+        mWritableSourcesCnt = 0;
+        mAllRestricted = true;
+
+        // TODO: This should be done in the background thread
+        final Sources sources = Sources.getInstance(mContext);
+
+        // Build up method entries
+        if (mContactData == null) {
+            return;
+        }
+
+        for (Entity entity: mContactData.getEntities()) {
+            final ContentValues entValues = entity.getEntityValues();
+            final String accountType = entValues.getAsString(RawContacts.ACCOUNT_TYPE);
+            final String accountName = entValues.getAsString(RawContacts.ACCOUNT_NAME);
+            final long rawContactId = entValues.getAsLong(RawContacts._ID);
+            final String rawContactUriString = ContentUris.withAppendedId(RawContacts.CONTENT_URI,
+                    rawContactId).toString();
+
+            // Mark when this contact has any unrestricted components
+            final boolean isRestricted = entValues.getAsInteger(RawContacts.IS_RESTRICTED) != 0;
+            if (!isRestricted) mAllRestricted = false;
+
+            final ContactsSource contactsSource = sources.getInflatedSource(accountType,
+                    ContactsSource.LEVEL_SUMMARY);
+            final boolean writable = contactsSource == null || !contactsSource.readOnly;
+            if (writable) {
+                mWritableSourcesCnt += 1;
+            } else {
+                mReadOnlySourcesCnt += 1;
+            }
+
+            final RawContact rawContact =
+                    new RawContact(contactsSource, accountName, rawContactId, writable);
+            mRawContacts.add(rawContact);
+
+            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, mContext,
+                        ContactsSource.LEVEL_MIMETYPES);
+                if (kind == null) continue;
+
+                final DataViewEntry entry = new DataViewEntry(mContext, mimeType, kind,
+                        rawContact, dataId, entryValues);
+
+                final boolean hasData = !TextUtils.isEmpty(entry.data);
+                final boolean isSuperPrimary = entryValues.getAsInteger(
+                        Data.IS_SUPER_PRIMARY) != 0;
+
+                final Intent itemEditIntent = new Intent(Intent.ACTION_EDIT, entry.uri);
+                itemEditIntent.putExtra(ContactFieldEditorActivity.BUNDLE_RAW_CONTACT_URI,
+                        rawContactUriString);
+                entry.intent = itemEditIntent;
+                entry.actionIcon = R.drawable.edit;
+
+                if (StructuredName.CONTENT_ITEM_TYPE.equals(mimeType)) {
+                    rawContact.getFields().add(entry);
+                } else if (Photo.CONTENT_ITEM_TYPE.equals(mimeType)) {
+                    rawContact.getFields().add(entry);
+                } else if (Phone.CONTENT_ITEM_TYPE.equals(mimeType) && hasData) {
+                    // Remember super-primary phone
+                    entry.isPrimary = isSuperPrimary;
+                    rawContact.getFields().add(entry);
+                } else if (Email.CONTENT_ITEM_TYPE.equals(mimeType) && hasData) {
+                    // Build email entries
+                    entry.isPrimary = isSuperPrimary;
+                    rawContact.getFields().add(entry);
+                } else if (StructuredPostal.CONTENT_ITEM_TYPE.equals(mimeType) && hasData) {
+                    // Build postal entries
+                    rawContact.getFields().add(entry);
+                } else if (Im.CONTENT_ITEM_TYPE.equals(mimeType) && hasData) {
+                    // Build IM entries
+                    if (TextUtils.isEmpty(entry.label)) {
+                        entry.label = mContext.getString(R.string.chat).toLowerCase();
+                    }
+                    rawContact.getFields().add(entry);
+                } else if (Organization.CONTENT_ITEM_TYPE.equals(mimeType) &&
+                        (hasData || !TextUtils.isEmpty(entry.label))) {
+                    entry.uri = null;
+
+                    if (TextUtils.isEmpty(entry.label)) {
+                        entry.label = entry.data;
+                        entry.data = "";
+                    }
+
+                    rawContact.getFields().add(entry);
+                } else if (Nickname.CONTENT_ITEM_TYPE.equals(mimeType) && hasData) {
+                    entry.uri = null;
+                    rawContact.getFields().add(entry);
+                } else if (Note.CONTENT_ITEM_TYPE.equals(mimeType) && hasData) {
+                    entry.uri = null;
+                    entry.maxLines = 100;
+                    rawContact.getFields().add(entry);
+                } else if (Website.CONTENT_ITEM_TYPE.equals(mimeType) && hasData) {
+                    entry.uri = null;
+                    entry.maxLines = 10;
+                    rawContact.getFields().add(entry);
+                } else {
+                    // Use social summary when requested by external source
+                    final DataStatus status = mContactData.getStatuses().get(entry.id);
+                    final boolean hasSocial = kind.actionBodySocial && status != null;
+
+                    if (hasSocial || hasData) {
+                        rawContact.getFields().add(entry);
+                    }
+                }
+            }
+        }
+    }
+
+    private 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();
+    }
+
+    private 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();
+    }
+
+    private abstract static class BaseViewEntry {
+        private final RawContact mRawContact;
+
+        public BaseViewEntry(RawContact rawContact) {
+            mRawContact = rawContact;
+        }
+
+        public RawContact getRawContact() {
+            return mRawContact;
+        }
+
+        public abstract int getEntryType();
+        public abstract View getView(View convertView, ViewGroup parent);
+    }
+
+    private class HeaderViewEntry extends BaseViewEntry {
+        private boolean mCollapsed;
+
+        public HeaderViewEntry(RawContact rawContact) {
+            super(rawContact);
+        }
+
+        public boolean isCollapsed() {
+            return mCollapsed;
+        }
+
+        public void setCollapsed(boolean collapsed) {
+            mCollapsed = collapsed;
+        }
+
+        @Override
+        public int getEntryType() {
+            return ItemTypes.RAW_CONTACT_HEADER;
+        }
+
+        @Override
+        public View getView(View convertView, ViewGroup parent) {
+            final View result;
+            final HeaderItemViewCache viewCache;
+            if (convertView != null) {
+                result = convertView;
+                viewCache = (HeaderItemViewCache) result.getTag();
+            } else {
+                result = mInflater.inflate(R.layout.list_edit_item_header, parent, false);
+                viewCache = new HeaderItemViewCache();
+                result.setTag(viewCache);
+                viewCache.logo = (ImageView) result.findViewById(R.id.logo);
+                viewCache.caption = (TextView) result.findViewById(R.id.caption);
+            }
+
+            CharSequence accountType = getRawContact().getSource().getDisplayLabel(mContext);
+            if (TextUtils.isEmpty(accountType)) {
+                accountType = mContext.getString(R.string.account_phone);
+            }
+            final String accountName = getRawContact().getAccountName();
+
+            final String accountTypeDisplay;
+            if (TextUtils.isEmpty(accountName)) {
+                accountTypeDisplay = mContext.getString(R.string.account_type_format,
+                        accountType);
+            } else {
+                accountTypeDisplay = mContext.getString(R.string.account_type_and_name,
+                        accountType, accountName);
+            }
+
+            viewCache.caption.setText(accountTypeDisplay);
+            viewCache.logo.setImageDrawable(getRawContact().getSource().getDisplayIcon(mContext));
+
+            return result;
+        }
+    }
+
+    private class FooterViewEntry extends BaseViewEntry {
+        public FooterViewEntry(RawContact rawContact) {
+            super(rawContact);
+        }
+
+        @Override
+        public int getEntryType() {
+            return ItemTypes.RAW_CONTACT_FOOTER;
+        }
+
+        @Override
+        public View getView(View convertView, ViewGroup parent) {
+            final View result;
+            final FooterItemViewCache viewCache;
+            if (convertView != null) {
+                result = convertView;
+                viewCache = (FooterItemViewCache) result.getTag();
+            } else {
+                result = mInflater.inflate(R.layout.list_edit_item_footer, parent, false);
+                viewCache = new FooterItemViewCache();
+                result.setTag(viewCache);
+                viewCache.addInformationButton =
+                    (Button) result.findViewById(R.id.add_information);
+                viewCache.separateButton =
+                    (Button) result.findViewById(R.id.separate);
+                viewCache.deleteButton =
+                    (Button) result.findViewById(R.id.deleteButton);
+                viewCache.addInformationButton.setOnClickListener(
+                        mAddInformationButtonClickListener);
+            }
+
+            viewCache.viewEntry = this;
+            return result;
+        }
+    }
+
+    private class DataViewEntry extends BaseViewEntry {
+        public String label;
+        public String data;
+        public Uri uri;
+        public long id = 0;
+        public int maxLines = 1;
+        public String mimetype;
+
+        public int actionIcon = -1;
+        public boolean isPrimary = false;
+        public Intent intent;
+        public Intent secondaryIntent = null;
+        public int maxLabelLines = 1;
+        public byte[] binaryData = null;
+
+        /**
+         * Build new {@link DataViewEntry} and populate from the given values.
+         */
+        public DataViewEntry(Context context, String mimeType, DataKind kind,
+                RawContact rawContact, long dataId, ContentValues values) {
+            super(rawContact);
+            id = dataId;
+            uri = ContentUris.withAppendedId(Data.CONTENT_URI, id);
+            mimetype = mimeType;
+            label = buildActionString(kind, values, false, context);
+            data = buildDataString(kind, values, context);
+            binaryData = values.getAsByteArray(Data.DATA15);
+        }
+
+        @Override
+        public int getEntryType() {
+            return Photo.CONTENT_ITEM_TYPE.equals(mimetype) ? ItemTypes.PHOTO : ItemTypes.DATA;
+        }
+
+        @Override
+        public View getView(View convertView, ViewGroup parent) {
+            final View result;
+            if (Photo.CONTENT_ITEM_TYPE.equals(mimetype)) {
+                final PhotoItemViewCache viewCache;
+                if (convertView != null) {
+                    result = convertView;
+                    viewCache = (PhotoItemViewCache) result.getTag();
+                } else {
+                    // Create a new view if needed
+                    result = mInflater.inflate(R.layout.list_edit_item_photo, parent, false);
+
+                    // Cache the children
+                    viewCache = new PhotoItemViewCache();
+                    viewCache.photo = (ImageView) result.findViewById(R.id.photo);
+                    viewCache.galleryActionButton =
+                            (ImageView) result.findViewById(R.id.action_icon);
+                    viewCache.takePhotoActionButton =
+                            (ImageView) result.findViewById(R.id.secondary_action_button);
+                    result.setTag(viewCache);
+                }
+                final Bitmap bitmap = binaryData != null
+                        ? BitmapFactory.decodeByteArray(binaryData, 0, binaryData.length)
+                        : null;
+                viewCache.photo.setImageBitmap(bitmap);
+            } else {
+                final DataItemViewCache viewCache;
+                if (convertView != null) {
+                    result = convertView;
+                    viewCache = (DataItemViewCache) result.getTag();
+                } else {
+                    // Create a new view if needed
+                    result = mInflater.inflate(R.layout.list_edit_item_text_icons, parent,
+                            false);
+
+                    // Cache the children
+                    viewCache = new DataItemViewCache();
+                    viewCache.label = (TextView) result.findViewById(android.R.id.text1);
+                    viewCache.data = (TextView) result.findViewById(android.R.id.text2);
+                    viewCache.actionIcon = (ImageView) result.findViewById(R.id.action_icon);
+                    viewCache.primaryIcon = (ImageView) result.findViewById(R.id.primary_icon);
+                    viewCache.secondaryActionButton = (ImageView) result.findViewById(
+                            R.id.secondary_action_button);
+                    viewCache.secondaryActionDivider = result.findViewById(R.id.divider);
+                    result.setTag(viewCache);
+                }
+                final Resources resources = mContext.getResources();
+
+                // Set the label
+                setMaxLines(viewCache.label, maxLabelLines);
+                viewCache.label.setText(label);
+
+                if (data != null) {
+                    if (Phone.CONTENT_ITEM_TYPE.equals(mimetype)
+                            || Constants.MIME_SMS_ADDRESS.equals(mimetype)) {
+                        viewCache.data.setText(PhoneNumberUtils.formatNumber(data));
+                    } else {
+                        viewCache.data.setText(data);
+                    }
+                    setMaxLines(viewCache.data, maxLines);
+                }
+
+                // Set the primary icon
+                viewCache.primaryIcon.setVisibility(isPrimary ? View.VISIBLE : View.GONE);
+
+                // Set the action icon
+                final ImageView action = viewCache.actionIcon;
+                if (intent != null) {
+                    action.setImageDrawable(resources.getDrawable(actionIcon));
+                    action.setVisibility(View.VISIBLE);
+                } else {
+                    action.setVisibility(View.INVISIBLE);
+                }
+
+                // Set the secondary action button
+                final ImageView secondaryActionView = viewCache.secondaryActionButton;
+                secondaryActionView.setVisibility(View.GONE);
+                viewCache.secondaryActionDivider.setVisibility(View.GONE);
+            }
+            return result;
+        }
+
+        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);
+            }
+        }
+    }
+
+    /** Cache of the header of a raw contact */
+    private static class HeaderItemViewCache {
+        public ImageView logo;
+        public TextView caption;
+    }
+
+    /** Cache of the footer of a raw contact */
+    private static class FooterItemViewCache {
+        public Button addInformationButton;
+        public Button separateButton;
+        public Button deleteButton;
+
+        public FooterViewEntry viewEntry;
+    }
+
+    /** Cache of the children views of a row */
+    private static class DataItemViewCache {
+        public TextView label;
+        public TextView data;
+        public ImageView actionIcon;
+        public ImageView primaryIcon;
+        public ImageView secondaryActionButton;
+        public View secondaryActionDivider;
+    }
+
+    /** Cache of the children views of a row */
+    private static class PhotoItemViewCache {
+        public ImageView photo;
+        public ImageView takePhotoActionButton;
+        public ImageView galleryActionButton;
+    }
+
+    /** Possible Item Types */
+    private interface ItemTypes {
+        public static final int DATA = 0;
+        public static final int PHOTO = 1;
+        public static final int RAW_CONTACT_HEADER = 2;
+        public static final int RAW_CONTACT_FOOTER = 3;
+        public static final int _COUNT = 4;
+    }
+
+    private final class ViewAdapter extends BaseAdapter {
+        public View getView(int position, View convertView, ViewGroup parent) {
+            final View result;
+
+            final BaseViewEntry viewEntry = getEntry(position);
+            return viewEntry.getView(convertView, parent);
+        }
+
+        public Object getItem(int position) {
+            return getEntry(position);
+        }
+
+        public long getItemId(int position) {
+            // TODO Get a unique Id. Use negative numbers for Headers/Footers
+            return position;
+        }
+
+        private BaseViewEntry getEntry(int position) {
+            for (int i = 0; i < mRawContacts.size(); i++) {
+                final RawContact rawContact = mRawContacts.get(i);
+                if (position == 0) return rawContact.getHeader();
+
+                // Collapsed header? Count one item and continue
+                if (rawContact.getHeader().isCollapsed()) {
+                    position--;
+                    continue;
+                }
+
+                final ArrayList<DataViewEntry> fields = rawContact.getFields();
+                // +1 for header, +1 for footer
+                final int fieldCount = fields.size() + 2;
+                if (position == fieldCount - 1) {
+                    return rawContact.getFooter();
+                }
+                if (position < fieldCount) {
+                    // -1 for header
+                    return fields.get(position - 1);
+                }
+                position -= fieldCount;
+            }
+            return null;
+        }
+
+        @Override
+        public int getViewTypeCount() {
+            return ItemTypes._COUNT;
+        }
+
+        @Override
+        public int getItemViewType(int position) {
+            return getEntry(position).getEntryType();
+        }
+
+        public int getCount() {
+            int result = 0;
+            for (int i = 0; i < mRawContacts.size(); i++) {
+                final RawContact rawContact = mRawContacts.get(i);
+                if (rawContact.getHeader().isCollapsed()) {
+                    // just one header item
+                    result++;
+                } else {
+                    // +1 for header, +1 for footer
+                    result += rawContact.getFields().size() + 2;
+                }
+            }
+            return result;
+        }
+    }
+
+    public boolean onCreateOptionsMenu(Menu menu, final MenuInflater inflater) {
+        inflater.inflate(R.menu.view, menu);
+        return true;
+    }
+
+    public boolean onPrepareOptionsMenu(Menu menu) {
+        // TODO: Prepare options
+        return true;
+    }
+
+    public boolean onOptionsItemSelected(MenuItem item) {
+        switch (item.getItemId()) {
+            case R.id.menu_edit: {
+                // TODO: This is temporary code to invoke the old editor. We can get rid of this
+                // later
+                final Intent intent = new Intent();
+                intent.setClass(mContext, EditContactActivity.class);
+                final long rawContactId = mRawContacts.get(0).mId;
+                final Uri rawContactUri = ContentUris.withAppendedId(RawContacts.CONTENT_URI,
+                        rawContactId);
+                intent.setAction(Intent.ACTION_EDIT);
+                intent.setData(rawContactUri);
+                startActivity(intent);
+                return true;
+            }
+            case R.id.menu_delete: {
+                showDeleteConfirmationDialog();
+                return true;
+            }
+            case R.id.menu_options: {
+                final Intent intent = new Intent(mContext, ContactOptionsActivity.class);
+                intent.setData(mContactData.getLookupUri());
+                mContext.startActivity(intent);
+                return true;
+            }
+            case R.id.menu_share: {
+                if (mAllRestricted) return false;
+
+                final String lookupKey = mContactData.getLookupKey();
+                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 = mContext.getText(R.string.share_via);
+                final Intent chooseIntent = Intent.createChooser(intent, chooseTitle);
+
+                try {
+                    mContext.startActivity(chooseIntent);
+                } catch (ActivityNotFoundException ex) {
+                    Toast.makeText(mContext, R.string.share_error, Toast.LENGTH_SHORT).show();
+                }
+                return true;
+            }
+        }
+        return false;
+    }
+
+    private void showDeleteConfirmationDialog() {
+        final int dialogId;
+        if (mReadOnlySourcesCnt > 0 & mWritableSourcesCnt > 0) {
+            dialogId = R.id.detail_dialog_confirm_readonly_delete;
+        } else if (mReadOnlySourcesCnt > 0 && mWritableSourcesCnt == 0) {
+            dialogId = R.id.detail_dialog_confirm_readonly_hide;
+        } else if (mReadOnlySourcesCnt == 0 && mWritableSourcesCnt > 1) {
+            dialogId = R.id.detail_dialog_confirm_multiple_delete;
+        } else {
+            dialogId = R.id.detail_dialog_confirm_delete;
+        }
+        if (mListener != null) mListener.onDialogRequested(dialogId, null);
+    }
+
+    public void onCreateContextMenu(ContextMenu menu, View v, 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;
+        }
+
+        final BaseViewEntry baseEntry = mAdapter.getEntry(info.position);
+        if (baseEntry instanceof DataViewEntry) {
+            final DataViewEntry entry = (DataViewEntry) baseEntry;
+            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);
+            }
+        }
+    }
+
+    public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
+        if (mListener == null) return;
+        final BaseViewEntry baseEntry = mAdapter.getEntry(position);
+        if (baseEntry == null) return;
+
+        if (baseEntry instanceof HeaderViewEntry) {
+            // Toggle rawcontact visibility
+            final HeaderViewEntry entry = (HeaderViewEntry) baseEntry;
+            entry.setCollapsed(!entry.isCollapsed());
+            mAdapter.notifyDataSetChanged();
+        } else if (baseEntry instanceof DataViewEntry) {
+            final DataViewEntry entry = (DataViewEntry) baseEntry;
+            final Intent intent = entry.intent;
+            if (intent == null) return;
+            mListener.onItemClicked(intent);
+        }
+    }
+
+    private final DialogInterface.OnClickListener mDeleteListener =
+            new DialogInterface.OnClickListener() {
+        public void onClick(DialogInterface dialog, int which) {
+            mContext.getContentResolver().delete(mContactData.getLookupUri(), null, null);
+        }
+    };
+
+    public Dialog onCreateDialog(int id, Bundle bundle) {
+        // TODO The delete dialogs mirror the functionality from the Contact-Detail-Fragment.
+        //      Consider whether we can extract common logic here
+        // TODO The actual removal is not in a worker thread currently
+        switch (id) {
+            case R.id.detail_dialog_confirm_delete:
+                return new AlertDialog.Builder(mContext)
+                        .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, mDeleteListener)
+                        .setCancelable(false)
+                        .create();
+            case R.id.detail_dialog_confirm_readonly_delete:
+                return new AlertDialog.Builder(mContext)
+                        .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, mDeleteListener)
+                        .setCancelable(false)
+                        .create();
+            case R.id.detail_dialog_confirm_multiple_delete:
+                return new AlertDialog.Builder(mContext)
+                        .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, mDeleteListener)
+                        .setCancelable(false)
+                        .create();
+            case R.id.detail_dialog_confirm_readonly_hide: {
+                return new AlertDialog.Builder(mContext)
+                        .setTitle(R.string.deleteConfirmation_title)
+                        .setIcon(android.R.drawable.ic_dialog_alert)
+                        .setMessage(R.string.readOnlyContactWarning)
+                        .setPositiveButton(android.R.string.ok, mDeleteListener)
+                        .create();
+            }
+            case R.id.edit_dialog_add_information: {
+                final long rawContactId = bundle.getLong(BUNDLE_RAW_CONTACT_ID);
+                final RawContact rawContact = findRawContactById(rawContactId);
+                if (rawContact == null) return null;
+                final ContactsSource source = rawContact.getSource();
+
+                final ArrayList<DataKind> sortedDataKinds = source.getSortedDataKinds();
+                final ArrayList<String> items = new ArrayList<String>(sortedDataKinds.size());
+                for (DataKind dataKind : sortedDataKinds) {
+                    // TODO: Filter out fields that do not make sense in the current Context
+                    //       (Name, Photo, Notes etc)
+                    if (dataKind.titleRes == -1) continue;
+                    if (!dataKind.editable) continue;
+                    final String title = mContext.getString(dataKind.titleRes);
+                    items.add(title);
+                }
+                final DialogInterface.OnClickListener itemClickListener =
+                        new DialogInterface.OnClickListener() {
+                    public void onClick(DialogInterface dialog, int which) {
+                        // TODO: Launch Intent to show Dialog
+//                        final KindSectionView view = (KindSectionView) mFields.getChildAt(which);
+//                        view.addItem();
+                    }
+                };
+                return new AlertDialog.Builder(mContext)
+                        .setItems(items.toArray(new String[0]), itemClickListener)
+                        .create();
+            }
+            default:
+                return null;
+        }
+    }
+
+    private RawContact findRawContactById(long rawContactId) {
+        for (RawContact rawContact : mRawContacts) {
+            if (rawContact.getId() == rawContactId) return rawContact;
+        }
+        return null;
+    }
+
+    public boolean onContextItemSelected(MenuItem item) {
+        switch (item.getItemId()) {
+            case MENU_ITEM_MAKE_DEFAULT: {
+                if (makeItemDefault(item)) {
+                    return true;
+                }
+                break;
+            }
+        }
+
+        return false;
+    }
+
+    private boolean makeItemDefault(MenuItem item) {
+        final BaseViewEntry baseEntry = getViewEntryForMenuItem(item);
+        if (baseEntry == null || !(baseEntry instanceof DataViewEntry)) {
+            return false;
+        }
+        final DataViewEntry entry = (DataViewEntry) baseEntry;
+
+        // Update the primary values in the data record.
+        ContentValues values = new ContentValues(1);
+        values.put(Data.IS_SUPER_PRIMARY, 1);
+
+        mContext.getContentResolver().update(ContentUris.withAppendedId(Data.CONTENT_URI, entry.id),
+                values, null, null);
+        return true;
+    }
+
+    private BaseViewEntry getViewEntryForMenuItem(MenuItem item) {
+        final AdapterView.AdapterContextMenuInfo info;
+        try {
+             info = (AdapterView.AdapterContextMenuInfo) item.getMenuInfo();
+        } catch (ClassCastException e) {
+            Log.e(TAG, "bad menuInfo", e);
+            return null;
+        }
+
+        return mAdapter.getEntry(info.position);
+    }
+
+    public OnClickListener mAddInformationButtonClickListener = new OnClickListener() {
+        public void onClick(View v) {
+            // The parent of the Button allows identifying the section
+            final View parentView = (View) v.getParent();
+            final FooterItemViewCache viewCache = (FooterItemViewCache) parentView.getTag();
+            final FooterViewEntry entry = viewCache.viewEntry;
+            final RawContact rawContact = entry.getRawContact();
+
+            // Create a bundle to show the Dialog
+            final Bundle bundle = new Bundle();
+            bundle.putLong(BUNDLE_RAW_CONTACT_ID, rawContact.getId());
+            if (mListener != null) mListener.onDialogRequested(R.id.edit_dialog_add_information,
+                    bundle);
+        }
+    };
+
+    private class RawContact {
+        private final ContactsSource mSource;
+        private String mAccountName;
+        private final long mId;
+        private boolean mWritable;
+        private final HeaderViewEntry mHeader = new HeaderViewEntry(this);
+        private final FooterViewEntry mFooter = new FooterViewEntry(this);
+        private final ArrayList<DataViewEntry> mFields = new ArrayList<DataViewEntry>();
+
+        public RawContact(ContactsSource source, String accountName, long id, boolean writable) {
+            mSource = source;
+            mAccountName = accountName;
+            mId = id;
+            mWritable = writable;
+        }
+
+        public ContactsSource getSource() {
+            return mSource;
+        }
+
+        public String getAccountName() {
+            return mAccountName;
+        }
+
+        public long getId() {
+            return mId;
+        }
+
+        public boolean isWritable() {
+            return mWritable;
+        }
+
+        public ArrayList<DataViewEntry> getFields() {
+            return mFields;
+        }
+
+        public HeaderViewEntry getHeader() {
+            return mHeader;
+        }
+
+        public FooterViewEntry getFooter() {
+            return mFooter;
+        }
+    }
+
+    public static interface Listener {
+        /**
+         * Contact was not found, so somehow close this fragment.
+         */
+        public void onContactNotFound();
+
+        /**
+         * There was an error loading the contact
+         */
+        public void onError();
+
+        /**
+         * User clicked a single item (e.g. mail)
+         */
+        public void onItemClicked(Intent intent);
+
+        /**
+         * Show a dialog using the globally unique id
+         */
+        public void onDialogRequested(int id, Bundle bundle);
+    }
+}
diff --git a/src/com/android/contacts/views/editor/ContactEditorHeaderView.java b/src/com/android/contacts/views/editor/ContactEditorHeaderView.java
new file mode 100644
index 0000000..18cbf72
--- /dev/null
+++ b/src/com/android/contacts/views/editor/ContactEditorHeaderView.java
@@ -0,0 +1,65 @@
+/*
+ * 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.views.editor;
+
+import com.android.contacts.R;
+
+import android.content.Context;
+import android.util.AttributeSet;
+import android.view.LayoutInflater;
+import android.widget.FrameLayout;
+import android.widget.TextView;
+
+/**
+ * Header for displaying the title bar in the contact editor
+ */
+public class ContactEditorHeaderView extends FrameLayout {
+    private static final String TAG = "ContactEditorHeaderView";
+
+    private TextView mMergeInfo;
+
+    public ContactEditorHeaderView(Context context) {
+        this(context, null);
+    }
+
+    public ContactEditorHeaderView(Context context, AttributeSet attrs) {
+        this(context, attrs, 0);
+    }
+
+    public ContactEditorHeaderView(Context context, AttributeSet attrs, int defStyle) {
+        super(context, attrs, defStyle);
+
+        final LayoutInflater inflater =
+            (LayoutInflater) mContext.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
+        inflater.inflate(R.layout.contact_editor_header_view, this);
+
+        mMergeInfo = (TextView) findViewById(R.id.merge_info);
+
+        // Start with unmerged
+        setMergeInfo(1);
+    }
+
+    public void setMergeInfo(int count) {
+        if (count <= 1) {
+            mMergeInfo.setVisibility(GONE);
+        } else {
+            mMergeInfo.setVisibility(VISIBLE);
+            mMergeInfo.setText(
+                    getResources().getQuantityString(R.plurals.merge_info, count, count));
+        }
+    }
+}
diff --git a/src/com/android/contacts/views/editor/ContactFieldEditorBaseFragment.java b/src/com/android/contacts/views/editor/ContactFieldEditorBaseFragment.java
new file mode 100644
index 0000000..11b9481
--- /dev/null
+++ b/src/com/android/contacts/views/editor/ContactFieldEditorBaseFragment.java
@@ -0,0 +1,208 @@
+/*
+ * 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.views.editor;
+
+import com.android.contacts.R;
+import com.android.contacts.views.ContactLoader;
+
+import android.app.Activity;
+import android.app.LoaderManagingFragment;
+import android.content.ContentProviderOperation;
+import android.content.ContentResolver;
+import android.content.ContentUris;
+import android.content.ContentValues;
+import android.content.Context;
+import android.content.Entity;
+import android.content.Loader;
+import android.content.OperationApplicationException;
+import android.content.ContentProviderOperation.Builder;
+import android.content.Entity.NamedContentValues;
+import android.net.Uri;
+import android.os.Bundle;
+import android.os.RemoteException;
+import android.provider.ContactsContract;
+import android.provider.ContactsContract.Data;
+import android.provider.ContactsContract.RawContacts;
+import android.util.Log;
+import android.widget.Toast;
+
+import java.util.ArrayList;
+
+public abstract class ContactFieldEditorBaseFragment
+        extends LoaderManagingFragment<ContactLoader.Result> {
+    private static final String TAG = "ContactFieldEditorBaseFragment";
+
+    private static final int LOADER_DETAILS = 1;
+
+    private long mRawContactId;
+    private Uri mRawContactUri;
+    private long mDataId;
+    private Uri mDataUri;
+    private Context mContext;
+    private Listener mListener;
+    private ContactLoader.Result mContactData;
+
+    @Override
+    public void onAttach(Activity activity) {
+        super.onAttach(activity);
+        mContext = activity;
+    }
+
+    public ContactLoader.Result getContactData() {
+        return mContactData;
+    }
+
+    /**
+     * Sets the Uris for the Fragment. If the dataUri is null, this is interpreted as an Insert
+     * to an existing rawContact. rawContactUri must always be given
+     */
+    public void setupUris(Uri rawContactUri, Uri dataUri) {
+        mRawContactUri = rawContactUri;
+        mRawContactId = Long.parseLong(rawContactUri.getLastPathSegment());
+        mDataUri = dataUri;
+        mDataId = dataUri == null ? 0 : Long.parseLong(dataUri.getLastPathSegment());
+    }
+
+    @Override
+    protected void onInitializeLoaders() {
+        startLoading(LOADER_DETAILS, null);
+    }
+
+    @Override
+    protected Loader<ContactLoader.Result> onCreateLoader(int id, Bundle args) {
+        switch (id) {
+            case LOADER_DETAILS: {
+                return new ContactLoader(mContext,
+                        ContentUris.withAppendedId(RawContacts.CONTENT_URI, mRawContactId));
+            }
+            default: {
+                Log.wtf(TAG, "Unknown ID in onCreateLoader: " + id);
+            }
+        }
+        return null;
+    }
+
+    @Override
+    protected void onLoadFinished(Loader<ContactLoader.Result> loader,
+            ContactLoader.Result data) {
+        final int id = loader.getId();
+        switch (id) {
+            case LOADER_DETAILS:
+                if (data == ContactLoader.Result.NOT_FOUND) {
+                    // Item has been deleted
+                    Log.i(TAG, "No contact found. Closing activity");
+                    if (mListener != null) mListener.onContactNotFound();
+                    return;
+                }
+                if (data == ContactLoader.Result.ERROR) {
+                    throw new IllegalStateException("Error during data loading");
+                }
+                mContactData = data;
+
+                // Find the correct RawContact
+                boolean valueFound = false;
+                for (Entity entity : mContactData.getEntities()) {
+                    final ContentValues entValues = entity.getEntityValues();
+                    final long rawContactId = entValues.getAsLong(RawContacts._ID);
+                    if (rawContactId == mRawContactId) {
+                        for (NamedContentValues subValue : entity.getSubValues()) {
+                            final long dataId = subValue.values.getAsLong(Data._ID);
+                            if (dataId == mDataId) {
+                                loadData(subValue);
+                                valueFound = true;
+                                break;
+                            }
+                        }
+                    }
+                }
+                if (!valueFound) {
+                    // Item has been deleted
+                    Log.i(TAG, "Data item not found. Closing activity");
+                    if (mListener != null) mListener.onDataNotFound();
+                    return;
+                }
+                break;
+            default: {
+                Log.wtf(TAG, "Unknown ID in onLoadFinished: " + id);
+            }
+        }
+    }
+
+    protected void saveData() {
+        final ContentResolver resolver = getActivity().getContentResolver();
+
+        final ArrayList<ContentProviderOperation> operations =
+            new ArrayList<ContentProviderOperation>();
+
+        final Builder builder;
+        if (getDataUri() == null) {
+            // INSERT
+            builder = ContentProviderOperation.newInsert(Data.CONTENT_URI);
+            builder.withValue(Data.RAW_CONTACT_ID, getRawContactId());
+            saveData(builder);
+        } else {
+            // UPDATE
+            builder = ContentProviderOperation.newUpdate(getDataUri());
+            saveData(builder);
+        }
+        operations.add(builder.build());
+
+        // TODO: Do in Background thread/service
+        try {
+            resolver.applyBatch(ContactsContract.AUTHORITY, operations);
+        } catch (RemoteException e) {
+            Toast.makeText(getActivity(), R.string.edit_error_saving, Toast.LENGTH_LONG).show();
+        } catch (OperationApplicationException e) {
+            Toast.makeText(getActivity(), R.string.edit_error_saving, Toast.LENGTH_LONG).show();
+        }
+    }
+
+    protected abstract void saveData(final Builder builder);
+
+    protected abstract void loadData(NamedContentValues contentValues);
+
+    public void setListener(Listener listener) {
+        mListener = listener;
+    }
+
+    public Listener getListener() {
+        return mListener;
+    }
+
+    public Uri getRawContactUri() {
+        return mRawContactUri;
+    }
+
+    public long getRawContactId() {
+        return mRawContactId;
+    }
+
+    public Uri getDataUri() {
+        return mDataUri;
+    }
+
+    public long getDataId() {
+        return mDataId;
+    }
+
+    public static interface Listener {
+        void onContactNotFound();
+        void onDataNotFound();
+        void onCancel();
+        void onSaved();
+    }
+}
diff --git a/src/com/android/contacts/views/editor/ContactFieldEditorEmailFragment.java b/src/com/android/contacts/views/editor/ContactFieldEditorEmailFragment.java
new file mode 100644
index 0000000..d838ea8
--- /dev/null
+++ b/src/com/android/contacts/views/editor/ContactFieldEditorEmailFragment.java
@@ -0,0 +1,126 @@
+/*
+ * 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.views.editor;
+
+import com.android.contacts.R;
+import com.android.contacts.util.ViewGroupAnimator;
+
+import android.content.ContentProviderOperation.Builder;
+import android.content.Entity.NamedContentValues;
+import android.graphics.Color;
+import android.os.Bundle;
+import android.provider.ContactsContract.CommonDataKinds.Email;
+import android.util.SparseArray;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.view.View.OnClickListener;
+import android.widget.Button;
+import android.widget.EditText;
+
+public class ContactFieldEditorEmailFragment extends ContactFieldEditorBaseFragment {
+    private EditText mEditText;
+    private View mCustomTypeContainer;
+    private EditText mCustomTypeTextView;
+    private SparseArray<Button> mTypeButtons = new SparseArray<Button>();
+    private int mSelectedType;
+
+    @Override
+    public View onCreateView(LayoutInflater inflater, ViewGroup container,
+            Bundle savedInstanceState) {
+        final View view = inflater.inflate(R.layout.contact_field_editor_email_fragment, container,
+                false);
+
+        mEditText = (EditText) view.findViewById(R.id.email_edit_text);
+        mTypeButtons.append(Email.TYPE_HOME, (Button) view.findViewById(R.id.email_type_home));
+        mTypeButtons.append(Email.TYPE_WORK, (Button) view.findViewById(R.id.email_type_work));
+        mTypeButtons.append(Email.TYPE_OTHER, (Button) view.findViewById(R.id.email_type_other));
+        mTypeButtons.append(Email.TYPE_CUSTOM, (Button) view.findViewById(R.id.email_type_custom));
+
+        mCustomTypeContainer = view.findViewById(R.id.email_edit_custom_type);
+        mCustomTypeTextView = (EditText) view.findViewById(R.id.email_edit_type_text);
+
+        for (int i = 0; i < mTypeButtons.size(); i++) {
+            final int type = mTypeButtons.keyAt(i);
+            final Button button = mTypeButtons.get(type);
+            button.setText(Email.getTypeLabelResource(type));
+            button.setOnClickListener(mTypeButtonOnClickListener);
+        }
+
+        final Button okButton = (Button) view.findViewById(R.id.btn_ok);
+        final Button cancelButton = (Button) view.findViewById(R.id.btn_cancel);
+        okButton.setOnClickListener(new OnClickListener() {
+            public void onClick(View v) {
+                saveData();
+
+                if (getListener() != null) getListener().onSaved();
+            }
+        });
+        cancelButton.setOnClickListener(new OnClickListener() {
+            public void onClick(View v) {
+                if (getListener() != null) getListener().onCancel();
+            }
+        });
+        return view;
+    }
+
+    private void pushTypeButton(int newType, boolean animate) {
+        boolean doAnimation =
+                animate && ((mSelectedType == Email.TYPE_CUSTOM) ^ (newType == Email.TYPE_CUSTOM));
+
+        final ViewGroupAnimator animator;
+        if (doAnimation) {
+            animator = ViewGroupAnimator.captureView(getView().getRootView());
+        } else {
+            animator = null;
+        }
+        for (int i = 0; i < mTypeButtons.size(); i++) {
+            final int type = mTypeButtons.keyAt(i);
+            final Button button = mTypeButtons.get(type);
+            button.setTextColor(newType == type ? Color.BLACK : Color.GRAY);
+        }
+
+        mCustomTypeContainer.setVisibility(newType == Email.TYPE_CUSTOM ? View.VISIBLE : View.GONE);
+
+        if (doAnimation) animator.animate();
+
+        mSelectedType = newType;
+    }
+
+    private OnClickListener mTypeButtonOnClickListener = new OnClickListener() {
+        public void onClick(View v) {
+            final Button b = (Button) v;
+            final int index = mTypeButtons.indexOfValue(b);
+            final int type = mTypeButtons.keyAt(index);
+            pushTypeButton(type, true);
+        }
+    };
+
+    @Override
+    protected void loadData(NamedContentValues contentValues) {
+        mEditText.setText(contentValues.values.getAsString(Email.ADDRESS));
+        pushTypeButton(contentValues.values.getAsInteger(Email.TYPE).intValue(), false);
+        mCustomTypeTextView.setText(contentValues.values.getAsString(Email.LABEL));
+    }
+
+    @Override
+    protected void saveData(Builder builder) {
+        builder.withValue(Email.ADDRESS, mEditText.getText().toString());
+        builder.withValue(Email.TYPE, mSelectedType);
+        builder.withValue(Email.LABEL, mCustomTypeTextView.getText().toString());
+    }
+}