Use the form-based editor in a fragment

Change-Id: I461bc1842f5c9cf1d97f4ef285eb5d88c850134f
diff --git a/src/com/android/contacts/activities/ContactEditorActivity.java b/src/com/android/contacts/activities/ContactEditorActivity.java
index 1007410..2bac145 100644
--- a/src/com/android/contacts/activities/ContactEditorActivity.java
+++ b/src/com/android/contacts/activities/ContactEditorActivity.java
@@ -25,8 +25,8 @@
 import android.content.Intent;
 import android.os.Bundle;
 import android.util.Log;
-import android.view.Menu;
 import android.view.MenuItem;
+import android.widget.Toast;
 
 public class ContactEditorActivity extends Activity {
     private static final String TAG = "ContactEditorActivity";
@@ -41,7 +41,8 @@
 
         mFragment = (ContactEditorFragment) findFragmentById(R.id.contact_editor_fragment);
         mFragment.setListener(mFragmentListener);
-        mFragment.loadUri(getIntent().getData());
+        mFragment.load(getIntent().getAction(), getIntent().getData(),
+                getIntent().resolveType(getContentResolver()), getIntent().getExtras());
 
         Log.i(TAG, getIntent().getData().toString());
     }
@@ -77,22 +78,44 @@
 
     private final ContactEditorFragment.Listener mFragmentListener =
             new ContactEditorFragment.Listener() {
-        public void onContactNotFound() {
-            // TODO: Show error
-            finish();
+        @Override
+        public void closeAfterDelete() {
+            Toast.makeText(ContactEditorActivity.this, "closeAfterDelete",
+                    Toast.LENGTH_LONG).show();
         }
 
-        public void onError() {
-            // TODO: Show error message
-            finish();
+        @Override
+        public void closeAfterRevert() {
+            Toast.makeText(ContactEditorActivity.this, "closeAfterRevert",
+                    Toast.LENGTH_LONG).show();
         }
 
-        public void onEditorRequested(Intent intent) {
-            startActivity(intent);
+        @Override
+        public void closeAfterSaving(int resultCode, Intent resultIntent) {
+            Toast.makeText(ContactEditorActivity.this, "closeAfterSaving",
+                    Toast.LENGTH_LONG).show();
         }
 
-        public void onDialogRequested(int id, Bundle bundle) {
-            showDialog(id, bundle);
+        @Override
+        public void closeAfterSplit() {
+            Toast.makeText(ContactEditorActivity.this, "closeAfterSplit", Toast.LENGTH_LONG).show();
+        }
+
+        @Override
+        public void closeBecauseAccountSelectorAborted() {
+            Toast.makeText(ContactEditorActivity.this, "closeBecauseAccountSelectorAborted",
+                    Toast.LENGTH_LONG).show();
+        }
+
+        @Override
+        public void closeBecauseContactNotFound() {
+            Toast.makeText(ContactEditorActivity.this, "closeBecauseContactNotFound",
+                    Toast.LENGTH_LONG).show();
+        }
+
+        @Override
+        public void setTitleTo(int resourceId) {
+            Toast.makeText(ContactEditorActivity.this, "setTitleTo", Toast.LENGTH_LONG).show();
         }
     };
 }
diff --git a/src/com/android/contacts/activities/TwoPaneActivity.java b/src/com/android/contacts/activities/TwoPaneActivity.java
index 3f8a139..9d8bd47 100644
--- a/src/com/android/contacts/activities/TwoPaneActivity.java
+++ b/src/com/android/contacts/activities/TwoPaneActivity.java
@@ -28,6 +28,7 @@
 import android.content.Intent;
 import android.net.Uri;
 import android.os.Bundle;
+import android.provider.ContactsContract.Contacts;
 import android.text.TextUtils;
 import android.widget.Toast;
 
@@ -169,7 +170,8 @@
 
         public void onEditRequested(Uri contactLookupUri) {
             setupContactEditorFragment();
-            mEditorFragment.loadUri(contactLookupUri);
+            mEditorFragment.load(Intent.ACTION_EDIT, contactLookupUri, Contacts.CONTENT_ITEM_TYPE,
+                    new Bundle());
         }
 
         public void onItemClicked(Intent intent) {
@@ -182,20 +184,41 @@
     }
 
     private class EditorFragmentListener implements ContactEditorFragment.Listener {
-        public void onContactNotFound() {
-            Toast.makeText(TwoPaneActivity.this, "onContactNotFound", Toast.LENGTH_LONG).show();
+        @Override
+        public void closeAfterDelete() {
+            Toast.makeText(TwoPaneActivity.this, "closeAfterDelete", Toast.LENGTH_LONG).show();
         }
 
-        public void onDialogRequested(int id, Bundle bundle) {
-            Toast.makeText(TwoPaneActivity.this, "onDialogRequested", Toast.LENGTH_LONG).show();
+        @Override
+        public void closeAfterRevert() {
+            Toast.makeText(TwoPaneActivity.this, "closeAfterRevert", Toast.LENGTH_LONG).show();
         }
 
-        public void onEditorRequested(Intent intent) {
-            Toast.makeText(TwoPaneActivity.this, "onEditorRequested", Toast.LENGTH_LONG).show();
+        @Override
+        public void closeAfterSaving(int resultCode, Intent resultIntent) {
+            Toast.makeText(TwoPaneActivity.this, "closeAfterSaving", Toast.LENGTH_LONG).show();
         }
 
-        public void onError() {
-            Toast.makeText(TwoPaneActivity.this, "onError", Toast.LENGTH_LONG).show();
+        @Override
+        public void closeAfterSplit() {
+            Toast.makeText(TwoPaneActivity.this, "closeAfterSplit", Toast.LENGTH_LONG).show();
+        }
+
+        @Override
+        public void closeBecauseAccountSelectorAborted() {
+            Toast.makeText(TwoPaneActivity.this, "closeBecauseAccountSelectorAborted",
+                    Toast.LENGTH_LONG).show();
+        }
+
+        @Override
+        public void closeBecauseContactNotFound() {
+            Toast.makeText(TwoPaneActivity.this, "closeBecauseContactNotFound",
+                    Toast.LENGTH_LONG).show();
+        }
+
+        @Override
+        public void setTitleTo(int resourceId) {
+            Toast.makeText(TwoPaneActivity.this, "setTitleTo", Toast.LENGTH_LONG).show();
         }
     }
 }
diff --git a/src/com/android/contacts/ui/EditContactActivity.java b/src/com/android/contacts/ui/EditContactActivity.java
deleted file mode 100644
index 7b61e19..0000000
--- a/src/com/android/contacts/ui/EditContactActivity.java
+++ /dev/null
@@ -1,1414 +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.ui;
-
-import com.android.contacts.ContactsSearchManager;
-import com.android.contacts.ContactsUtils;
-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.widget.BaseContactEditorView;
-import com.android.contacts.ui.widget.PhotoEditorView;
-import com.android.contacts.util.DialogManager;
-import com.android.contacts.util.EmptyService;
-import com.android.contacts.util.WeakAsyncTask;
-
-import android.accounts.Account;
-import android.app.Activity;
-import android.app.AlertDialog;
-import android.app.Dialog;
-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.Entity;
-import android.content.Intent;
-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.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.provider.ContactsContract.Contacts.Data;
-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.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;
-
-/**
- * Activity for editing or inserting a contact.
- */
-public final class EditContactActivity extends Activity
-        implements View.OnClickListener, Comparator<EntityDelta>,
-        DialogManager.DialogShowingViewActivity {
-
-    private static final String TAG = "EditContactActivity";
-
-    /** The launch code when picking a photo and the raw data is returned */
-    private static final int PHOTO_PICKED_WITH_DATA = 3021;
-
-    /** The launch code when a contact to join with is returned */
-    private static final int REQUEST_JOIN_CONTACT = 3022;
-
-    /** The launch code when taking a picture */
-    private static final int CAMERA_WITH_DATA = 3023;
-
-    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_CONTACT_ID_FOR_JOIN = "contactidforjoin";
-
-    /** The result code when view activity should close after edit returns */
-    public static final int RESULT_CLOSE_VIEW_ACTIVITY = 777;
-
-    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 static final int DIALOG_CONFIRM_DELETE = 1;
-    private static final int DIALOG_CONFIRM_READONLY_DELETE = 2;
-    private static final int DIALOG_CONFIRM_MULTIPLE_DELETE = 3;
-    private static final int DIALOG_CONFIRM_READONLY_HIDE = 4;
-    private static final int DIALOG_PICK_PHOTO = 5;
-    private static final int DIALOG_SPLIT = 6;
-    private static final int DIALOG_SELECT_ACCOUNT = 7;
-    private static final int DIALOG_VIEW_DIALOGS_ID1 = 8;
-    private static final int DIALOG_VIEW_DIALOGS_ID2 = 9;
-
-    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;
-
-    String mQuerySelection;
-
-    private long mContactIdForJoin;
-
-    private static final int STATUS_LOADING = 0;
-    private static final int STATUS_EDITING = 1;
-    private static final int STATUS_SAVING = 2;
-
-    private int mStatus;
-    private DialogManager mDialogManager;
-
-    EntitySet mState;
-
-    /** The linear layout holding the ContactEditorViews */
-    LinearLayout mContent;
-
-    private ViewIdGenerator mViewIdGenerator;
-
-    @Override
-    protected void onCreate(Bundle icicle) {
-        super.onCreate(icicle);
-
-        final Intent intent = getIntent();
-        final String action = intent.getAction();
-
-        setContentView(R.layout.act_edit);
-
-        mDialogManager = new DialogManager(this, DIALOG_VIEW_DIALOGS_ID1, DIALOG_VIEW_DIALOGS_ID2);
-
-        // Build editor and listen for photo requests
-        mContent = (LinearLayout) findViewById(R.id.editors);
-
-        findViewById(R.id.btn_done).setOnClickListener(this);
-        findViewById(R.id.btn_discard).setOnClickListener(this);
-
-        // Handle initial actions only when existing state missing
-        final boolean hasIncomingState = icicle != null && icicle.containsKey(KEY_EDIT_STATE);
-
-        if (Intent.ACTION_EDIT.equals(action) && !hasIncomingState) {
-            setTitle(R.string.editContact_title_edit);
-            mStatus = STATUS_LOADING;
-
-            // Read initial state from database
-            new QueryEntitiesTask(this).execute(intent);
-        } else if (Intent.ACTION_INSERT.equals(action) && !hasIncomingState) {
-            setTitle(R.string.editContact_title_insert);
-            mStatus = STATUS_EDITING;
-            // Trigger dialog to pick account type
-            doAddAction();
-        }
-
-        if (icicle == null) {
-            // If icicle is non-null, onRestoreInstanceState() will restore the generator.
-            mViewIdGenerator = new ViewIdGenerator();
-        }
-    }
-
-    private static class QueryEntitiesTask extends
-            WeakAsyncTask<Intent, Void, EntitySet, EditContactActivity> {
-
-        private String mSelection;
-
-        public QueryEntitiesTask(EditContactActivity target) {
-            super(target);
-        }
-
-        @Override
-        protected EntitySet doInBackground(EditContactActivity target, Intent... params) {
-            final Intent intent = params[0];
-
-            final ContentResolver resolver = target.getContentResolver();
-
-            // Handle both legacy and new authorities
-            final Uri data = intent.getData();
-            final String authority = data.getAuthority();
-            final String mimeType = intent.resolveType(resolver);
-
-            mSelection = "0";
-            if (ContactsContract.AUTHORITY.equals(authority)) {
-                if (Contacts.CONTENT_ITEM_TYPE.equals(mimeType)) {
-                    // Handle selected aggregate
-                    final long contactId = ContentUris.parseId(data);
-                    mSelection = RawContacts.CONTACT_ID + "=" + contactId;
-                } else if (RawContacts.CONTENT_ITEM_TYPE.equals(mimeType)) {
-                    final long rawContactId = ContentUris.parseId(data);
-                    final long contactId = ContactsUtils.queryForContactId(resolver, rawContactId);
-                    mSelection = RawContacts.CONTACT_ID + "=" + contactId;
-                }
-            } else if (android.provider.Contacts.AUTHORITY.equals(authority)) {
-                final long rawContactId = ContentUris.parseId(data);
-                mSelection = Data.RAW_CONTACT_ID + "=" + rawContactId;
-            }
-
-            return EntitySet.fromQuery(target.getContentResolver(), mSelection, null, null);
-        }
-
-        @Override
-        protected void onPostExecute(EditContactActivity target, EntitySet entitySet) {
-            target.mQuerySelection = mSelection;
-
-            // Load edit details in background
-            final Context context = target;
-            final Sources sources = Sources.getInstance(context);
-
-            // Handle any incoming values that should be inserted
-            final Bundle extras = target.getIntent().getExtras();
-            final boolean hasExtras = extras != null && extras.size() > 0;
-            final boolean hasState = entitySet.size() > 0;
-            if (hasExtras && hasState) {
-                // Find source defining the first RawContact found
-                final EntityDelta state = entitySet.get(0);
-                final String accountType = state.getValues().getAsString(RawContacts.ACCOUNT_TYPE);
-                final ContactsSource source = sources.getInflatedSource(accountType,
-                        ContactsSource.LEVEL_CONSTRAINTS);
-                EntityModifier.parseExtras(context, source, state, extras);
-            }
-
-            target.mState = entitySet;
-
-            // Bind UI to new background state
-            target.bindEditors();
-        }
-    }
-
-    @Override
-    protected 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.putLong(KEY_CONTACT_ID_FOR_JOIN, mContactIdForJoin);
-        super.onSaveInstanceState(outState);
-    }
-
-    @Override
-    protected void onRestoreInstanceState(Bundle savedInstanceState) {
-        // Read modifications from instance
-        mState = savedInstanceState.<EntitySet> getParcelable(KEY_EDIT_STATE);
-        mRawContactIdRequestingPhoto = savedInstanceState.getLong(
-                KEY_RAW_CONTACT_ID_REQUESTING_PHOTO);
-        mViewIdGenerator = savedInstanceState.getParcelable(KEY_VIEW_ID_GENERATOR);
-        String fileName = savedInstanceState.getString(KEY_CURRENT_PHOTO_FILE);
-        if (fileName != null) {
-            mCurrentPhotoFile = new File(fileName);
-        }
-        mQuerySelection = savedInstanceState.getString(KEY_QUERY_SELECTION);
-        mContactIdForJoin = savedInstanceState.getLong(KEY_CONTACT_ID_FOR_JOIN);
-
-        bindEditors();
-
-        super.onRestoreInstanceState(savedInstanceState);
-    }
-
-    @Override
-    protected Dialog onCreateDialog(int id, Bundle bundle) {
-        switch (id) {
-            case DIALOG_CONFIRM_DELETE:
-                return new AlertDialog.Builder(this)
-                        .setTitle(R.string.deleteConfirmation_title)
-                        .setIcon(android.R.drawable.ic_dialog_alert)
-                        .setMessage(R.string.deleteConfirmation)
-                        .setNegativeButton(android.R.string.cancel, null)
-                        .setPositiveButton(android.R.string.ok, new DeleteClickListener())
-                        .setCancelable(false)
-                        .create();
-            case DIALOG_CONFIRM_READONLY_DELETE:
-                return new AlertDialog.Builder(this)
-                        .setTitle(R.string.deleteConfirmation_title)
-                        .setIcon(android.R.drawable.ic_dialog_alert)
-                        .setMessage(R.string.readOnlyContactDeleteConfirmation)
-                        .setNegativeButton(android.R.string.cancel, null)
-                        .setPositiveButton(android.R.string.ok, new DeleteClickListener())
-                        .setCancelable(false)
-                        .create();
-            case DIALOG_CONFIRM_MULTIPLE_DELETE:
-                return new AlertDialog.Builder(this)
-                        .setTitle(R.string.deleteConfirmation_title)
-                        .setIcon(android.R.drawable.ic_dialog_alert)
-                        .setMessage(R.string.multipleContactDeleteConfirmation)
-                        .setNegativeButton(android.R.string.cancel, null)
-                        .setPositiveButton(android.R.string.ok, new DeleteClickListener())
-                        .setCancelable(false)
-                        .create();
-            case DIALOG_CONFIRM_READONLY_HIDE:
-                return new AlertDialog.Builder(this)
-                        .setTitle(R.string.deleteConfirmation_title)
-                        .setIcon(android.R.drawable.ic_dialog_alert)
-                        .setMessage(R.string.readOnlyContactWarning)
-                        .setPositiveButton(android.R.string.ok, new DeleteClickListener())
-                        .setCancelable(false)
-                        .create();
-            case DIALOG_PICK_PHOTO:
-                return createPickPhotoDialog();
-            case DIALOG_SPLIT:
-                return createSplitDialog();
-            case DIALOG_SELECT_ACCOUNT:
-                return createSelectAccountDialog(bundle);
-            default:
-                return mDialogManager.onCreateDialog(id, bundle);
-        }
-    }
-
-    /**
-     * Dismiss the given {@link Dialog}.
-     */
-    static void dismissDialog(Dialog dialog) {
-        try {
-            // Only dismiss when valid reference and still showing
-            if (dialog != null && dialog.isShowing()) {
-                dialog.dismiss();
-            }
-        } catch (Exception e) {
-            Log.w(TAG, "Ignoring exception while dismissing dialog: " + e.toString());
-        }
-    }
-
-    /**
-     * Check if our internal {@link #mState} is valid, usually checked before
-     * performing user actions.
-     */
-    protected boolean hasValidState() {
-        return mStatus == STATUS_EDITING && mState != null && mState.size() > 0;
-    }
-
-    /**
-     * Rebuild the editors to match our underlying {@link #mState} object, usually
-     * called once we've parsed {@link Entity} data or have inserted a new
-     * {@link RawContacts}.
-     */
-    protected void bindEditors() {
-        if (mState == null) {
-            return;
-        }
-
-        final LayoutInflater inflater = (LayoutInflater) getSystemService(
-                Context.LAYOUT_INFLATER_SERVICE);
-        final Sources sources = Sources.getInstance(this);
-
-        // Sort the editors
-        Collections.sort(mState, this);
-
-        // Remove any existing editors and rebuild any visible
-        mContent.removeAllViews();
-        int size = mState.size();
-        for (int i = 0; i < size; i++) {
-            // TODO ensure proper ordering of entities in the list
-            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);
-
-            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);
-            }
-            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);
-        mStatus = STATUS_EDITING;
-    }
-
-    /**
-     * 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() {
-            Context context = EditContactActivity.this;
-
-            // Wrap our context to inflate list items using correct theme
-            final Context dialogContext = new ContextThemeWrapper(context,
-                    android.R.style.Theme_Light);
-
-            String[] choices;
-            if (mReadOnly) {
-                choices = new String[1];
-                choices[0] = getString(R.string.use_photo_as_primary);
-            } else {
-                choices = new String[3];
-                choices[0] = getString(R.string.use_photo_as_primary);
-                choices[1] = getString(R.string.removePicture);
-                choices[2] = 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;
-            }
-        }
-    }
-
-    /** {@inheritDoc} */
-    public void onClick(View view) {
-        switch (view.getId()) {
-            case R.id.btn_done:
-                doSaveAction(SAVE_MODE_DEFAULT);
-                break;
-            case R.id.btn_discard:
-                doRevertAction();
-                break;
-        }
-    }
-
-    /** {@inheritDoc} */
-    @Override
-    public void onBackPressed() {
-        doSaveAction(SAVE_MODE_DEFAULT);
-    }
-
-    /** {@inheritDoc} */
-    @Override
-    protected void onActivityResult(int requestCode, int resultCode, Intent data) {
-        // Ignore failed requests
-        if (resultCode != RESULT_OK) return;
-
-        switch (requestCode) {
-            case 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 CAMERA_WITH_DATA: {
-                doCropPhoto(mCurrentPhotoFile);
-                break;
-            }
-
-            case REQUEST_JOIN_CONTACT: {
-                if (resultCode == RESULT_OK && data != null) {
-                    final long contactId = ContentUris.parseId(data.getData());
-                    joinAggregate(contactId);
-                }
-            }
-        }
-    }
-
-    @Override
-    public boolean onCreateOptionsMenu(Menu menu) {
-        super.onCreateOptionsMenu(menu);
-
-        MenuInflater inflater = getMenuInflater();
-        inflater.inflate(R.menu.edit, menu);
-
-
-        return true;
-    }
-
-    @Override
-    public boolean onPrepareOptionsMenu(Menu menu) {
-        menu.findItem(R.id.menu_split).setVisible(mState != null && mState.size() > 1);
-        return true;
-    }
-
-    @Override
-    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;
-    }
-
-    /**
-     * 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, EditContactActivity> {
-        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 int mSaveMode;
-        private Uri mContactLookupUri = null;
-
-        public PersistTask(EditContactActivity target, int saveMode) {
-            super(target);
-            mSaveMode = saveMode;
-        }
-
-        /** {@inheritDoc} */
-        @Override
-        protected void onPreExecute(EditContactActivity target) {
-            mProgress = new WeakReference<ProgressDialog>(ProgressDialog.show(target, null,
-                    target.getText(R.string.savingContact)));
-
-            // Before starting this task, start an empty service to protect our
-            // process from being reclaimed by the system.
-            final Context context = target;
-            context.startService(new Intent(context, EmptyService.class));
-        }
-
-        /** {@inheritDoc} */
-        @Override
-        protected Integer doInBackground(EditContactActivity target, EntitySet... params) {
-            final Context context = target;
-            final ContentResolver resolver = context.getContentResolver();
-
-            EntitySet state = params[0];
-
-            // Trim any empty fields, and RawContacts, before persisting
-            final Sources sources = Sources.getInstance(context);
-            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, null, 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(EditContactActivity target, Integer result) {
-            final Context context = target;
-            final ProgressDialog progress = mProgress.get();
-
-            if (result == RESULT_SUCCESS && mSaveMode != SAVE_MODE_JOIN) {
-                Toast.makeText(context, R.string.contactSavedToast, Toast.LENGTH_SHORT).show();
-            } else if (result == RESULT_FAILURE) {
-                Toast.makeText(context, R.string.contactSavedErrorToast, Toast.LENGTH_LONG).show();
-            }
-
-            dismissDialog(progress);
-
-            // Stop the service that was protecting us
-            context.stopService(new Intent(context, EmptyService.class));
-
-            target.onSaveCompleted(result != RESULT_FAILURE, mSaveMode, mContactLookupUri);
-        }
-    }
-
-    /**
-     * Saves or creates the contact based on the mode, and if successful
-     * finishes the activity.
-     */
-    boolean doSaveAction(int saveMode) {
-        if (!hasValidState()) {
-            return false;
-        }
-
-        mStatus = STATUS_SAVING;
-        final PersistTask task = new PersistTask(this, saveMode);
-        task.execute(mState);
-
-        return true;
-    }
-
-    private class DeleteClickListener implements DialogInterface.OnClickListener {
-
-        public void onClick(DialogInterface dialog, int which) {
-            Sources sources = Sources.getInstance(EditContactActivity.this);
-            // Mark all raw contacts for deletion
-            for (EntityDelta delta : mState) {
-                delta.markDeleted();
-            }
-            // Save the deletes
-            doSaveAction(SAVE_MODE_DEFAULT);
-            finish();
-        }
-    }
-
-    private void onSaveCompleted(boolean success, int saveMode, Uri contactLookupUri) {
-        switch (saveMode) {
-            case SAVE_MODE_DEFAULT:
-                if (success && contactLookupUri != null) {
-                    final Intent resultIntent = new Intent();
-
-                    final Uri requestData = getIntent().getData();
-                    final String requestAuthority = requestData == null ? null : requestData
-                            .getAuthority();
-
-                    if (android.provider.Contacts.AUTHORITY.equals(requestAuthority)) {
-                        // Build legacy Uri when requested by caller
-                        final long contactId = ContentUris.parseId(Contacts.lookupContact(
-                                getContentResolver(), contactLookupUri));
-                        final Uri legacyUri = ContentUris.withAppendedId(
-                                android.provider.Contacts.People.CONTENT_URI, contactId);
-                        resultIntent.setData(legacyUri);
-                    } else {
-                        // Otherwise pass back a lookup-style Uri
-                        resultIntent.setData(contactLookupUri);
-                    }
-
-                    setResult(RESULT_OK, resultIntent);
-                } else {
-                    setResult(RESULT_CANCELED, null);
-                }
-                finish();
-                break;
-
-            case SAVE_MODE_SPLIT:
-                if (success) {
-                    Intent intent = new Intent();
-                    intent.setData(contactLookupUri);
-                    setResult(RESULT_CLOSE_VIEW_ACTIVITY, intent);
-                }
-                finish();
-                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)
-     */
-    public void showJoinAggregateActivity(Uri contactLookupUri) {
-        if (contactLookupUri == null) {
-            return;
-        }
-
-        mContactIdForJoin = ContentUris.parseId(contactLookupUri);
-        Intent intent = new Intent(JoinContactActivity.JOIN_CONTACT);
-        intent.putExtra(JoinContactActivity.EXTRA_TARGET_CONTACT_ID, mContactIdForJoin);
-        startActivityForResult(intent, REQUEST_JOIN_CONTACT);
-    }
-
-    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) {
-        ContentResolver resolver = 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 {
-            getContentResolver().applyBatch(ContactsContract.AUTHORITY, operations);
-
-            // We can use any of the constituent raw contacts to refresh the UI - why not the first
-            Intent intent = new Intent();
-            intent.setData(ContentUris.withAppendedId(RawContacts.CONTENT_URI, rawContactIds[0]));
-
-            // Reload the new state from database
-            new QueryEntitiesTask(this).execute(intent);
-
-            Toast.makeText(this, R.string.contactsJoinedMessage, Toast.LENGTH_LONG).show();
-        } catch (RemoteException e) {
-            Log.e(TAG, "Failed to apply aggregation exception batch", e);
-            Toast.makeText(this, R.string.contactSavedErrorToast, Toast.LENGTH_LONG).show();
-        } catch (OperationApplicationException e) {
-            Log.e(TAG, "Failed to apply aggregation exception batch", e);
-            Toast.makeText(this, 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());
-    }
-
-    /**
-     * Revert any changes the user has made, and finish the activity.
-     */
-    private boolean doRevertAction() {
-        finish();
-        return true;
-    }
-
-    /**
-     * Create a new {@link RawContacts} which will exist as another
-     * {@link EntityDelta} under the currently edited {@link Contacts}.
-     */
-    private boolean doAddAction() {
-        if (mStatus != STATUS_EDITING) {
-            return false;
-        }
-
-        // Adding is okay when missing state
-        new AddContactTask(this).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;
-        Sources sources = Sources.getInstance(EditContactActivity.this);
-        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) {
-            showDialog(DIALOG_CONFIRM_READONLY_DELETE);
-        } else if (readOnlySourcesCnt > 0 && writableSourcesCnt == 0) {
-            showDialog(DIALOG_CONFIRM_READONLY_HIDE);
-        } else if (readOnlySourcesCnt == 0 && writableSourcesCnt > 1) {
-            showDialog(DIALOG_CONFIRM_MULTIPLE_DELETE);
-        } else {
-            showDialog(DIALOG_CONFIRM_DELETE);
-        }
-        return true;
-    }
-
-    /**
-     * Pick a specific photo to be added under the currently selected tab.
-     */
-    boolean doPickPhotoAction(long rawContactId) {
-        if (!hasValidState()) return false;
-
-        mRawContactIdRequestingPhoto = rawContactId;
-
-        showDialog(DIALOG_PICK_PHOTO);
-
-        return true;
-    }
-
-    /**
-     * Creates a dialog offering two options: take a photo or pick a photo from the gallery.
-     */
-    private Dialog createPickPhotoDialog() {
-        Context context = EditContactActivity.this;
-
-        // Wrap our context to inflate list items using correct theme
-        final Context dialogContext = new ContextThemeWrapper(context,
-                android.R.style.Theme_Light);
-
-        String[] choices = new String[2];
-        choices[0] = getString(R.string.take_photo);
-        choices[1] = 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();
-    }
-
-    /**
-     * 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);
-            startActivityForResult(intent, CAMERA_WITH_DATA);
-        } catch (ActivityNotFoundException e) {
-            Toast.makeText(this, 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(
-                    this,
-                    new String[] { f.getAbsolutePath() },
-                    new String[] { null },
-                    null);
-
-            // Launch gallery to crop the photo
-            final Intent intent = getCropImageIntent(Uri.fromFile(f));
-            startActivityForResult(intent, PHOTO_PICKED_WITH_DATA);
-        } catch (Exception e) {
-            Log.e(TAG, "Cannot crop image", e);
-            Toast.makeText(this, 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;
-    }
-
-    /**
-     * Launches Gallery to pick a photo.
-     */
-    protected void doPickPhotoFromGallery() {
-        try {
-            // Launch picker to choose photo for selected contact
-            final Intent intent = getPhotoPickIntent();
-            startActivityForResult(intent, PHOTO_PICKED_WITH_DATA);
-        } catch (ActivityNotFoundException e) {
-            Toast.makeText(this, 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;
-    }
-
-    /** {@inheritDoc} */
-    public void onDeleted(Editor editor) {
-        // Ignore any editor deletes
-    }
-
-    private boolean doSplitContactAction() {
-        if (!hasValidState()) return false;
-
-        showDialog(DIALOG_SPLIT);
-        return true;
-    }
-
-    private Dialog createSplitDialog() {
-        final AlertDialog.Builder builder = new AlertDialog.Builder(this);
-        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);
-    }
-
-    /**
-     * Build dialog that handles adding a new {@link RawContacts} after the user
-     * picks a specific {@link ContactsSource}.
-     */
-    private static class AddContactTask extends
-            WeakAsyncTask<Void, Void, ArrayList<Account>, EditContactActivity> {
-
-        public AddContactTask(EditContactActivity target) {
-            super(target);
-        }
-
-        @Override
-        protected ArrayList<Account> doInBackground(final EditContactActivity target,
-                Void... params) {
-            return Sources.getInstance(target).getAccounts(true);
-        }
-
-        @Override
-        protected void onPostExecute(final EditContactActivity target, ArrayList<Account> accounts) {
-            target.selectAccountAndCreateContact(accounts);
-        }
-    }
-
-    public void selectAccountAndCreateContact(ArrayList<Account> accounts) {
-        // No Accounts available.  Create a phone-local contact.
-        if (accounts.isEmpty()) {
-            createContact(null);
-            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));
-            return;  // Don't show a dialog.
-        }
-
-        Bundle bundle = new Bundle();
-        bundle.putParcelableArrayList(BUNDLE_SELECT_ACCOUNT_LIST, accounts);
-        showDialog(DIALOG_SELECT_ACCOUNT, bundle);
-    }
-
-    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(this, android.R.style.Theme_Light);
-        final LayoutInflater dialogInflater =
-            (LayoutInflater)dialogContext.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
-
-        final Sources sources = Sources.getInstance(this);
-
-        final ArrayAdapter<Account> accountAdapter = new ArrayAdapter<Account>(this,
-                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(EditContactActivity.this));
-
-                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);
-            }
-        };
-
-        final DialogInterface.OnCancelListener cancelListener =
-                new DialogInterface.OnCancelListener() {
-            public void onCancel(DialogInterface dialog) {
-                // If nothing remains, close activity
-                if (!hasValidState()) {
-                    finish();
-                }
-            }
-        };
-
-        final AlertDialog.Builder builder = new AlertDialog.Builder(this);
-        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) {
-                removeDialog(DIALOG_SELECT_ACCOUNT);
-            }
-        });
-        return result;
-    }
-
-    /**
-     * @param account may be null to signal a device-local contact should
-     *     be created.
-     */
-    private void createContact(Account account) {
-        final Sources sources = Sources.getInstance(this);
-        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);
-        final Bundle extras = getIntent().getExtras();
-        EntityModifier.parseExtras(this, source, insert, extras);
-
-        // 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();
-    }
-
-    /**
-     * 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(this);
-        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);
-    }
-
-    @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);
-        }
-    }
-
-    public DialogManager getDialogManager() {
-        return mDialogManager;
-    }
-}
diff --git a/src/com/android/contacts/views/detail/ContactDetailFragment.java b/src/com/android/contacts/views/detail/ContactDetailFragment.java
index 49471cc..a35285f 100644
--- a/src/com/android/contacts/views/detail/ContactDetailFragment.java
+++ b/src/com/android/contacts/views/detail/ContactDetailFragment.java
@@ -195,6 +195,7 @@
     }
 
     public void loadUri(Uri lookupUri) {
+        // TODO: Ensure we are not loading twice here
         mLookupUri = lookupUri;
         if (mIsInitialized) startLoading(LOADER_DETAILS, null);
     }
diff --git a/src/com/android/contacts/views/editor/ContactEditorFragment.java b/src/com/android/contacts/views/editor/ContactEditorFragment.java
index afb6f70..e50994a 100644
--- a/src/com/android/contacts/views/editor/ContactEditorFragment.java
+++ b/src/com/android/contacts/views/editor/ContactEditorFragment.java
@@ -16,96 +16,139 @@
 
 package com.android.contacts.views.editor;
 
-import com.android.contacts.ContactOptionsActivity;
+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.DataKind;
-import com.android.contacts.ui.EditContactActivity;
-import com.android.contacts.views.ContactLoader;
-import com.android.contacts.views.editor.viewModel.BaseViewModel;
-import com.android.contacts.views.editor.viewModel.EmailViewModel;
-import com.android.contacts.views.editor.viewModel.FooterViewModel;
-import com.android.contacts.views.editor.viewModel.GenericViewModel;
-import com.android.contacts.views.editor.viewModel.ImViewModel;
-import com.android.contacts.views.editor.viewModel.OrganizationViewModel;
-import com.android.contacts.views.editor.viewModel.PhotoViewModel;
-import com.android.contacts.views.editor.viewModel.StructuredNameViewModel;
-import com.android.contacts.views.editor.viewModel.StructuredPostalViewModel;
+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.Entity;
 import android.content.Intent;
 import android.content.Loader;
-import android.content.Entity.NamedContentValues;
+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.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.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.OnCreateContextMenuListener;
+import android.view.View.OnClickListener;
+import android.widget.ArrayAdapter;
 import android.widget.LinearLayout;
-import android.widget.ListView;
+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;
 
-public class ContactEditorFragment extends LoaderManagingFragment<ContactLoader.Result>
-        implements OnCreateContextMenuListener {
+//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 ContactEditorFragment extends LoaderManagingFragment<ContactEditorLoader.Result> {
+
     private static final String TAG = "ContactEditorFragment";
 
-    private static final String BUNDLE_RAW_CONTACT_ID = "rawContactId";
+    private static final int LOADER_DATA = 1;
 
-    private static final int LOADER_DETAILS = 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 Uri mLookupUri;
+    private String mAction;
+    private Uri mUri;
+    private String mMimeType;
+    private Bundle mIntentExtras;
     private Listener mListener;
 
+    private String mQuerySelection;
+    private String[] mQuerySelectionArgs;
+
+    private long mContactIdForJoin;
+
+    private LinearLayout mContent;
+    private EntitySet mState;
+
+    private ViewIdGenerator mViewIdGenerator;
+
     private boolean mIsInitialized;
 
-    private ContactLoader.Result mContactData;
-    private ContactEditorHeaderView mHeaderView;
-    private LinearLayout mFieldContainer;
-
-    private int mReadOnlySourcesCnt;
-    private int mWritableSourcesCnt;
-    private boolean mAllRestricted;
-
-    /**
-     * A list of RawContacts included in this Contact.
-     */
-    private ArrayList<DisplayRawContact> mRawContacts = new ArrayList<DisplayRawContact>();
-
-    private LayoutInflater mInflater;
-
     public ContactEditorFragment() {
-        // Explicit constructor for inflation
     }
 
     @Override
@@ -118,442 +161,1182 @@
     public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedState) {
         final View view = inflater.inflate(R.layout.contact_editor_fragment, container, false);
 
-        setHasOptionsMenu(true);
+        mContent = (LinearLayout) view.findViewById(R.id.editors);
 
-        mInflater = inflater;
-
-        mHeaderView = (ContactEditorHeaderView) view.findViewById(R.id.contact_header_widget);
-
-        mFieldContainer = (LinearLayout) view.findViewById(R.id.field_container);
-        mFieldContainer.setOnCreateContextMenuListener(this);
-        mFieldContainer.setScrollBarStyle(ListView.SCROLLBARS_OUTSIDE_OVERLAY);
+        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 (mIsInitialized) {
+            if (Intent.ACTION_EDIT.equals(mAction)) {
+                // Read initial state from database
+                if (mListener != null) mListener.setTitleTo(R.string.editContact_title_edit);
+                startLoading(LOADER_DATA, null);
+            } else if (Intent.ACTION_INSERT.equals(mAction)) {
+                if (mListener != null) mListener.setTitleTo(R.string.editContact_title_insert);
+
+                doAddAction();
+            } else throw new IllegalArgumentException("Unknown Action String " + mAction +
+                    ". Only support " + Intent.ACTION_EDIT + " or " + Intent.ACTION_INSERT);
+        }
+    }
+
+    @Override
+    protected void onInitializeLoaders() {
+        mIsInitialized = true;
+        if (mUri != null) {
+            if (Intent.ACTION_EDIT.equals(mAction)) {
+                // Read initial state from database
+                if (mListener != null) mListener.setTitleTo(R.string.editContact_title_edit);
+                startLoading(LOADER_DATA, null);
+            } else if (Intent.ACTION_INSERT.equals(mAction)) {
+                if (mListener != null) mListener.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 setListener(Listener value) {
         mListener = value;
     }
 
-    public void loadUri(Uri lookupUri) {
-        mLookupUri = lookupUri;
-        if (mIsInitialized) startLoading(LOADER_DETAILS, null);
-    }
-
     @Override
-    protected void onInitializeLoaders() {
-        mIsInitialized = true;
-        if (mLookupUri != null) startLoading(LOADER_DETAILS, null);
-    }
+    public void onCreate(Bundle savedState) {
+        // TODO: Currently savedState is always null (framework issue). Test once this is fixed
+        super.onCreate(savedState);
 
-    @Override
-    protected Loader<ContactLoader.Result> onCreateLoader(int id, Bundle args) {
-        switch (id) {
-            case LOADER_DETAILS: {
-                return new ContactLoader(mContext, mLookupUri);
+        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);
             }
-            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);
-            }
+            mQuerySelection = savedState.getString(KEY_QUERY_SELECTION);
+            mQuerySelectionArgs = savedState.getStringArray(KEY_QUERY_SELECTION_ARGS);
+            mContactIdForJoin = savedState.getLong(KEY_CONTACT_ID_FOR_JOIN);
         }
     }
 
-    private void bindData() {
-        // Build up the contact entries
-        buildEntries();
+    @Override
+    protected Loader<ContactEditorLoader.Result> onCreateLoader(int id, Bundle args) {
+        return new ContactEditorLoader(mContext, mUri, mMimeType, mIntentExtras);
+    }
 
-        mHeaderView.setMergeInfo(mRawContacts.size());
+    @Override
+    protected void onLoadFinished(Loader<ContactEditorLoader.Result> loader,
+            ContactEditorLoader.Result data) {
+        if (data == ContactEditorLoader.Result.NOT_FOUND) {
+            // Item has been deleted
+            Log.i(TAG, "No contact found. Closing fragment");
+            if (mListener != null) mListener.closeBecauseContactNotFound();
+            return;
+        }
+        setData(data);
+    }
 
-        createFieldViews();
+    public void setData(ContactEditorLoader.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);
     }
 
     /**
-     * Build up the entries to display on the screen.
+     * @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 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
+    private void createContact(Account account, boolean prefillFromIntent) {
         final Sources sources = Sources.getInstance(mContext);
-
-        // Build up method entries
-        if (mContactData == null) {
-            return;
+        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);
         }
 
-        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();
+        // 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);
 
-            // Mark when this contact has any unrestricted components
-            final boolean isRestricted = entValues.getAsInteger(RawContacts.IS_RESTRICTED) != 0;
-            if (!isRestricted) mAllRestricted = false;
+        // Ensure we have some default fields
+        EntityModifier.ensureKindExists(insert, source, Phone.CONTENT_ITEM_TYPE);
+        EntityModifier.ensureKindExists(insert, source, Email.CONTENT_ITEM_TYPE);
 
-            final ContactsSource contactsSource = sources.getInflatedSource(accountType,
-                    ContactsSource.LEVEL_SUMMARY);
-            final boolean writable = contactsSource == null || !contactsSource.readOnly;
-            if (writable) {
-                mWritableSourcesCnt += 1;
-            } else {
-                mReadOnlySourcesCnt += 1;
-            }
-
-            final DisplayRawContact rawContact = new DisplayRawContact(mContext, contactsSource,
-                    accountName, rawContactId, writable, mRawContactFooterListener);
-            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_CONSTRAINTS);
-                if (kind == null) continue;
-
-                if (StructuredName.CONTENT_ITEM_TYPE.equals(mimeType)) {
-                    final StructuredNameViewModel itemEditor =
-                            StructuredNameViewModel.createForExisting(mContext, rawContact, dataId,
-                            entryValues, kind.titleRes);
-                    rawContact.getFields().add(itemEditor);
-                    continue;
-                }
-
-                if (StructuredPostal.CONTENT_ITEM_TYPE.equals(mimeType)) {
-                    final StructuredPostalViewModel itemEditor =
-                            StructuredPostalViewModel.createForExisting(mContext, rawContact,
-                            dataId, entryValues, kind.titleRes);
-                    rawContact.getFields().add(itemEditor);
-                    continue;
-                }
-
-                if (Email.CONTENT_ITEM_TYPE.equals(mimeType)) {
-                    final EmailViewModel itemEditor = EmailViewModel.createForExisting(mContext,
-                            rawContact, dataId, entryValues, kind.titleRes);
-                    rawContact.getFields().add(itemEditor);
-                    continue;
-                }
-
-                if (Im.CONTENT_ITEM_TYPE.equals(mimeType)) {
-                    final ImViewModel itemEditor = ImViewModel.createForExisting(mContext,
-                            rawContact, dataId, entryValues, kind.titleRes);
-                    rawContact.getFields().add(itemEditor);
-                    continue;
-                }
-
-                if (Nickname.CONTENT_ITEM_TYPE.equals(mimeType) ||
-                        Note.CONTENT_ITEM_TYPE.equals(mimeType) ||
-                        Website.CONTENT_ITEM_TYPE.equals(mimeType) ||
-                        Phone.CONTENT_ITEM_TYPE.equals(mimeType)) {
-                    final GenericViewModel itemEditor = GenericViewModel.fromDataKind(
-                            mContext, rawContact, dataId, entryValues, kind);
-                    rawContact.getFields().add(itemEditor);
-                    continue;
-                }
-
-                if (Organization.CONTENT_ITEM_TYPE.equals(mimeType)) {
-                    final OrganizationViewModel itemEditor =
-                            OrganizationViewModel.createForExisting(mContext, rawContact, dataId,
-                            entryValues, kind.titleRes);
-                    rawContact.getFields().add(itemEditor);
-                    continue;
-                }
-
-                if (Photo.CONTENT_ITEM_TYPE.equals(mimeType)) {
-                    final PhotoViewModel itemEditor =
-                            PhotoViewModel.createForExisting(mContext, rawContact, dataId,
-                            entryValues);
-                    rawContact.getFields().add(itemEditor);
-                    continue;
-                }
-            }
+        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 createFieldViews() {
-        mFieldContainer.removeAllViews();
+    private void bindEditors() {
+        // Sort the editors
+        Collections.sort(mState, mComparator);
 
-        for (int i = 0; i < mRawContacts.size(); i++) {
-            final DisplayRawContact rawContact = mRawContacts.get(i);
-            // Header
-            rawContact.getHeader().createAndAddView(mInflater, mFieldContainer);
+        // Remove any existing editors and rebuild any visible
+        mContent.removeAllViews();
 
-            // Data items
-            final ArrayList<BaseViewModel> fields = rawContact.getFields();
-            for (int j = 0; j < fields.size(); j++) {
-                fields.get(j).createAndAddView(mInflater, mFieldContainer);
+        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));
 
-            // Footer
-            rawContact.getFooter().createAndAddView(mInflater, mFieldContainer);
+            mContent.addView(editor);
+            editor.setState(entity, source, mViewIdGenerator);
         }
+
+        // Show editor now that we've loaded state
+        mContent.setVisibility(View.VISIBLE);
     }
 
     @Override
     public void onCreateOptionsMenu(Menu menu, final MenuInflater inflater) {
-        inflater.inflate(R.menu.view, menu);
+        inflater.inflate(R.menu.edit, menu);
     }
 
     @Override
     public void onPrepareOptionsMenu(Menu menu) {
-        // TODO: Prepare options
+        menu.findItem(R.id.menu_split).setVisible(mState != null && mState.size() > 1);
     }
 
     @Override
     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).getId();
-                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;
-            }
+            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 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);
+    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;
     }
 
-    // This was the ListView based code to expand/collapse sections.
-//    public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
-//        if (mListener == null) return;
-//        final BaseViewModel baseEntry = mAdapter.getEntry(position);
-//        if (baseEntry == null) return;
-//
-//        if (baseEntry instanceof HeaderViewModel) {
-//            // Toggle rawcontact visibility
-//            final HeaderViewModel entry = (HeaderViewModel) baseEntry;
-//            entry.setCollapsed(!entry.isCollapsed());
-//            mAdapter.notifyDataSetChanged();
-//        }
-//    }
-
-    private final DialogInterface.OnClickListener mDeleteListener =
-            new DialogInterface.OnClickListener() {
-        public void onClick(DialogInterface dialog, int which) {
-            mContext.getContentResolver().delete(mContactData.getLookupUri(), null, null);
+    /**
+     * 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) {
-        // 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:
+            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, mDeleteListener)
+                        .setPositiveButton(android.R.string.ok, new DeleteClickListener())
                         .setCancelable(false)
                         .create();
-            case R.id.detail_dialog_confirm_readonly_delete:
+            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, mDeleteListener)
+                        .setPositiveButton(android.R.string.ok, new DeleteClickListener())
                         .setCancelable(false)
                         .create();
-            case R.id.detail_dialog_confirm_multiple_delete:
+            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, mDeleteListener)
+                        .setPositiveButton(android.R.string.ok, new DeleteClickListener())
                         .setCancelable(false)
                         .create();
-            case R.id.detail_dialog_confirm_readonly_hide: {
+            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, mDeleteListener)
+                        .setPositiveButton(android.R.string.ok, new DeleteClickListener())
+                        .setCancelable(false)
                         .create();
-            }
-            case R.id.edit_dialog_add_information: {
-                final long rawContactId = bundle.getLong(BUNDLE_RAW_CONTACT_ID);
-                final DisplayRawContact rawContact = findRawContactById(rawContactId);
-                if (rawContact == null) return null;
-                final ContactsSource source = rawContact.getSource();
-
-                final ArrayList<DataKind> originalDataKinds = source.getSortedDataKinds();
-                // We should not modify the result returned from getSortedDataKinds but
-                // we have to filter some items out. Therefore we copy items into a new ArrayList
-                final ArrayList<DataKind> filteredDataKinds =
-                        new ArrayList<DataKind>(originalDataKinds.size());
-                final ArrayList<String> items = new ArrayList<String>(filteredDataKinds.size());
-                for (DataKind dataKind : originalDataKinds) {
-                    // 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);
-                    filteredDataKinds.add(dataKind);
-                }
-                final DialogInterface.OnClickListener itemClickListener =
-                        new DialogInterface.OnClickListener() {
-                    public void onClick(DialogInterface dialog, int which) {
-                        // Create an Intent for the INSERT-Editor. Its data is null
-                        // and the RawContact is identified in the Extras
-                        final String rawContactUriString = ContentUris.withAppendedId(
-                                RawContacts.CONTENT_URI, rawContactId).toString();
-                        final DataKind dataKind = filteredDataKinds.get(which);
-                        // TODO: Add new row
-//                        final Intent intent = new Intent();
-//                        intent.setType(dataKind.mimeType);
-//                        intent.setAction(Intent.ACTION_INSERT);
-//                        intent.putExtra(ContactFieldEditorActivity.BUNDLE_RAW_CONTACT_URI,
-//                                rawContactUriString);
-//                        if (mListener != null) mListener.onEditorRequested(intent);
-                    }
-                };
-                return new AlertDialog.Builder(mContext)
-                        .setItems(items.toArray(new String[0]), itemClickListener)
-                        .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 DisplayRawContact findRawContactById(long rawContactId) {
-        for (DisplayRawContact rawContact : mRawContacts) {
-            if (rawContact.getId() == rawContactId) return rawContact;
-        }
-        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()) {
+                    mListener.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 FooterViewModel.Listener mRawContactFooterListener =
-            new FooterViewModel.Listener() {
-        public void onAddClicked(DisplayRawContact rawContact) {
-            // 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 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 (mListener != null) mListener.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 (mListener != null) mListener.closeAfterSaving(resultCode, resultIntent);
+                break;
+            case SAVE_MODE_SPLIT:
+                if (mListener != null) mListener.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]);
+                }
             }
         }
-        public void onSeparateClicked(DisplayRawContact rawContact) {
+
+        // 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();
         }
-        public void onDeleteClicked(DisplayRawContact rawContact) {
-        }
-    };
+    }
+
+    /**
+     * 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 Listener {
         /**
          * Contact was not found, so somehow close this fragment.
          */
-        public void onContactNotFound();
+        void closeBecauseContactNotFound();
 
         /**
-         * There was an error loading the contact
+         * Contact was split, so we can close now
          */
-        public void onError();
+        void closeAfterSplit();
 
         /**
-         * User clicked a single item (e.g. mail) to edit it or is adding a new field
+         * User was presented with an account selection and couldn't decide.
          */
-        public void onEditorRequested(Intent intent);
+        void closeBecauseAccountSelectorAborted();
 
         /**
-         * Show a dialog using the globally unique id
+         * User has tapped Revert, close the fragment now.
          */
-        public void onDialogRequested(int id, Bundle bundle);
+        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);
+            mListener.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, ContactEditorFragment> {
+        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(ContactEditorFragment target, int saveMode) {
+            super(target);
+            mSaveMode = saveMode;
+            mContext = target.mContext;
+        }
+
+        /** {@inheritDoc} */
+        @Override
+        protected void onPreExecute(ContactEditorFragment 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(ContactEditorFragment 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(ContactEditorFragment 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);
+                }
+            }
+        }
+    }
+}
\ No newline at end of file
diff --git a/src/com/android/contacts/views/editor/ContactEditorHeaderView.java b/src/com/android/contacts/views/editor/ContactEditorHeaderView.java
deleted file mode 100644
index 18cbf72..0000000
--- a/src/com/android/contacts/views/editor/ContactEditorHeaderView.java
+++ /dev/null
@@ -1,65 +0,0 @@
-/*
- * 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/ContactEditorLoader.java b/src/com/android/contacts/views/editor/ContactEditorLoader.java
new file mode 100644
index 0000000..77c03c0
--- /dev/null
+++ b/src/com/android/contacts/views/editor/ContactEditorLoader.java
@@ -0,0 +1,207 @@
+/*
+ * 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.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 ContactEditorLoader extends Loader<ContactEditorLoader.Result> {
+    private static final String TAG = "ContactEditorLoader";
+
+    private final Uri mLookupUri;
+    private final String mMimeType;
+    private Result mContact;
+    private boolean mDestroyed;
+    private ForceLoadContentObserver mObserver;
+    private final Bundle mIntentExtras;
+
+    public ContactEditorLoader(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 = ensureIsContactUri(resolver, 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 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?
+            if (ContactsContract.AUTHORITY.equals(authority)) {
+                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
+            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(resolver,
+                        ContentUris.withAppendedId(RawContacts.CONTENT_URI, rawContactId));
+            }
+
+            throw new IllegalArgumentException("uri authority 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;
+    }
+}
\ No newline at end of file
diff --git a/src/com/android/contacts/views/editor/DisplayRawContact.java b/src/com/android/contacts/views/editor/DisplayRawContact.java
deleted file mode 100644
index 0e6564b..0000000
--- a/src/com/android/contacts/views/editor/DisplayRawContact.java
+++ /dev/null
@@ -1,74 +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.editor;
-
-import com.android.contacts.model.ContactsSource;
-import com.android.contacts.views.editor.viewModel.BaseViewModel;
-import com.android.contacts.views.editor.viewModel.FooterViewModel;
-import com.android.contacts.views.editor.viewModel.HeaderViewModel;
-
-import android.content.Context;
-
-import java.util.ArrayList;
-
-public class DisplayRawContact {
-    private final ContactsSource mSource;
-    private String mAccountName;
-    private final long mId;
-    private boolean mWritable;
-    private final HeaderViewModel mHeader;
-    private final FooterViewModel mFooter;
-    private final ArrayList<BaseViewModel> mFields = new ArrayList<BaseViewModel>();
-
-    public DisplayRawContact(Context context, ContactsSource source, String accountName, long id,
-            boolean writable, FooterViewModel.Listener footerListener) {
-        mSource = source;
-        mAccountName = accountName;
-        mId = id;
-        mWritable = writable;
-        mHeader = new HeaderViewModel(context, this);
-        mFooter = new FooterViewModel(context, this, footerListener);
-    }
-
-    public ContactsSource getSource() {
-        return mSource;
-    }
-
-    public String getAccountName() {
-        return mAccountName;
-    }
-
-    public long getId() {
-        return mId;
-    }
-
-    public boolean isWritable() {
-        return mWritable;
-    }
-
-    public ArrayList<BaseViewModel> getFields() {
-        return mFields;
-    }
-
-    public HeaderViewModel getHeader() {
-        return mHeader;
-    }
-
-    public FooterViewModel getFooter() {
-        return mFooter;
-    }
-}
diff --git a/src/com/android/contacts/views/editor/view/EditorItemView.java b/src/com/android/contacts/views/editor/view/EditorItemView.java
deleted file mode 100644
index 5d22d62..0000000
--- a/src/com/android/contacts/views/editor/view/EditorItemView.java
+++ /dev/null
@@ -1,181 +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.editor.view;
-
-import android.content.Context;
-import android.util.AttributeSet;
-import android.view.View;
-import android.view.View.OnClickListener;
-import android.view.View.OnFocusChangeListener;
-import android.widget.Button;
-import android.widget.EditText;
-import android.widget.LinearLayout;
-import android.widget.TextView;
-
-public class EditorItemView extends LinearLayout implements OnClickListener, OnFocusChangeListener {
-    private final int CAPTION_WIDTH_DIP = 70;
-    private final int TYPE_BUTTON_WIDTH_DIP = 50;
-
-    private TextView mCaptionTextView;
-    private LinearLayout mFieldContainer;
-    private Button mTypeButton;
-
-    private Listener mListener;
-
-    private int[] mTypeResIds;
-    private int mCustomTypeIndex;
-
-    private boolean mHasFocus;
-
-    public EditorItemView(Context context) {
-        super(context);
-        createEmptyLayout();
-    }
-
-    public EditorItemView(Context context, AttributeSet attrs) {
-        super(context, attrs);
-        createEmptyLayout();
-    }
-
-    public EditorItemView(Context context, AttributeSet attrs, int defStyle) {
-        super(context, attrs, defStyle);
-        createEmptyLayout();
-    }
-
-    private void createEmptyLayout() {
-        final int captionWidthPixels =
-                (int) (getResources().getDisplayMetrics().density * CAPTION_WIDTH_DIP);
-
-        // Caption
-        mCaptionTextView = new TextView(getContext());
-        mCaptionTextView.setLayoutParams(new LinearLayout.LayoutParams(captionWidthPixels,
-                LayoutParams.WRAP_CONTENT));
-        addView(mCaptionTextView);
-
-        // Text Fields
-        mFieldContainer = new LinearLayout(getContext());
-        mFieldContainer.setOrientation(LinearLayout.VERTICAL);
-        mFieldContainer.setLayoutParams(new LinearLayout.LayoutParams(0, LayoutParams.WRAP_CONTENT,
-                1.0f));
-        addView(mFieldContainer);
-    }
-
-    /**
-     * Configures the View. This function must only be called once after construction
-     * @param captionResId
-     *         The caption of this item
-     * @param fieldResIds
-     *         Ressource Ids of the editable fields
-     * @param typeResIds
-     *         A list of user selectable type-ressource-ids. If this parameter is null,
-     *         no type can be selected
-     * @param customTypeIndex
-     *         The index of the type representing "Custom" (allowing the user to type
-     *         a custom type. If this is -1, no type is custom. If types is null, this
-     *         parameter is ignored.
-     */
-    public void configure(int captionResId, int[] fieldResIds, int[] typeResIds,
-            int customTypeIndex) {
-        mCaptionTextView.setText(captionResId);
-
-        // Create Fields
-        for (int fieldResId : fieldResIds) {
-            final EditText fieldEditText = new EditText(getContext());
-            fieldEditText.setLayoutParams(new LinearLayout.LayoutParams(LayoutParams.MATCH_PARENT,
-                    LayoutParams.WRAP_CONTENT));
-            fieldEditText.setContentDescription(getResources().getString(fieldResId));
-            fieldEditText.setOnFocusChangeListener(this);
-            mFieldContainer.addView(fieldEditText);
-        }
-
-        // Configure Type Button
-        mCustomTypeIndex = customTypeIndex;
-        mTypeResIds = typeResIds;
-        if (typeResIds != null) {
-            final int typeButtonWidthPixels =
-                (int) (getResources().getDisplayMetrics().density * TYPE_BUTTON_WIDTH_DIP);
-            mTypeButton = new Button(getContext());
-            mTypeButton.setLayoutParams(new LinearLayout.LayoutParams(typeButtonWidthPixels,
-                    LayoutParams.WRAP_CONTENT));
-        }
-    }
-
-    public void setFieldValue(int index, CharSequence value) {
-        final EditText editText = (EditText) mFieldContainer.getChildAt(index);
-        editText.setText(value);
-    }
-
-    public String getFieldValue(int index) {
-        final EditText editText = (EditText) mFieldContainer.getChildAt(index);
-        final CharSequence resultCharSequence = editText.getText();
-        if (resultCharSequence == null) return "";
-        return resultCharSequence.toString();
-    }
-
-    public void setType(int typeValue, String labelValue) {
-        if (mTypeButton == null) {
-            return;
-        }
-        if (typeValue == mCustomTypeIndex) {
-            mTypeButton.setText(labelValue);
-        } else {
-            final int typeResId = mTypeResIds[typeValue];
-            mTypeButton.setText(getResources().getString(typeResId));
-        }
-    }
-
-    @Override
-    public void onClick(View v) {
-        if (v == mTypeButton) {
-
-            return;
-        }
-    }
-
-    @Override
-    public void onFocusChange(View v, boolean hasFocus) {
-        // Focus was gained, lost etc. We should only fire an event if we lost focus to another
-        // control
-        if (mHasFocus && !hasFocus && mListener != null) {
-            mListener.onFocusLost();
-        }
-        mHasFocus = hasFocus;
-    }
-
-    public void setListener(Listener value) {
-        mListener = value;
-    }
-
-    public interface Listener {
-        /**
-         * Called when the user has changed the Type.
-         * @param newIndex
-         *         The index of the newly selected type (corresponds to the array passed as
-         *         typeResIds in configure.
-         * @param customText
-         *         If this is the type "Custom", this field contains the user-entered text.
-         *         Otherwise this parameter is null
-         */
-        void onTypeChanged(int newIndex, String customText);
-
-        /**
-         * Called when the user has navigated away from this editor (this is not raised if
-         * the focus switches between fields of the same editor).
-         */
-        void onFocusLost();
-    }
-}
diff --git a/src/com/android/contacts/views/editor/view/FieldAndTypeView.java b/src/com/android/contacts/views/editor/view/FieldAndTypeView.java
deleted file mode 100644
index 69e35b3..0000000
--- a/src/com/android/contacts/views/editor/view/FieldAndTypeView.java
+++ /dev/null
@@ -1,98 +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.editor.view;
-
-import com.android.contacts.R;
-
-import android.content.Context;
-import android.util.AttributeSet;
-import android.view.LayoutInflater;
-import android.view.View;
-import android.view.ViewGroup;
-import android.widget.Button;
-import android.widget.EditText;
-import android.widget.LinearLayout;
-import android.widget.TextView;
-
-public class FieldAndTypeView extends LinearLayout {
-    private TextView mCaptionTextView;
-    private EditText mFieldEditText;
-    private Button mTypeButton;
-    private Listener mListener;
-    private boolean mHasFocus;
-
-    public FieldAndTypeView(Context context) {
-        super(context);
-    }
-
-    public FieldAndTypeView(Context context, AttributeSet attrs) {
-        super(context, attrs);
-    }
-
-    public FieldAndTypeView(Context context, AttributeSet attrs, int defStyle) {
-        super(context, attrs, defStyle);
-    }
-
-    public static FieldAndTypeView inflate(LayoutInflater inflater, ViewGroup parent,
-            boolean attachToRoot) {
-        return (FieldAndTypeView) inflater.inflate(R.layout.list_edit_item_field_and_type,
-                parent, attachToRoot);
-    }
-
-    public void setListener(Listener value) {
-        mListener = value;
-    }
-
-    @Override
-    protected void onFinishInflate() {
-        super.onFinishInflate();
-
-        mCaptionTextView = (TextView) findViewById(R.id.caption);
-        mFieldEditText = (EditText) findViewById(R.id.field);
-        mFieldEditText.setOnFocusChangeListener(mFieldEditTextFocusChangeListener);
-        mTypeButton = (Button) findViewById(R.id.type);
-    }
-
-    public void setLabelText(int resId) {
-        mCaptionTextView.setText(resId);
-    }
-
-    public void setFieldValue(CharSequence value) {
-        mFieldEditText.setText(value);
-    }
-
-    public CharSequence getFieldValue() {
-        return mFieldEditText.getText();
-    }
-
-    public void setTypeDisplayLabel(CharSequence type) {
-        mTypeButton.setText(type);
-    }
-
-    private OnFocusChangeListener mFieldEditTextFocusChangeListener = new OnFocusChangeListener() {
-        public void onFocusChange(View v, boolean hasFocus) {
-            if (mHasFocus && !hasFocus && mListener != null) {
-                mListener.onFocusLost(FieldAndTypeView.this);
-            }
-            mHasFocus = hasFocus;
-        }
-    };
-
-    public interface Listener {
-        void onFocusLost(FieldAndTypeView view);
-    }
-}
diff --git a/src/com/android/contacts/views/editor/view/FooterView.java b/src/com/android/contacts/views/editor/view/FooterView.java
deleted file mode 100644
index adc47cd..0000000
--- a/src/com/android/contacts/views/editor/view/FooterView.java
+++ /dev/null
@@ -1,92 +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.editor.view;
-
-import com.android.contacts.R;
-
-import android.content.Context;
-import android.util.AttributeSet;
-import android.view.LayoutInflater;
-import android.view.View;
-import android.view.ViewGroup;
-import android.widget.Button;
-import android.widget.LinearLayout;
-
-public class FooterView extends LinearLayout {
-    private Button mAddInformationButton;
-    private Button mSeparateButton;
-    private Button mDeleteButton;
-    private Listener mListener;
-
-    public FooterView(Context context) {
-        super(context);
-    }
-
-    public FooterView(Context context, AttributeSet attrs) {
-        super(context, attrs);
-    }
-
-    public FooterView(Context context, AttributeSet attrs, int defStyle) {
-        super(context, attrs, defStyle);
-    }
-
-    public static FooterView inflate(LayoutInflater inflater, ViewGroup parent,
-            boolean attachToRoot) {
-        return (FooterView) inflater.inflate(R.layout.list_edit_item_footer, parent, attachToRoot);
-    }
-
-    @Override
-    protected void onFinishInflate() {
-        super.onFinishInflate();
-
-        mAddInformationButton = (Button) findViewById(R.id.add_information);
-        mAddInformationButton.setOnClickListener(mClickListener);
-
-        mSeparateButton = (Button) findViewById(R.id.separate);
-        mSeparateButton.setOnClickListener(mClickListener);
-
-        mDeleteButton = (Button) findViewById(R.id.delete);
-        mDeleteButton.setOnClickListener(mClickListener);
-    }
-
-    public void setListener(Listener value) {
-        mListener = value;
-    }
-
-    private OnClickListener mClickListener = new OnClickListener() {
-        public void onClick(View v) {
-            if (mListener == null) return;
-            switch (v.getId()) {
-                case R.id.add_information:
-                    mListener.onAddClicked();
-                    break;
-                case R.id.separate:
-                    mListener.onSeparateClicked();
-                    break;
-                case R.id.delete:
-                    mListener.onDeleteClicked();
-                    break;
-            }
-        }
-    };
-
-    public static interface Listener {
-        void onAddClicked();
-        void onSeparateClicked();
-        void onDeleteClicked();
-    }
-}
diff --git a/src/com/android/contacts/views/editor/view/HeaderView.java b/src/com/android/contacts/views/editor/view/HeaderView.java
deleted file mode 100644
index 6822520..0000000
--- a/src/com/android/contacts/views/editor/view/HeaderView.java
+++ /dev/null
@@ -1,66 +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.editor.view;
-
-import com.android.contacts.R;
-
-import android.content.Context;
-import android.graphics.drawable.Drawable;
-import android.util.AttributeSet;
-import android.view.LayoutInflater;
-import android.view.ViewGroup;
-import android.widget.ImageView;
-import android.widget.LinearLayout;
-import android.widget.TextView;
-
-public class HeaderView extends LinearLayout {
-    private ImageView mLogoImageView;
-    private TextView mCaptionTextView;
-
-    public HeaderView(Context context) {
-        super(context);
-    }
-
-    public HeaderView(Context context, AttributeSet attrs) {
-        super(context, attrs);
-    }
-
-    public HeaderView(Context context, AttributeSet attrs, int defStyle) {
-        super(context, attrs, defStyle);
-    }
-
-    public static HeaderView inflate(LayoutInflater inflater, ViewGroup parent,
-            boolean attachToRoot) {
-        return (HeaderView) inflater.inflate(R.layout.list_edit_item_header, parent, attachToRoot);
-    }
-
-    @Override
-    protected void onFinishInflate() {
-        super.onFinishInflate();
-
-        mLogoImageView = (ImageView) findViewById(R.id.logo);
-        mCaptionTextView = (TextView) findViewById(R.id.caption);
-    }
-
-    public void setCaptionText(String value) {
-        mCaptionTextView.setText(value);
-    }
-
-    public void setLogo(Drawable value) {
-        mLogoImageView.setImageDrawable(value);
-    }
-}
diff --git a/src/com/android/contacts/views/editor/view/OrganizationView.java b/src/com/android/contacts/views/editor/view/OrganizationView.java
deleted file mode 100644
index 53b400f..0000000
--- a/src/com/android/contacts/views/editor/view/OrganizationView.java
+++ /dev/null
@@ -1,108 +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.editor.view;
-
-import com.android.contacts.R;
-
-import android.content.Context;
-import android.util.AttributeSet;
-import android.view.LayoutInflater;
-import android.view.View;
-import android.view.ViewGroup;
-import android.widget.Button;
-import android.widget.EditText;
-import android.widget.LinearLayout;
-import android.widget.TextView;
-
-public class OrganizationView extends LinearLayout {
-    private TextView mCaptionTextView;
-    private EditText mCompanyFieldEditText;
-    private EditText mTitleFieldEditText;
-    private Button mTypeButton;
-    private Listener mListener;
-    private boolean mHasFocus;
-
-    public OrganizationView(Context context) {
-        super(context);
-    }
-
-    public OrganizationView(Context context, AttributeSet attrs) {
-        super(context, attrs);
-    }
-
-    public OrganizationView(Context context, AttributeSet attrs, int defStyle) {
-        super(context, attrs, defStyle);
-    }
-
-    public static OrganizationView inflate(LayoutInflater inflater, ViewGroup parent,
-            boolean attachToRoot) {
-        return (OrganizationView) inflater.inflate(R.layout.list_edit_item_organization, parent,
-                attachToRoot);
-    }
-
-    public void setListener(Listener value) {
-        mListener = value;
-    }
-
-    @Override
-    protected void onFinishInflate() {
-        super.onFinishInflate();
-
-        mCaptionTextView = (TextView) findViewById(R.id.caption);
-        mCompanyFieldEditText = (EditText) findViewById(R.id.company_field);
-        mCompanyFieldEditText.setOnFocusChangeListener(mFieldEditTextFocusChangeListener);
-        mTitleFieldEditText = (EditText) findViewById(R.id.title_field);
-        mTitleFieldEditText.setOnFocusChangeListener(mFieldEditTextFocusChangeListener);
-        mTypeButton = (Button) findViewById(R.id.type);
-    }
-
-    public void setLabelText(int resId) {
-        mCaptionTextView.setText(resId);
-    }
-
-    public void setFieldValues(CharSequence company, CharSequence title) {
-        mCompanyFieldEditText.setText(company);
-        mTitleFieldEditText.setText(title);
-    }
-
-    public CharSequence getCompanyFieldValue() {
-        return mCompanyFieldEditText.getText();
-    }
-
-    public CharSequence getTitleFieldValue() {
-        return mTitleFieldEditText.getText();
-    }
-
-    public void setTypeDisplayLabel(CharSequence type) {
-        mTypeButton.setText(type);
-    }
-
-    private OnFocusChangeListener mFieldEditTextFocusChangeListener = new OnFocusChangeListener() {
-        public void onFocusChange(View v, boolean hasFocus) {
-            boolean anyHasFocus = mCompanyFieldEditText.hasFocus()
-                    || mTitleFieldEditText.hasFocus();
-            if (mHasFocus && !anyHasFocus && mListener != null) {
-                mListener.onFocusLost(OrganizationView.this);
-            }
-            mHasFocus = anyHasFocus;
-        }
-    };
-
-    public interface Listener {
-        void onFocusLost(OrganizationView view);
-    }
-}
diff --git a/src/com/android/contacts/views/editor/view/PhotoView.java b/src/com/android/contacts/views/editor/view/PhotoView.java
deleted file mode 100644
index d43e90e..0000000
--- a/src/com/android/contacts/views/editor/view/PhotoView.java
+++ /dev/null
@@ -1,92 +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.editor.view;
-
-import com.android.contacts.R;
-
-import android.content.Context;
-import android.graphics.Bitmap;
-import android.util.AttributeSet;
-import android.view.LayoutInflater;
-import android.view.View;
-import android.view.ViewGroup;
-import android.widget.ImageView;
-import android.widget.LinearLayout;
-
-public class PhotoView extends LinearLayout {
-    private ImageView mPhotoImageView;
-    private ImageView mTakePhotoActionButton;
-    private ImageView mGalleryActionButton;
-    private Listener mListener;
-
-    public PhotoView(Context context) {
-        super(context);
-    }
-
-    public PhotoView(Context context, AttributeSet attrs) {
-        super(context, attrs);
-    }
-
-    public PhotoView(Context context, AttributeSet attrs, int defStyle) {
-        super(context, attrs, defStyle);
-    }
-
-    public static PhotoView inflate(LayoutInflater inflater, ViewGroup parent,
-            boolean attachToRoot) {
-        return (PhotoView) inflater.inflate(R.layout.list_edit_item_photo, parent, attachToRoot);
-    }
-
-    @Override
-    protected void onFinishInflate() {
-        super.onFinishInflate();
-
-        mPhotoImageView = (ImageView) findViewById(R.id.photo);
-
-        mTakePhotoActionButton = (ImageView) findViewById(R.id.action_icon);
-        mTakePhotoActionButton.setOnClickListener(mClickListener);
-
-        mGalleryActionButton = (ImageView) findViewById(R.id.secondary_action_button);
-        mGalleryActionButton.setOnClickListener(mClickListener);
-    }
-
-    public void setListener(Listener value) {
-        mListener = value;
-    }
-
-    public void setPhoto(Bitmap value) {
-        mPhotoImageView.setImageBitmap(value);
-    }
-
-    private OnClickListener mClickListener = new OnClickListener() {
-        public void onClick(View v) {
-            if (mListener == null) return;
-            switch (v.getId()) {
-                case R.id.action_icon:
-                    mListener.onTakePhotoClicked();
-                    break;
-                case R.id.secondary_action_button:
-                    mListener.onChooseFromGalleryClicked();
-                    break;
-            }
-        }
-    };
-
-    public static interface Listener {
-        void onTakePhotoClicked();
-        void onChooseFromGalleryClicked();
-    }
-}
diff --git a/src/com/android/contacts/views/editor/view/SimpleOrStructuredView.java b/src/com/android/contacts/views/editor/view/SimpleOrStructuredView.java
deleted file mode 100644
index 2bd0bd0..0000000
--- a/src/com/android/contacts/views/editor/view/SimpleOrStructuredView.java
+++ /dev/null
@@ -1,104 +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.editor.view;
-
-import com.android.contacts.R;
-
-import android.content.Context;
-import android.util.AttributeSet;
-import android.view.LayoutInflater;
-import android.view.View;
-import android.view.ViewGroup;
-import android.widget.Button;
-import android.widget.EditText;
-import android.widget.LinearLayout;
-import android.widget.TextView;
-
-public class SimpleOrStructuredView extends LinearLayout {
-    private TextView mCaptionTextView;
-    private EditText mFieldEditText;
-    private Button mStructuredEditorButton;
-    private Listener mListener;
-    private boolean mHasFocus;
-
-    public SimpleOrStructuredView(Context context) {
-        super(context);
-    }
-
-    public SimpleOrStructuredView(Context context, AttributeSet attrs) {
-        super(context, attrs);
-    }
-
-    public SimpleOrStructuredView(Context context, AttributeSet attrs, int defStyle) {
-        super(context, attrs, defStyle);
-    }
-
-    public static SimpleOrStructuredView inflate(LayoutInflater inflater, ViewGroup parent,
-            boolean attachToRoot) {
-        return (SimpleOrStructuredView) inflater.inflate(
-                R.layout.list_edit_item_simple_or_structured, parent, attachToRoot);
-    }
-
-    public void setListener(Listener value) {
-        mListener = value;
-    }
-
-    @Override
-    protected void onFinishInflate() {
-        super.onFinishInflate();
-
-        mCaptionTextView = (TextView) findViewById(R.id.caption);
-        mFieldEditText = (EditText) findViewById(R.id.field);
-        mFieldEditText.setOnFocusChangeListener(mFieldEditTextFocusChangeListener);
-        mStructuredEditorButton = (Button) findViewById(R.id.structuredEditorButton);
-        mStructuredEditorButton.setOnClickListener(mFullEditorClickListener);
-    }
-
-    public void setLabelText(int resId) {
-        mCaptionTextView.setText(resId);
-    }
-
-    public void setDisplayName(CharSequence value) {
-        mFieldEditText.setText(value);
-    }
-
-    public CharSequence getDisplayName() {
-        return mFieldEditText.getText();
-    }
-
-    private OnFocusChangeListener mFieldEditTextFocusChangeListener = new OnFocusChangeListener() {
-        public void onFocusChange(View v, boolean hasFocus) {
-            if (mHasFocus && !hasFocus && mListener != null) {
-                mListener.onFocusLost(SimpleOrStructuredView.this);
-            }
-            mHasFocus = hasFocus;
-        }
-    };
-
-    private OnClickListener mFullEditorClickListener = new OnClickListener() {
-        public void onClick(View v) {
-            if (mListener != null) {
-                mListener.onStructuredEditorRequested(SimpleOrStructuredView.this);
-            }
-        }
-    };
-
-    public interface Listener {
-        void onFocusLost(SimpleOrStructuredView view);
-        void onStructuredEditorRequested(SimpleOrStructuredView view);
-    }
-}
diff --git a/src/com/android/contacts/views/editor/viewModel/BaseViewModel.java b/src/com/android/contacts/views/editor/viewModel/BaseViewModel.java
deleted file mode 100644
index 3e5a4f4..0000000
--- a/src/com/android/contacts/views/editor/viewModel/BaseViewModel.java
+++ /dev/null
@@ -1,46 +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.editor.viewModel;
-
-import com.android.contacts.views.editor.DisplayRawContact;
-
-import android.content.Context;
-import android.view.LayoutInflater;
-import android.view.View;
-import android.view.ViewGroup;
-
-public abstract class BaseViewModel {
-    private final DisplayRawContact mRawContact;
-    private final Context mContext;
-
-    public BaseViewModel(Context context, DisplayRawContact rawContact) {
-        if (context == null) throw new IllegalArgumentException("context must not be null");
-        if (rawContact == null) throw new IllegalArgumentException("rawContact must not be null");
-        mContext = context;
-        mRawContact = rawContact;
-    }
-
-    public DisplayRawContact getRawContact() {
-        return mRawContact;
-    }
-
-    public Context getContext() {
-        return mContext;
-    }
-
-    public abstract View createAndAddView(LayoutInflater inflater, ViewGroup parent);
-}
diff --git a/src/com/android/contacts/views/editor/viewModel/DataViewModel.java b/src/com/android/contacts/views/editor/viewModel/DataViewModel.java
deleted file mode 100644
index 23e98e4..0000000
--- a/src/com/android/contacts/views/editor/viewModel/DataViewModel.java
+++ /dev/null
@@ -1,101 +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.editor.viewModel;
-
-import com.android.contacts.views.ContactSaveService;
-import com.android.contacts.views.editor.DisplayRawContact;
-import com.android.internal.util.ArrayUtils;
-
-import android.content.ContentProviderOperation;
-import android.content.ContentResolver;
-import android.content.ContentUris;
-import android.content.ContentValues;
-import android.content.Context;
-import android.content.Intent;
-import android.content.ContentProviderOperation.Builder;
-import android.net.Uri;
-import android.provider.ContactsContract.Data;
-
-import java.util.ArrayList;
-
-public abstract class DataViewModel extends BaseViewModel {
-    private final long mDataId;
-    private final ContentValues mContentValues;
-    private final String mMimeType;
-    private final Uri mDataUri;
-
-    protected DataViewModel(Context context, DisplayRawContact rawContact, long dataId,
-            ContentValues contentValues, String mimeType) {
-        super(context, rawContact);
-        mDataId = dataId;
-        mContentValues = contentValues;
-        mMimeType = mimeType;
-        mDataUri = ContentUris.withAppendedId(Data.CONTENT_URI, mDataId);
-    }
-
-    public long getDataId() {
-        return mDataId;
-    }
-
-    public Uri getDataUri() {
-        return mDataUri;
-    }
-
-    protected ContentValues getContentValues() {
-        return mContentValues;
-    }
-
-    public String getMimeType() {
-        return mMimeType;
-    }
-
-    /**
-     * Uncoditionally saves the current state to the database. No difference analysis is performed
-     */
-    public void saveData() {
-        final ContentResolver resolver = getContext().getContentResolver();
-
-        final ArrayList<ContentProviderOperation> operations =
-            new ArrayList<ContentProviderOperation>();
-
-        final Builder builder;
-        if (getDataUri() == null) {
-            // INSERT
-            builder = ContentProviderOperation.newInsert(Data.CONTENT_URI);
-            builder.withValue(Data.MIMETYPE, getMimeType());
-            builder.withValue(Data.RAW_CONTACT_ID, getRawContact().getId());
-            writeToBuilder(builder, true);
-        } else {
-            // UPDATE
-            builder = ContentProviderOperation.newUpdate(getDataUri());
-            writeToBuilder(builder, false);
-        }
-        operations.add(builder.build());
-
-        // Tell the Service to save
-        // TODO: Handle the case where the data element has been removed in the background
-        final Intent serviceIntent = new Intent();
-        final ContentProviderOperation[] operationsArray =
-                operations.toArray(ArrayUtils.emptyArray(ContentProviderOperation.class));
-        serviceIntent.putExtra(ContactSaveService.EXTRA_OPERATIONS, operationsArray);
-        serviceIntent.setClass(getContext().getApplicationContext(), ContactSaveService.class);
-
-        getContext().startService(serviceIntent);
-    }
-
-    protected abstract void writeToBuilder(final Builder builder, boolean isInsert);
-}
diff --git a/src/com/android/contacts/views/editor/viewModel/EmailViewModel.java b/src/com/android/contacts/views/editor/viewModel/EmailViewModel.java
deleted file mode 100644
index bcc4397..0000000
--- a/src/com/android/contacts/views/editor/viewModel/EmailViewModel.java
+++ /dev/null
@@ -1,41 +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.editor.viewModel;
-
-import com.android.contacts.views.editor.DisplayRawContact;
-
-import android.content.ContentValues;
-import android.content.Context;
-import android.provider.ContactsContract.CommonDataKinds.Email;
-
-public class EmailViewModel extends FieldAndTypeViewModel {
-    private EmailViewModel(Context context, DisplayRawContact rawContact, long dataId,
-            ContentValues contentValues, int titleResId) {
-        super(context, rawContact, dataId, contentValues, Email.CONTENT_ITEM_TYPE, titleResId,
-                Email.ADDRESS, Email.TYPE, Email.LABEL);
-    }
-
-    public static EmailViewModel createForExisting(Context context, DisplayRawContact rawContact,
-            long dataId, ContentValues contentValues, int titleResId) {
-        return new EmailViewModel(context, rawContact, dataId, contentValues, titleResId);
-    }
-
-    @Override
-    protected CharSequence getTypeDisplayLabel() {
-        return Email.getTypeLabel(getContext().getResources(), getType(), getLabel());
-    }
-}
diff --git a/src/com/android/contacts/views/editor/viewModel/FieldAndTypeViewModel.java b/src/com/android/contacts/views/editor/viewModel/FieldAndTypeViewModel.java
deleted file mode 100644
index 3ca11ae..0000000
--- a/src/com/android/contacts/views/editor/viewModel/FieldAndTypeViewModel.java
+++ /dev/null
@@ -1,112 +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.editor.viewModel;
-
-import com.android.contacts.views.editor.DisplayRawContact;
-import com.android.contacts.views.editor.view.FieldAndTypeView;
-
-import android.content.ContentValues;
-import android.content.Context;
-import android.content.ContentProviderOperation.Builder;
-import android.text.TextUtils;
-import android.util.Log;
-import android.view.LayoutInflater;
-import android.view.ViewGroup;
-
-public abstract class FieldAndTypeViewModel extends DataViewModel {
-    private static final String TAG = "FieldAndTypeViewModel";
-
-    private final int mLabelResId;
-    private final String mFieldColumn;
-    private final String mLabelColumn;
-    private final String mTypeColumn;
-
-    protected FieldAndTypeViewModel(Context context, DisplayRawContact rawContact,
-            long dataId, ContentValues contentValues, String mimeType, int labelResId,
-            String fieldColumn, String typeColumn, String labelColumn) {
-        super(context, rawContact, dataId, contentValues, mimeType);
-        mLabelResId = labelResId;
-
-        mFieldColumn = fieldColumn;
-        mTypeColumn = typeColumn;
-        mLabelColumn = labelColumn;
-    }
-
-    @Override
-    public FieldAndTypeView createAndAddView(LayoutInflater inflater, ViewGroup parent) {
-        final FieldAndTypeView result = FieldAndTypeView.inflate(inflater, parent, false);
-
-        result.setListener(mViewListener);
-        result.setLabelText(mLabelResId);
-        result.setFieldValue(getFieldValue());
-        result.setTypeDisplayLabel(getTypeDisplayLabel());
-
-        parent.addView(result);
-        return result;
-    }
-
-    @Override
-    protected void writeToBuilder(Builder builder, boolean isInsert) {
-        builder.withValue(mFieldColumn, getFieldValue());
-        builder.withValue(mTypeColumn, getType());
-        builder.withValue(mLabelColumn, getLabel());
-    }
-
-    protected String getFieldValue() {
-        return getContentValues().getAsString(mFieldColumn);
-    }
-
-    protected void putFieldValue(String value) {
-        getContentValues().put(mFieldColumn, value);
-    }
-
-    protected int getType() {
-        return getContentValues().getAsInteger(mTypeColumn).intValue();
-    }
-
-    protected void putType(int value) {
-        getContentValues().put(mTypeColumn, value);
-    }
-
-    protected String getLabel() {
-        return getContentValues().getAsString(mLabelColumn);
-    }
-
-    protected void putLabel(String value) {
-        getContentValues().put(mLabelColumn, value);
-    }
-
-    protected abstract CharSequence getTypeDisplayLabel();
-
-    private FieldAndTypeView.Listener mViewListener = new FieldAndTypeView.Listener() {
-        public void onFocusLost(FieldAndTypeView view) {
-            Log.v(TAG, "Received FocusLost. Checking for changes");
-            boolean hasChanged = false;
-
-            final String oldValue = getFieldValue();
-            final String newValue = view.getFieldValue().toString();
-            if (!TextUtils.equals(oldValue, newValue)) {
-                putFieldValue(newValue);
-                hasChanged = true;
-            }
-            if (hasChanged) {
-                Log.v(TAG, "Found changes. Updating DB");
-                saveData();
-            }
-        }
-    };
-}
diff --git a/src/com/android/contacts/views/editor/viewModel/FooterViewModel.java b/src/com/android/contacts/views/editor/viewModel/FooterViewModel.java
deleted file mode 100644
index df254fc..0000000
--- a/src/com/android/contacts/views/editor/viewModel/FooterViewModel.java
+++ /dev/null
@@ -1,64 +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.editor.viewModel;
-
-import com.android.contacts.views.editor.DisplayRawContact;
-import com.android.contacts.views.editor.view.FooterView;
-
-import android.content.Context;
-import android.view.LayoutInflater;
-import android.view.View;
-import android.view.ViewGroup;
-
-public class FooterViewModel extends BaseViewModel {
-    private final Listener mListener;
-
-    public FooterViewModel(Context context, DisplayRawContact rawContact, Listener listener) {
-        super(context, rawContact);
-        if (listener == null) throw new IllegalArgumentException("listener must not be null");
-        mListener = listener;
-    }
-
-    @Override
-    public View createAndAddView(LayoutInflater inflater, ViewGroup parent) {
-        final FooterView result = FooterView.inflate(inflater, parent, false);
-
-        result.setListener(mViewListener);
-        parent.addView(result);
-        return result;
-    }
-
-    private FooterView.Listener mViewListener = new FooterView.Listener() {
-        public void onAddClicked() {
-            if (mListener != null) mListener.onAddClicked(getRawContact());
-        }
-
-        public void onSeparateClicked() {
-            if (mListener != null) mListener.onAddClicked(getRawContact());
-        }
-
-        public void onDeleteClicked() {
-            if (mListener != null) mListener.onAddClicked(getRawContact());
-        }
-    };
-
-    public interface Listener {
-        public void onAddClicked(DisplayRawContact rawContact);
-        public void onSeparateClicked(DisplayRawContact rawContact);
-        public void onDeleteClicked(DisplayRawContact rawContact);
-    }
-}
diff --git a/src/com/android/contacts/views/editor/viewModel/GenericViewModel.java b/src/com/android/contacts/views/editor/viewModel/GenericViewModel.java
deleted file mode 100644
index 5817876..0000000
--- a/src/com/android/contacts/views/editor/viewModel/GenericViewModel.java
+++ /dev/null
@@ -1,188 +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.editor.viewModel;
-
-import com.android.contacts.model.ContactsSource.DataKind;
-import com.android.contacts.model.ContactsSource.EditField;
-import com.android.contacts.model.ContactsSource.EditType;
-import com.android.contacts.views.editor.DisplayRawContact;
-import com.android.contacts.views.editor.view.EditorItemView;
-
-import android.content.ContentValues;
-import android.content.Context;
-import android.content.ContentProviderOperation.Builder;
-import android.provider.ContactsContract.CommonDataKinds.BaseTypes;
-import android.text.TextUtils;
-import android.util.Log;
-import android.view.LayoutInflater;
-import android.view.ViewGroup;
-
-public class GenericViewModel extends DataViewModel {
-    private static final String TAG = "GenericViewModel";
-
-    private final int mLabelResId;
-    private final Field[] mFields;
-    private final int[] mTypeResIds;
-    private final int mCustomTypeIndex;
-    private final String mTypeColumn;
-    private final String mLabelColumn;
-
-    private EditorItemView mEditorItemView;
-
-    /**
-     * Overload without custom types
-     */
-    protected GenericViewModel(Context context, DisplayRawContact rawContact,
-            long dataId, ContentValues contentValues, String mimeType, int labelResId,
-            Field[] fields) {
-        this(context, rawContact, dataId, contentValues, mimeType, labelResId, fields,
-                null, -1, null, null);
-    }
-
-    protected GenericViewModel(Context context, DisplayRawContact rawContact,
-            long dataId, ContentValues contentValues, String mimeType, int labelResId,
-            Field[] fields, int[] typeResIds, int customTypeIndex,
-            String typeColumn, String labelColumn) {
-        super(context, rawContact, dataId, contentValues, mimeType);
-        mLabelResId = labelResId;
-        mFields = fields;
-        mTypeResIds = typeResIds;
-        mCustomTypeIndex = customTypeIndex;
-        mTypeColumn = typeColumn;
-        mLabelColumn = labelColumn;
-    }
-
-    public static GenericViewModel fromDataKind(Context context, DisplayRawContact rawContact,
-            long dataId, ContentValues contentValues, DataKind dataKind) {
-        final Field[] fields = new Field[dataKind.fieldList.size()];
-        for (int i = 0; i < fields.length; i++) {
-            final EditField editField = dataKind.fieldList.get(i);
-            fields[i] = new Field(editField.titleRes, editField.column);
-        }
-        final int[] typeResIds;
-        if (dataKind.typeList == null) {
-            typeResIds = null;
-        } else {
-            typeResIds = new int[dataKind.typeList.size()];
-            for (int i = 0; i < typeResIds.length; i++) {
-                final EditType editType = dataKind.typeList.get(i);
-                typeResIds[i] = editType.labelRes;
-            }
-        }
-        return new GenericViewModel(context, rawContact, dataId, contentValues, dataKind.mimeType,
-                dataKind.titleRes, fields, typeResIds, BaseTypes.TYPE_CUSTOM,
-                dataKind.typeColumn, "");
-    }
-
-    @Override
-    public EditorItemView createAndAddView(LayoutInflater inflater, ViewGroup parent) {
-        if (mEditorItemView == null) {
-            final EditorItemView result = new EditorItemView(getContext());
-            result.setListener(mViewListener);
-
-            final int[] fieldResIds = new int[mFields.length];
-            for (int i = 0; i < mFields.length; i++) {
-                fieldResIds[i] = mFields[i].getResId();
-            }
-
-            result.configure(mLabelResId, fieldResIds, mTypeResIds, mCustomTypeIndex);
-
-            parent.addView(result);
-
-            mEditorItemView = result;
-        }
-
-        // Set fields
-        final ContentValues contentValues = getContentValues();
-        for (int i = 0; i < mFields.length; i++) {
-            mEditorItemView.setFieldValue(i, contentValues.getAsString(mFields[i].getName()));
-        }
-
-        // Set type if required
-        if (mTypeColumn != null) {
-            mEditorItemView.setType(getTypeValue(), getLabelValue());
-        }
-
-        return mEditorItemView;
-    }
-
-    @Override
-    protected void writeToBuilder(Builder builder, boolean isInsert) {
-        for (int i = 0; i < mFields.length; i++) {
-            final String fieldName = mFields[i].getName();
-            builder.withValue(fieldName, getContentValues().getAsString(fieldName));
-        }
-        if (mTypeColumn != null) {
-            builder.withValue(mTypeColumn, getTypeValue());
-            builder.withValue(mLabelColumn, getLabelValue());
-        }
-    }
-
-    private int getTypeValue() {
-        return getContentValues().getAsInteger(mTypeColumn);
-    }
-
-    private String getLabelValue() {
-        return getContentValues().getAsString(mLabelColumn);
-    }
-
-    private EditorItemView.Listener mViewListener = new EditorItemView.Listener() {
-        public void onFocusLost() {
-            Log.v(TAG, "Received FocusLost. Checking for changes");
-            boolean hasChanged = false;
-
-            final ContentValues contentValues = getContentValues();
-
-            for (int i = 0; i < mFields.length; i++) {
-                final String oldValue = contentValues.getAsString(mFields[i].getName());
-                final String newValue = mEditorItemView.getFieldValue(i);
-                if (!TextUtils.equals(oldValue, newValue)) {
-                    contentValues.put(mFields[i].getName(), newValue);
-                    hasChanged = true;
-                }
-            }
-            if (hasChanged) {
-                Log.v(TAG, "Found changes. Updating DB");
-                saveData();
-            }
-        }
-
-        @Override
-        public void onTypeChanged(int newIndex, String customText) {
-            // TODO Auto-generated method stub
-
-        }
-    };
-
-    /* package */ static class Field {
-        private int mResId;
-        private String mName;
-
-        public int getResId() {
-            return mResId;
-        }
-
-        public String getName() {
-            return mName;
-        }
-
-        public Field(int resourceId, String databaseField) {
-            mResId = resourceId;
-            mName = databaseField;
-        }
-    }
-}
diff --git a/src/com/android/contacts/views/editor/viewModel/HeaderViewModel.java b/src/com/android/contacts/views/editor/viewModel/HeaderViewModel.java
deleted file mode 100644
index 901452c..0000000
--- a/src/com/android/contacts/views/editor/viewModel/HeaderViewModel.java
+++ /dev/null
@@ -1,69 +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.editor.viewModel;
-
-import com.android.contacts.R;
-import com.android.contacts.views.editor.DisplayRawContact;
-import com.android.contacts.views.editor.view.HeaderView;
-
-import android.content.Context;
-import android.text.TextUtils;
-import android.view.LayoutInflater;
-import android.view.View;
-import android.view.ViewGroup;
-
-public class HeaderViewModel extends BaseViewModel {
-    private boolean mCollapsed;
-
-    public HeaderViewModel(Context context, DisplayRawContact rawContact) {
-        super(context, rawContact);
-    }
-
-    public boolean isCollapsed() {
-        return mCollapsed;
-    }
-
-    public void setCollapsed(boolean collapsed) {
-        mCollapsed = collapsed;
-    }
-
-    @Override
-    public View createAndAddView(LayoutInflater inflater, ViewGroup parent) {
-        final HeaderView result = HeaderView.inflate(inflater, parent, false);
-
-        CharSequence accountType = getRawContact().getSource().getDisplayLabel(getContext());
-        if (TextUtils.isEmpty(accountType)) {
-            accountType = getContext().getString(R.string.account_phone);
-        }
-        final String accountName = getRawContact().getAccountName();
-
-        final String accountTypeDisplay;
-        if (TextUtils.isEmpty(accountName)) {
-            accountTypeDisplay = getContext().getString(R.string.account_type_format,
-                    accountType);
-        } else {
-            accountTypeDisplay = getContext().getString(R.string.account_type_and_name,
-                    accountType, accountName);
-        }
-
-        result.setCaptionText(accountTypeDisplay);
-        result.setLogo(getRawContact().getSource().getDisplayIcon(getContext()));
-
-        parent.addView(result);
-        return result;
-    }
-}
diff --git a/src/com/android/contacts/views/editor/viewModel/ImViewModel.java b/src/com/android/contacts/views/editor/viewModel/ImViewModel.java
deleted file mode 100644
index 7ee3b21..0000000
--- a/src/com/android/contacts/views/editor/viewModel/ImViewModel.java
+++ /dev/null
@@ -1,58 +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.editor.viewModel;
-
-import com.android.contacts.views.editor.DisplayRawContact;
-
-import android.content.ContentValues;
-import android.content.Context;
-import android.content.ContentProviderOperation.Builder;
-import android.provider.ContactsContract.CommonDataKinds.Im;
-
-/**
- * Editor for Instant Messaging fields. The Type (HOME, WORK, OTHER, CUSTOM) is not shown but
- * instead the same field is used for showing the Protocol (AIM, YAHOO etc). When
- * creating new Im rows, the Type is set to OTHER
- */
-public class ImViewModel extends FieldAndTypeViewModel {
-    private ImViewModel(Context context, DisplayRawContact rawContact, long dataId,
-            ContentValues contentValues, int titleResId) {
-        super(context, rawContact, dataId, contentValues, Im.CONTENT_ITEM_TYPE, titleResId, Im.DATA,
-                Im.PROTOCOL, Im.CUSTOM_PROTOCOL);
-    }
-
-    public static ImViewModel createForExisting(Context context, DisplayRawContact rawContact,
-            long dataId, ContentValues contentValues, int titleResId) {
-        return new ImViewModel(context, rawContact, dataId, contentValues, titleResId);
-    }
-
-    @Override
-    protected CharSequence getTypeDisplayLabel() {
-        return Im.getProtocolLabel(getContext().getResources(), getType(), getLabel());
-    }
-
-    @Override
-    protected void writeToBuilder(Builder builder, boolean isInsert) {
-        // The Type field is not exposed in the UI. Write OTHER for Insert but don't change it
-        // for updates
-        if (isInsert) {
-            builder.withValue(Im.TYPE, Im.TYPE_OTHER);
-            builder.withValue(Im.LABEL, "");
-        }
-        super.writeToBuilder(builder, isInsert);
-    }
-}
diff --git a/src/com/android/contacts/views/editor/viewModel/OrganizationViewModel.java b/src/com/android/contacts/views/editor/viewModel/OrganizationViewModel.java
deleted file mode 100644
index bfa296d..0000000
--- a/src/com/android/contacts/views/editor/viewModel/OrganizationViewModel.java
+++ /dev/null
@@ -1,138 +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.editor.viewModel;
-
-import com.android.contacts.views.editor.DisplayRawContact;
-import com.android.contacts.views.editor.view.OrganizationView;
-
-import android.content.ContentValues;
-import android.content.Context;
-import android.content.ContentProviderOperation.Builder;
-import android.provider.ContactsContract.CommonDataKinds.Organization;
-import android.text.TextUtils;
-import android.util.Log;
-import android.view.LayoutInflater;
-import android.view.ViewGroup;
-
-public class OrganizationViewModel extends DataViewModel {
-    private static final String TAG = "OrganizationViewModel";
-
-    private final int mLabelResId;
-    private final String mCompanyFieldColumn;
-    private final String mTitleFieldColumn;
-    private final String mLabelColumn;
-    private final String mTypeColumn;
-
-    private OrganizationViewModel(Context context, DisplayRawContact rawContact,
-            long dataId, ContentValues contentValues, int labelResId) {
-        super(context, rawContact, dataId, contentValues, Organization.CONTENT_ITEM_TYPE);
-        mLabelResId = labelResId;
-
-        mCompanyFieldColumn = Organization.COMPANY;
-        mTitleFieldColumn = Organization.TITLE;
-        mTypeColumn = Organization.TYPE;
-        mLabelColumn = Organization.LABEL;
-    }
-
-    public static OrganizationViewModel createForExisting(Context context,
-            DisplayRawContact rawContact, long dataId, ContentValues contentValues,
-            int titleResId) {
-        return new OrganizationViewModel(context, rawContact, dataId, contentValues, titleResId);
-    }
-
-    @Override
-    public OrganizationView createAndAddView(LayoutInflater inflater, ViewGroup parent) {
-        final OrganizationView result = OrganizationView.inflate(inflater, parent, false);
-
-        result.setListener(mViewListener);
-        result.setLabelText(mLabelResId);
-        result.setFieldValues(getCompanyFieldValue(), getTitleFieldValue());
-        result.setTypeDisplayLabel(getTypeDisplayLabel());
-
-        parent.addView(result);
-        return result;
-    }
-
-    @Override
-    protected void writeToBuilder(Builder builder, boolean isInsert) {
-        builder.withValue(mCompanyFieldColumn, getCompanyFieldValue());
-        builder.withValue(mTitleFieldColumn, getTitleFieldValue());
-        builder.withValue(mTypeColumn, getType());
-        builder.withValue(mLabelColumn, getLabel());
-    }
-
-    protected String getCompanyFieldValue() {
-        return getContentValues().getAsString(mCompanyFieldColumn);
-    }
-
-    protected String getTitleFieldValue() {
-        return getContentValues().getAsString(mTitleFieldColumn);
-    }
-
-    private void putCompanyFieldValue(String value) {
-        getContentValues().put(mCompanyFieldColumn, value);
-    }
-
-    private void putTitleFieldValue(String value) {
-        getContentValues().put(mTitleFieldColumn, value);
-    }
-
-    private int getType() {
-        return getContentValues().getAsInteger(mTypeColumn).intValue();
-    }
-
-    private void putType(int value) {
-        getContentValues().put(mTypeColumn, value);
-    }
-
-    private String getLabel() {
-        return getContentValues().getAsString(mLabelColumn);
-    }
-
-    private void putLabel(String value) {
-        getContentValues().put(mLabelColumn, value);
-    }
-
-    private CharSequence getTypeDisplayLabel() {
-        return Organization.getTypeLabel(getContext().getResources(), getType(), getLabel());
-    }
-
-    private OrganizationView.Listener mViewListener = new OrganizationView.Listener() {
-        public void onFocusLost(OrganizationView view) {
-            Log.v(TAG, "Received FocusLost. Checking for changes");
-            boolean hasChanged = false;
-
-            final String oldCompanyValue = getCompanyFieldValue();
-            final String newCompanyValue = view.getCompanyFieldValue().toString();
-            if (!TextUtils.equals(oldCompanyValue, newCompanyValue)) {
-                putCompanyFieldValue(newCompanyValue);
-                hasChanged = true;
-            }
-
-            final String oldTitleValue = getTitleFieldValue();
-            final String newTitleValue = view.getTitleFieldValue().toString();
-            if (!TextUtils.equals(oldTitleValue, newTitleValue)) {
-                putTitleFieldValue(newTitleValue);
-                hasChanged = true;
-            }
-            if (hasChanged) {
-                Log.v(TAG, "Found changes. Updating DB");
-                saveData();
-            }
-        }
-    };
-}
diff --git a/src/com/android/contacts/views/editor/viewModel/PhotoViewModel.java b/src/com/android/contacts/views/editor/viewModel/PhotoViewModel.java
deleted file mode 100644
index 682545c..0000000
--- a/src/com/android/contacts/views/editor/viewModel/PhotoViewModel.java
+++ /dev/null
@@ -1,65 +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.editor.viewModel;
-
-import com.android.contacts.views.editor.DisplayRawContact;
-import com.android.contacts.views.editor.view.PhotoView;
-
-import android.content.ContentValues;
-import android.content.Context;
-import android.content.ContentProviderOperation.Builder;
-import android.graphics.Bitmap;
-import android.graphics.BitmapFactory;
-import android.provider.ContactsContract.CommonDataKinds.Im;
-import android.provider.ContactsContract.CommonDataKinds.Photo;
-import android.view.LayoutInflater;
-import android.view.View;
-import android.view.ViewGroup;
-
-/**
- * Editor for the contact photo.
- */
-public class PhotoViewModel extends DataViewModel {
-    private PhotoViewModel(Context context, DisplayRawContact rawContact, long dataId,
-            ContentValues contentValues) {
-        super(context, rawContact, dataId, contentValues, Im.CONTENT_ITEM_TYPE);
-    }
-
-    public static PhotoViewModel createForExisting(Context context, DisplayRawContact rawContact,
-            long dataId, ContentValues contentValues) {
-        return new PhotoViewModel(context, rawContact, dataId, contentValues);
-    }
-
-    @Override
-    protected void writeToBuilder(Builder builder, boolean isInsert) {
-        // TODO
-    }
-
-    @Override
-    public View createAndAddView(LayoutInflater inflater, ViewGroup parent) {
-        final PhotoView result = PhotoView.inflate(inflater, parent, false);
-
-        final byte[] binaryData = getContentValues().getAsByteArray(Photo.PHOTO);
-
-        final Bitmap bitmap = binaryData != null
-                ? BitmapFactory.decodeByteArray(binaryData, 0, binaryData.length)
-                : null;
-        result.setPhoto(bitmap);
-        parent.addView(result);
-        return result;
-    }
-}
diff --git a/src/com/android/contacts/views/editor/viewModel/StructuredNameViewModel.java b/src/com/android/contacts/views/editor/viewModel/StructuredNameViewModel.java
deleted file mode 100644
index 3351f4e..0000000
--- a/src/com/android/contacts/views/editor/viewModel/StructuredNameViewModel.java
+++ /dev/null
@@ -1,107 +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.editor.viewModel;
-
-import com.android.contacts.views.editor.DisplayRawContact;
-import com.android.contacts.views.editor.view.SimpleOrStructuredView;
-
-import android.content.ContentValues;
-import android.content.Context;
-import android.content.ContentProviderOperation.Builder;
-import android.provider.ContactsContract.CommonDataKinds.StructuredName;
-import android.text.TextUtils;
-import android.util.Log;
-import android.view.LayoutInflater;
-import android.view.View;
-import android.view.ViewGroup;
-
-/**
- * Editor for the StructuredName. Handles both the structured representation as well the
- * single field display
- */
-public class StructuredNameViewModel extends DataViewModel {
-    protected static final String TAG = "StructuredNameViewModel";
-    private final int mLabelResId;
-
-    private StructuredNameViewModel(Context context, DisplayRawContact rawContact, long dataId,
-            ContentValues contentValues, int labelResId) {
-        super(context, rawContact, dataId, contentValues, StructuredName.CONTENT_ITEM_TYPE);
-        mLabelResId = labelResId;
-    }
-
-    public static StructuredNameViewModel createForExisting(Context context, DisplayRawContact rawContact,
-            long dataId, ContentValues contentValues, int labelResId) {
-        return new StructuredNameViewModel(context, rawContact, dataId, contentValues, labelResId);
-    }
-
-    @Override
-    protected void writeToBuilder(Builder builder, boolean isInsert) {
-        // TODO: Handle both structured and unstructured inputs.
-        // if (structuredEntered()) {
-        // } else {
-        builder.withValue(StructuredName.DISPLAY_NAME, getDisplayName());
-        builder.withValue(StructuredName.GIVEN_NAME, null);
-        builder.withValue(StructuredName.FAMILY_NAME, null);
-        builder.withValue(StructuredName.PREFIX, null);
-        builder.withValue(StructuredName.MIDDLE_NAME, null);
-        builder.withValue(StructuredName.SUFFIX, null);
-        // }
-    }
-
-    @Override
-    public View createAndAddView(LayoutInflater inflater, ViewGroup parent) {
-        final SimpleOrStructuredView result =
-                SimpleOrStructuredView.inflate(inflater, parent, false);
-
-        result.setListener(mViewListener);
-        result.setLabelText(mLabelResId);
-        result.setDisplayName(getDisplayName());
-
-        parent.addView(result);
-        return result;
-    }
-
-    private String getDisplayName() {
-        return getContentValues().getAsString(StructuredName.DISPLAY_NAME);
-    }
-
-    private void putDisplayName(String value) {
-        getContentValues().put(StructuredName.DISPLAY_NAME, value);
-    }
-
-    private SimpleOrStructuredView.Listener mViewListener = new SimpleOrStructuredView.Listener() {
-        public void onFocusLost(SimpleOrStructuredView view) {
-            Log.v(TAG, "Received FocusLost. Checking for changes");
-            boolean hasChanged = false;
-
-            final String oldValue = getDisplayName();
-            final String newValue = view.getDisplayName().toString();
-            if (!TextUtils.equals(oldValue, newValue)) {
-                putDisplayName(newValue);
-                hasChanged = true;
-            }
-            if (hasChanged) {
-                Log.v(TAG, "Found changes. Updating DB");
-                saveData();
-            }
-        }
-
-        public void onStructuredEditorRequested(SimpleOrStructuredView view) {
-            // TODO
-        }
-    };
-}
diff --git a/src/com/android/contacts/views/editor/viewModel/StructuredPostalViewModel.java b/src/com/android/contacts/views/editor/viewModel/StructuredPostalViewModel.java
deleted file mode 100644
index f1a1559..0000000
--- a/src/com/android/contacts/views/editor/viewModel/StructuredPostalViewModel.java
+++ /dev/null
@@ -1,116 +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.editor.viewModel;
-
-import com.android.contacts.views.editor.DisplayRawContact;
-import com.android.contacts.views.editor.view.SimpleOrStructuredView;
-
-import android.content.ContentValues;
-import android.content.Context;
-import android.content.ContentProviderOperation.Builder;
-import android.provider.ContactsContract.CommonDataKinds.StructuredPostal;
-import android.text.TextUtils;
-import android.util.Log;
-import android.view.LayoutInflater;
-import android.view.View;
-import android.view.ViewGroup;
-
-/**
- * Editor for the StructuredPostal. Handles both the structured representation as well the
- * single field display
- */
-public class StructuredPostalViewModel extends DataViewModel {
-    protected static final String TAG = "StructuredPostalViewModel";
-    private final int mLabelResId;
-
-    private StructuredPostalViewModel(Context context, DisplayRawContact rawContact, long dataId,
-            ContentValues contentValues, int labelResId) {
-        super(context, rawContact, dataId, contentValues, StructuredPostal.CONTENT_ITEM_TYPE);
-        mLabelResId = labelResId;
-    }
-
-    public static StructuredPostalViewModel createForExisting(Context context,
-            DisplayRawContact rawContact, long dataId, ContentValues contentValues,
-            int labelResId) {
-        return new StructuredPostalViewModel(context, rawContact, dataId, contentValues,
-                labelResId);
-    }
-
-    @Override
-    protected void writeToBuilder(Builder builder, boolean isInsert) {
-        // TODO: Writing the non-structured field works pretty badly as we
-        // currently can't parse it properly. This is also a problem when an address it taken
-        // from Maps
-
-        // TODO: Handle both structured and unstructured inputs.
-
-        // if (structuredEntered()) {
-        // } else {
-        builder.withValue(StructuredPostal.FORMATTED_ADDRESS, getFormattedAddress());
-        builder.withValue(StructuredPostal.STREET, null);
-        builder.withValue(StructuredPostal.POBOX, null);
-        builder.withValue(StructuredPostal.NEIGHBORHOOD, null);
-        builder.withValue(StructuredPostal.CITY, null);
-        builder.withValue(StructuredPostal.REGION, null);
-        builder.withValue(StructuredPostal.POSTCODE, null);
-        builder.withValue(StructuredPostal.COUNTRY, null);
-        // }
-    }
-
-    @Override
-    public View createAndAddView(LayoutInflater inflater, ViewGroup parent) {
-        final SimpleOrStructuredView result =
-                SimpleOrStructuredView.inflate(inflater, parent, false);
-
-        result.setListener(mViewListener);
-        result.setLabelText(mLabelResId);
-        result.setDisplayName(getFormattedAddress());
-
-        parent.addView(result);
-        return result;
-    }
-
-    private String getFormattedAddress() {
-        return getContentValues().getAsString(StructuredPostal.FORMATTED_ADDRESS);
-    }
-
-    private void putDisplayName(String value) {
-        getContentValues().put(StructuredPostal.FORMATTED_ADDRESS, value);
-    }
-
-    private SimpleOrStructuredView.Listener mViewListener = new SimpleOrStructuredView.Listener() {
-        public void onFocusLost(SimpleOrStructuredView view) {
-            Log.v(TAG, "Received FocusLost. Checking for changes");
-            boolean hasChanged = false;
-
-            final String oldValue = getFormattedAddress();
-            final String newValue = view.getDisplayName().toString();
-            if (!TextUtils.equals(oldValue, newValue)) {
-                putDisplayName(newValue);
-                hasChanged = true;
-            }
-            if (hasChanged) {
-                Log.v(TAG, "Found changes. Updating DB");
-                saveData();
-            }
-        }
-
-        public void onStructuredEditorRequested(SimpleOrStructuredView view) {
-            // TODO
-        }
-    };
-}