Merge base and compact editor fragments

Base editor constants, inner classes, members,
and methods were moved to the compact fragment
unchanged except:

* isAggregationSuggestionRawContactId was unused so just
  removed it.

* maybeConvertToLegacyLookupUri was moved to
  ContactEditorUtils

Test: Manually tested the following editor scenarios:
  1) new contact
  2) edit other contact
  3) edit writable raw contact
  4) edit read-only raw contact (joins a new writable raw contact to it)
  5) edit aggregate w/ 1 writable and 1 read-only raw contact
  6) edit aggregate w/ 2 writable raw contacts
  7) edit local me raw contact
  8) edit local me raw contact joined with a read-only raw contact

Bug: 31088704
Change-Id: If3652dffef6a428aa0534ffbc639ae66601d6ab4
diff --git a/src/com/android/contacts/ContactsDrawerActivity.java b/src/com/android/contacts/ContactsDrawerActivity.java
index faae794..cb817b2 100644
--- a/src/com/android/contacts/ContactsDrawerActivity.java
+++ b/src/com/android/contacts/ContactsDrawerActivity.java
@@ -57,7 +57,7 @@
 import com.android.contacts.common.util.DeviceAccountPresentationValues;
 import com.android.contacts.common.util.ImplicitIntentsUtil;
 import com.android.contacts.common.util.ViewUtil;
-import com.android.contacts.editor.ContactEditorBaseFragment;
+import com.android.contacts.editor.CompactContactEditorFragment;
 import com.android.contacts.editor.SelectAccountDialogFragment;
 import com.android.contacts.group.GroupListItem;
 import com.android.contacts.group.GroupMembersFragment;
@@ -575,7 +575,7 @@
     private Intent createPreferenceIntent() {
         final Intent intent = new Intent(this, ContactsPreferenceActivity.class);
         intent.putExtra(ContactsPreferenceActivity.EXTRA_NEW_LOCAL_PROFILE,
-                ContactEditorBaseFragment.INTENT_EXTRA_NEW_LOCAL_PROFILE);
+                CompactContactEditorFragment.INTENT_EXTRA_NEW_LOCAL_PROFILE);
         intent.putExtra(ContactsPreferenceActivity.EXTRA_MODE_FULLY_EXPANDED,
                 QuickContactActivity.MODE_FULLY_EXPANDED);
         intent.putExtra(ContactsPreferenceActivity.EXTRA_PREVIOUS_SCREEN_TYPE,
diff --git a/src/com/android/contacts/activities/CompactContactEditorActivity.java b/src/com/android/contacts/activities/CompactContactEditorActivity.java
index e741cb5..f8c9c5c 100644
--- a/src/com/android/contacts/activities/CompactContactEditorActivity.java
+++ b/src/com/android/contacts/activities/CompactContactEditorActivity.java
@@ -28,7 +28,6 @@
 import com.android.contacts.detail.PhotoSelectionHandler;
 import com.android.contacts.editor.CompactContactEditorFragment;
 import com.android.contacts.editor.CompactPhotoSelectionFragment;
-import com.android.contacts.editor.ContactEditorBaseFragment;
 import com.android.contacts.editor.EditorIntents;
 import com.android.contacts.editor.PhotoSourceDialogFragment;
 import com.android.contacts.interactions.ContactDeletionInteraction;
@@ -154,7 +153,7 @@
         /**
          * Sets the hosting Activity that will receive callbacks from the contact editor.
          */
-        void setListener(ContactEditorBaseFragment.Listener listener);
+        void setListener(CompactContactEditorFragment.Listener listener);
 
         /**
          * Initialize the contact editor.
@@ -273,8 +272,8 @@
     private int mPhotoMode;
     private boolean mIsPhotoSelection;
 
-    private final ContactEditorBaseFragment.Listener  mFragmentListener =
-            new ContactEditorBaseFragment.Listener() {
+    private final CompactContactEditorFragment.Listener  mFragmentListener =
+            new CompactContactEditorFragment.Listener() {
 
                 @Override
                 public void onDeleteRequested(Uri contactUri) {
@@ -481,11 +480,11 @@
             mFragment.setIntentExtras(intent.getExtras());
         } else if (ACTION_SAVE_COMPLETED.equals(action)) {
             mFragment.onSaveCompleted(true,
-                    intent.getIntExtra(ContactEditorBaseFragment.SAVE_MODE_EXTRA_KEY,
+                    intent.getIntExtra(CompactContactEditorFragment.SAVE_MODE_EXTRA_KEY,
                             ContactEditor.SaveMode.CLOSE),
                     intent.getBooleanExtra(ContactSaveService.EXTRA_SAVE_SUCCEEDED, false),
                     intent.getData(),
-                    intent.getLongExtra(ContactEditorBaseFragment.JOIN_CONTACT_ID_EXTRA_KEY, -1));
+                    intent.getLongExtra(CompactContactEditorFragment.JOIN_CONTACT_ID_EXTRA_KEY, -1));
         } else if (ACTION_JOIN_COMPLETED.equals(action)) {
             mFragment.onJoinCompleted(intent.getData());
         }
diff --git a/src/com/android/contacts/common/model/RawContactModifier.java b/src/com/android/contacts/common/model/RawContactModifier.java
index 4d62bb3..79254e4 100644
--- a/src/com/android/contacts/common/model/RawContactModifier.java
+++ b/src/com/android/contacts/common/model/RawContactModifier.java
@@ -694,7 +694,7 @@
                 final Integer type = values.getAsInteger(Phone.TYPE);
                 // If the provided phone number provides a custom phone type but not a label,
                 // replace it with mobile (by default) to avoid the "Enter custom label" from
-                // popping up immediately upon entering the ContactEditorBaseFragment
+                // popping up immediately upon entering the CompactContactEditorFragment
                 if (type != null && type == Phone.TYPE_CUSTOM &&
                         TextUtils.isEmpty(values.getAsString(Phone.LABEL))) {
                     values.put(Phone.TYPE, Phone.TYPE_MOBILE);
diff --git a/src/com/android/contacts/editor/CancelEditDialogFragment.java b/src/com/android/contacts/editor/CancelEditDialogFragment.java
index ba5f9fa..ec937a6 100644
--- a/src/com/android/contacts/editor/CancelEditDialogFragment.java
+++ b/src/com/android/contacts/editor/CancelEditDialogFragment.java
@@ -35,7 +35,7 @@
      * Shows a {@link CancelEditDialogFragment} after setting the given Fragment as the
      * target of the dialog.
      */
-    public static void show(ContactEditorBaseFragment fragment) {
+    public static void show(CompactContactEditorFragment fragment) {
         final CancelEditDialogFragment dialog = new CancelEditDialogFragment();
         dialog.setTargetFragment(fragment, 0);
         dialog.show(fragment.getFragmentManager(), TAG);
@@ -69,4 +69,4 @@
          */
         void onCancelEditConfirmed();
     }
-}
\ No newline at end of file
+}
diff --git a/src/com/android/contacts/editor/CompactContactEditorFragment.java b/src/com/android/contacts/editor/CompactContactEditorFragment.java
index 4ce0d98..c07ebe2 100644
--- a/src/com/android/contacts/editor/CompactContactEditorFragment.java
+++ b/src/com/android/contacts/editor/CompactContactEditorFragment.java
@@ -16,48 +16,539 @@
 
 package com.android.contacts.editor;
 
-import com.android.contacts.ContactSaveService;
-import com.android.contacts.R;
-import com.android.contacts.activities.CompactContactEditorActivity;
-import com.android.contacts.common.model.RawContactDelta;
-import com.android.contacts.common.model.ValuesDelta;
-import com.android.contacts.common.model.account.AccountWithDataSet;
-import com.android.contacts.util.ContactPhotoUtils;
-
+import android.accounts.Account;
 import android.app.Activity;
+import android.app.Fragment;
+import android.app.LoaderManager;
+import android.content.ActivityNotFoundException;
+import android.content.ContentUris;
+import android.content.ContentValues;
+import android.content.Context;
+import android.content.CursorLoader;
 import android.content.Intent;
+import android.content.Loader;
+import android.database.Cursor;
 import android.graphics.Bitmap;
+import android.media.RingtoneManager;
 import android.net.Uri;
 import android.os.Bundle;
+import android.os.SystemClock;
+import android.provider.ContactsContract;
+import android.provider.ContactsContract.CommonDataKinds.Email;
+import android.provider.ContactsContract.CommonDataKinds.Event;
+import android.provider.ContactsContract.CommonDataKinds.Organization;
+import android.provider.ContactsContract.CommonDataKinds.Phone;
+import android.provider.ContactsContract.CommonDataKinds.StructuredName;
+import android.provider.ContactsContract.CommonDataKinds.StructuredPostal;
+import android.provider.ContactsContract.Intents;
+import android.provider.ContactsContract.RawContacts;
 import android.text.TextUtils;
 import android.util.Log;
 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.AdapterView;
+import android.widget.BaseAdapter;
 import android.widget.LinearLayout;
+import android.widget.ListPopupWindow;
 import android.widget.Toast;
 
+import com.android.contacts.ContactSaveService;
+import com.android.contacts.GroupMetaDataLoader;
+import com.android.contacts.R;
+import com.android.contacts.activities.CompactContactEditorActivity;
+import com.android.contacts.activities.CompactContactEditorActivity.ContactEditor;
+import com.android.contacts.activities.ContactEditorAccountsChangedActivity;
+import com.android.contacts.activities.ContactSelectionActivity;
+import com.android.contacts.common.logging.ScreenEvent.ScreenType;
+import com.android.contacts.common.model.AccountTypeManager;
+import com.android.contacts.common.model.Contact;
+import com.android.contacts.common.model.ContactLoader;
+import com.android.contacts.common.model.RawContact;
+import com.android.contacts.common.model.RawContactDelta;
+import com.android.contacts.common.model.RawContactDeltaList;
+import com.android.contacts.common.model.RawContactModifier;
+import com.android.contacts.common.model.ValuesDelta;
+import com.android.contacts.common.model.account.AccountType;
+import com.android.contacts.common.model.account.AccountWithDataSet;
+import com.android.contacts.common.util.ImplicitIntentsUtil;
+import com.android.contacts.common.util.MaterialColorMapUtils;
+import com.android.contacts.editor.AggregationSuggestionEngine.Suggestion;
+import com.android.contacts.list.UiIntentActions;
+import com.android.contacts.quickcontact.QuickContactActivity;
+import com.android.contacts.util.ContactPhotoUtils;
+import com.android.contacts.util.HelpUtils;
+import com.android.contacts.util.PhoneCapabilityTester;
+import com.android.contacts.util.UiClosables;
+
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.Lists;
+
 import java.io.FileNotFoundException;
 import java.util.ArrayList;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Set;
 
 /**
  * Contact editor with only the most important fields displayed initially.
  */
-public class CompactContactEditorFragment extends ContactEditorBaseFragment implements
+public class CompactContactEditorFragment extends Fragment implements
+        ContactEditor, SplitContactConfirmationDialogFragment.Listener,
+        JoinContactConfirmationDialogFragment.Listener,
+        AggregationSuggestionEngine.Listener, AggregationSuggestionView.Listener,
+        CancelEditDialogFragment.Listener,
         CompactRawContactsEditorView.Listener, CompactPhotoEditorView.Listener {
 
+    static final String TAG = "ContactEditor";
+
+    private static final int LOADER_CONTACT = 1;
+    private static final int LOADER_GROUPS = 2;
+
     private static final String KEY_PHOTO_RAW_CONTACT_ID = "photo_raw_contact_id";
     private static final String KEY_UPDATED_PHOTOS = "updated_photos";
 
+    private static final List<String> VALID_INTENT_ACTIONS = new ArrayList<String>() {{
+        add(Intent.ACTION_EDIT);
+        add(Intent.ACTION_INSERT);
+        add(CompactContactEditorActivity.ACTION_SAVE_COMPLETED);
+    }};
+
+    private static final String KEY_ACTION = "action";
+    private static final String KEY_URI = "uri";
+    private static final String KEY_AUTO_ADD_TO_DEFAULT_GROUP = "autoAddToDefaultGroup";
+    private static final String KEY_DISABLE_DELETE_MENU_OPTION = "disableDeleteMenuOption";
+    private static final String KEY_NEW_LOCAL_PROFILE = "newLocalProfile";
+    private static final String KEY_MATERIAL_PALETTE = "materialPalette";
+    private static final String KEY_PHOTO_ID = "photoId";
+
+    private static final String KEY_VIEW_ID_GENERATOR = "viewidgenerator";
+
+    private static final String KEY_RAW_CONTACTS = "rawContacts";
+
+    private static final String KEY_EDIT_STATE = "state";
+    private static final String KEY_STATUS = "status";
+
+    private static final String KEY_HAS_NEW_CONTACT = "hasNewContact";
+    private static final String KEY_NEW_CONTACT_READY = "newContactDataReady";
+
+    private static final String KEY_IS_EDIT = "isEdit";
+    private static final String KEY_EXISTING_CONTACT_READY = "existingContactDataReady";
+
+    private static final String KEY_RAW_CONTACT_DISPLAY_ALONE_IS_READ_ONLY = "isReadOnly";
+
+    // Phone option menus
+    private static final String KEY_SEND_TO_VOICE_MAIL_STATE = "sendToVoicemailState";
+    private static final String KEY_ARE_PHONE_OPTIONS_CHANGEABLE = "arePhoneOptionsChangable";
+    private static final String KEY_CUSTOM_RINGTONE = "customRingtone";
+
+    private static final String KEY_IS_USER_PROFILE = "isUserProfile";
+
+    private static final String KEY_ENABLED = "enabled";
+
+    // Aggregation PopupWindow
+    private static final String KEY_AGGREGATION_SUGGESTIONS_RAW_CONTACT_ID =
+            "aggregationSuggestionsRawContactId";
+
+    // Join Activity
+    private static final String KEY_CONTACT_ID_FOR_JOIN = "contactidforjoin";
+
+    private static final String KEY_READ_ONLY_DISPLAY_NAME = "readOnlyDisplayName";
+
+    protected static final int REQUEST_CODE_JOIN = 0;
+    protected static final int REQUEST_CODE_ACCOUNTS_CHANGED = 1;
+    protected static final int REQUEST_CODE_PICK_RINGTONE = 2;
+
+    private static final int CURRENT_API_VERSION = android.os.Build.VERSION.SDK_INT;
+
+    /**
+     * An intent extra that forces the editor to add the edited contact
+     * to the default group (e.g. "My Contacts").
+     */
+    public static final String INTENT_EXTRA_ADD_TO_DEFAULT_DIRECTORY = "addToDefaultDirectory";
+
+    public static final String INTENT_EXTRA_NEW_LOCAL_PROFILE = "newLocalProfile";
+
+    public static final String INTENT_EXTRA_DISABLE_DELETE_MENU_OPTION =
+            "disableDeleteMenuOption";
+
+    /**
+     * Intent key to pass the photo palette primary color calculated by
+     * {@link com.android.contacts.quickcontact.QuickContactActivity} to the editor and between
+     * the compact and fully expanded editors.
+     */
+    public static final String INTENT_EXTRA_MATERIAL_PALETTE_PRIMARY_COLOR =
+            "material_palette_primary_color";
+
+    /**
+     * Intent key to pass the photo palette secondary color calculated by
+     * {@link com.android.contacts.quickcontact.QuickContactActivity} to the editor and between
+     * the compact and fully expanded editors.
+     */
+    public static final String INTENT_EXTRA_MATERIAL_PALETTE_SECONDARY_COLOR =
+            "material_palette_secondary_color";
+
+    /**
+     * Intent key to pass the ID of the photo to display on the editor.
+     */
+    public static final String INTENT_EXTRA_PHOTO_ID = "photo_id";
+
+    /**
+     * Intent key to pass the ID of the raw contact id that should be displayed in the full editor
+     * by itself.
+     */
+    public static final String INTENT_EXTRA_RAW_CONTACT_ID_TO_DISPLAY_ALONE =
+            "raw_contact_id_to_display_alone";
+
+    /**
+     * Intent key to pass the boolean value of if the raw contact id that should be displayed
+     * in the full editor by itself is read-only.
+     */
+    public static final String INTENT_EXTRA_RAW_CONTACT_DISPLAY_ALONE_IS_READ_ONLY =
+            "raw_contact_display_alone_is_read_only";
+
+    /**
+     * Intent extra to specify a {@link ContactEditor.SaveMode}.
+     */
+    public static final String SAVE_MODE_EXTRA_KEY = "saveMode";
+
+    /**
+     * Intent extra key for the contact ID to join the current contact to after saving.
+     */
+    public static final String JOIN_CONTACT_ID_EXTRA_KEY = "joinContactId";
+
+    /**
+     * Callbacks for Activities that host contact editors Fragments.
+     */
+    public interface Listener {
+
+        /**
+         * Contact was not found, so somehow close this fragment. This is raised after a contact
+         * is removed via Menu/Delete
+         */
+        void onContactNotFound();
+
+        /**
+         * Contact was split, so we can close now.
+         *
+         * @param newLookupUri The lookup uri of the new contact that should be shown to the user.
+         *                     The editor tries best to chose the most natural contact here.
+         */
+        void onContactSplit(Uri newLookupUri);
+
+        /**
+         * User has tapped Revert, close the fragment now.
+         */
+        void onReverted();
+
+        /**
+         * Contact was saved and the Fragment can now be closed safely.
+         */
+        void onSaveFinished(Intent resultIntent);
+
+        /**
+         * User switched to editing a different contact (a suggestion from the
+         * aggregation engine).
+         */
+        void onEditOtherContactRequested(Uri contactLookupUri,
+                ArrayList<ContentValues> contentValues);
+
+        /**
+         * Contact is being created for an external account that provides its own
+         * new contact activity.
+         */
+        void onCustomCreateContactActivityRequested(AccountWithDataSet account,
+                Bundle intentExtras);
+
+        /**
+         * The edited raw contact belongs to an external account that provides
+         * its own edit activity.
+         *
+         * @param redirect indicates that the current editor should be closed
+         *                 before the custom editor is shown.
+         */
+        void onCustomEditContactActivityRequested(AccountWithDataSet account, Uri rawContactUri,
+                Bundle intentExtras, boolean redirect);
+
+        /**
+         * User has requested that contact be deleted.
+         */
+        void onDeleteRequested(Uri contactUri);
+    }
+
+    /**
+     * Adapter for aggregation suggestions displayed in a PopupWindow when
+     * editor fields change.
+     */
+    private static final class AggregationSuggestionAdapter extends BaseAdapter {
+        private final LayoutInflater mLayoutInflater;
+        private final boolean mSetNewContact;
+        private final AggregationSuggestionView.Listener mListener;
+        private final List<AggregationSuggestionEngine.Suggestion> mSuggestions;
+
+        public AggregationSuggestionAdapter(Activity activity, boolean setNewContact,
+                AggregationSuggestionView.Listener listener, List<Suggestion> suggestions) {
+            mLayoutInflater = activity.getLayoutInflater();
+            mSetNewContact = setNewContact;
+            mListener = listener;
+            mSuggestions = suggestions;
+        }
+
+        @Override
+        public View getView(int position, View convertView, ViewGroup parent) {
+            final Suggestion suggestion = (Suggestion) getItem(position);
+            final AggregationSuggestionView suggestionView =
+                    (AggregationSuggestionView) mLayoutInflater.inflate(
+                            R.layout.aggregation_suggestions_item, null);
+            suggestionView.setNewContact(mSetNewContact);
+            suggestionView.setListener(mListener);
+            suggestionView.bindSuggestion(suggestion);
+            return suggestionView;
+        }
+
+        @Override
+        public long getItemId(int position) {
+            return position;
+        }
+
+        @Override
+        public Object getItem(int position) {
+            return mSuggestions.get(position);
+        }
+
+        @Override
+        public int getCount() {
+            return mSuggestions.size();
+        }
+    }
+
+    protected Context mContext;
+    protected Listener mListener;
+
+    //
+    // Views
+    //
+    protected LinearLayout mContent;
+    protected View mAggregationSuggestionView;
+    protected ListPopupWindow mAggregationSuggestionPopup;
+
+    //
+    // Parameters passed in on {@link #load}
+    //
+    protected String mAction;
+    protected Uri mLookupUri;
+    protected Bundle mIntentExtras;
+    protected boolean mAutoAddToDefaultGroup;
+    protected boolean mDisableDeleteMenuOption;
+    protected boolean mNewLocalProfile;
+    protected MaterialColorMapUtils.MaterialPalette mMaterialPalette;
+    protected long mPhotoId = -1;
+
+    //
+    // Helpers
+    //
+    protected ContactEditorUtils mEditorUtils;
+    protected RawContactDeltaComparator mComparator;
+    protected ViewIdGenerator mViewIdGenerator;
+    private AggregationSuggestionEngine mAggregationSuggestionEngine;
+
+    //
+    // Loaded data
+    //
+    // Used to store existing contact data so it can be re-applied during a rebind call,
+    // i.e. account switch.
+    protected ImmutableList<RawContact> mRawContacts;
+    protected Cursor mGroupMetaData;
+
+    //
+    // Editor state
+    //
+    protected RawContactDeltaList mState;
+    protected int mStatus;
+    protected long mRawContactIdToDisplayAlone = -1;
+    protected boolean mRawContactDisplayAloneIsReadOnly = false;
+
+    // Whether to show the new contact blank form and if it's corresponding delta is ready.
+    protected boolean mHasNewContact;
+    protected AccountWithDataSet mAccountWithDataSet;
+    protected boolean mNewContactDataReady;
+    protected boolean mNewContactAccountChanged;
+
+    // Whether it's an edit of existing contact and if it's corresponding delta is ready.
+    protected boolean mIsEdit;
+    protected boolean mExistingContactDataReady;
+
+    // Whether we are editing the "me" profile
+    protected boolean mIsUserProfile;
+
+    // Phone specific option menu items
+    private boolean mSendToVoicemailState;
+    private boolean mArePhoneOptionsChangable;
+    private String mCustomRingtone;
+
+    // Whether editor views and options menu items should be enabled
+    private boolean mEnabled = true;
+
+    // Aggregation PopupWindow
+    private long mAggregationSuggestionsRawContactId;
+
+    // Join Activity
+    protected long mContactIdForJoin;
+
+    // Used to pre-populate the editor with a display name when a user edits a read-only contact.
+    protected String mReadOnlyDisplayName;
+
+    //
+    // Not saved/restored on rotates
+    //
+
+    // The name editor view for the new raw contact that was created so that the user can
+    // edit a read-only contact (to which the new raw contact was joined)
+    protected StructuredNameEditorView mReadOnlyNameEditorView;
+
+    /**
+     * The contact data loader listener.
+     */
+    protected final LoaderManager.LoaderCallbacks<Contact> mContactLoaderListener =
+            new LoaderManager.LoaderCallbacks<Contact>() {
+
+                protected long mLoaderStartTime;
+
+                @Override
+                public Loader<Contact> onCreateLoader(int id, Bundle args) {
+                    mLoaderStartTime = SystemClock.elapsedRealtime();
+                    return new ContactLoader(mContext, mLookupUri, true);
+                }
+
+                @Override
+                public void onLoadFinished(Loader<Contact> loader, Contact contact) {
+                    final long loaderCurrentTime = SystemClock.elapsedRealtime();
+                    Log.v(TAG, "Time needed for loading: " + (loaderCurrentTime-mLoaderStartTime));
+                    if (!contact.isLoaded()) {
+                        // Item has been deleted. Close activity without saving again.
+                        Log.i(TAG, "No contact found. Closing activity");
+                        mStatus = Status.CLOSING;
+                        if (mListener != null) mListener.onContactNotFound();
+                        return;
+                    }
+
+                    mStatus = Status.EDITING;
+                    mLookupUri = contact.getLookupUri();
+                    final long setDataStartTime = SystemClock.elapsedRealtime();
+                    setState(contact);
+                    setStateForPhoneMenuItems(contact);
+                    final long setDataEndTime = SystemClock.elapsedRealtime();
+
+                    Log.v(TAG, "Time needed for setting UI: " + (setDataEndTime - setDataStartTime));
+                }
+
+                @Override
+                public void onLoaderReset(Loader<Contact> loader) {
+                }
+            };
+
+    /**
+     * The groups meta data loader listener.
+     */
+    protected final LoaderManager.LoaderCallbacks<Cursor> mGroupsLoaderListener =
+            new LoaderManager.LoaderCallbacks<Cursor>() {
+
+                @Override
+                public CursorLoader onCreateLoader(int id, Bundle args) {
+                    return new GroupMetaDataLoader(mContext, ContactsContract.Groups.CONTENT_URI);
+                }
+
+                @Override
+                public void onLoadFinished(Loader<Cursor> loader, Cursor data) {
+                    mGroupMetaData = data;
+                    setGroupMetaData();
+                }
+
+                @Override
+                public void onLoaderReset(Loader<Cursor> loader) {
+                }
+            };
+
     private long mPhotoRawContactId;
     private Bundle mUpdatedPhotos = new Bundle();
 
     @Override
+    public Context getContext() {
+        return getActivity();
+    }
+
+    @Override
+    public void onAttach(Activity activity) {
+        super.onAttach(activity);
+        mContext = activity;
+        mEditorUtils = ContactEditorUtils.getInstance(mContext);
+        mComparator = new RawContactDeltaComparator(mContext);
+    }
+
+    @Override
     public void onCreate(Bundle savedState) {
+        if (savedState != null) {
+            // Restore mUri before calling super.onCreate so that onInitializeLoaders
+            // would already have a uri and an action to work with
+            mAction = savedState.getString(KEY_ACTION);
+            mLookupUri = savedState.getParcelable(KEY_URI);
+        }
+
         super.onCreate(savedState);
 
-        if (savedState != null) {
+        if (savedState == null) {
+            mViewIdGenerator = new ViewIdGenerator();
+
+            // mState can still be null because it may not have have finished loading before
+            // onSaveInstanceState was called.
+            mState = new RawContactDeltaList();
+        } else {
+            mViewIdGenerator = savedState.getParcelable(KEY_VIEW_ID_GENERATOR);
+
+            mAutoAddToDefaultGroup = savedState.getBoolean(KEY_AUTO_ADD_TO_DEFAULT_GROUP);
+            mDisableDeleteMenuOption = savedState.getBoolean(KEY_DISABLE_DELETE_MENU_OPTION);
+            mNewLocalProfile = savedState.getBoolean(KEY_NEW_LOCAL_PROFILE);
+            mMaterialPalette = savedState.getParcelable(KEY_MATERIAL_PALETTE);
+            mPhotoId = savedState.getLong(KEY_PHOTO_ID);
+
+            mRawContacts = ImmutableList.copyOf(savedState.<RawContact>getParcelableArrayList(
+                    KEY_RAW_CONTACTS));
+            // NOTE: mGroupMetaData is not saved/restored
+
+            // Read state from savedState. No loading involved here
+            mState = savedState.<RawContactDeltaList> getParcelable(KEY_EDIT_STATE);
+            mStatus = savedState.getInt(KEY_STATUS);
+            mRawContactDisplayAloneIsReadOnly = savedState.getBoolean(
+                    KEY_RAW_CONTACT_DISPLAY_ALONE_IS_READ_ONLY);
+
+            mHasNewContact = savedState.getBoolean(KEY_HAS_NEW_CONTACT);
+            mNewContactDataReady = savedState.getBoolean(KEY_NEW_CONTACT_READY);
+
+            mIsEdit = savedState.getBoolean(KEY_IS_EDIT);
+            mExistingContactDataReady = savedState.getBoolean(KEY_EXISTING_CONTACT_READY);
+
+            mIsUserProfile = savedState.getBoolean(KEY_IS_USER_PROFILE);
+
+            // Phone specific options menus
+            mSendToVoicemailState = savedState.getBoolean(KEY_SEND_TO_VOICE_MAIL_STATE);
+            mArePhoneOptionsChangable = savedState.getBoolean(KEY_ARE_PHONE_OPTIONS_CHANGEABLE);
+            mCustomRingtone = savedState.getString(KEY_CUSTOM_RINGTONE);
+
+            mEnabled = savedState.getBoolean(KEY_ENABLED);
+
+            // Aggregation PopupWindow
+            mAggregationSuggestionsRawContactId = savedState.getLong(
+                    KEY_AGGREGATION_SUGGESTIONS_RAW_CONTACT_ID);
+
+            // Join Activity
+            mContactIdForJoin = savedState.getLong(KEY_CONTACT_ID_FOR_JOIN);
+
+            mReadOnlyDisplayName = savedState.getString(KEY_READ_ONLY_DISPLAY_NAME);
+
             mPhotoRawContactId = savedState.getLong(KEY_PHOTO_RAW_CONTACT_ID);
             mUpdatedPhotos = savedState.getParcelable(KEY_UPDATED_PHOTOS);
         }
@@ -74,21 +565,809 @@
     }
 
     @Override
+    public void onActivityCreated(Bundle savedInstanceState) {
+        super.onActivityCreated(savedInstanceState);
+
+        validateAction(mAction);
+
+        if (mState.isEmpty()) {
+            // The delta list may not have finished loading before orientation change happens.
+            // In this case, there will be a saved state but deltas will be missing.  Reload from
+            // database.
+            if (Intent.ACTION_EDIT.equals(mAction)) {
+                // Either
+                // 1) orientation change but load never finished.
+                // 2) not an orientation change so data needs to be loaded for first time.
+                getLoaderManager().initLoader(LOADER_CONTACT, null, mContactLoaderListener);
+                getLoaderManager().initLoader(LOADER_GROUPS, null, mGroupsLoaderListener);
+            }
+        } else {
+            // Orientation change, we already have mState, it was loaded by onCreate
+            bindEditors();
+        }
+
+        // Handle initial actions only when existing state missing
+        if (savedInstanceState == null) {
+            final Account account = mIntentExtras == null ? null :
+                    (Account) mIntentExtras.getParcelable(Intents.Insert.EXTRA_ACCOUNT);
+            final String dataSet = mIntentExtras == null ? null :
+                    mIntentExtras.getString(Intents.Insert.EXTRA_DATA_SET);
+            if (account != null) {
+                mAccountWithDataSet = new AccountWithDataSet(account.name, account.type, dataSet);
+            }
+
+            if (Intent.ACTION_EDIT.equals(mAction)) {
+                mIsEdit = true;
+            } else if (Intent.ACTION_INSERT.equals(mAction)) {
+                mHasNewContact = true;
+                if (mAccountWithDataSet != null) {
+                    createContact(mAccountWithDataSet);
+                } else {
+                    // No Account specified. Let the user choose
+                    // Load Accounts async so that we can present them
+                    selectAccountAndCreateContact();
+                }
+            }
+        }
+    }
+
+    /**
+     * Checks if the requested action is valid.
+     *
+     * @param action The action to test.
+     * @throws IllegalArgumentException when the action is invalid.
+     */
+    private static void validateAction(String action) {
+        if (VALID_INTENT_ACTIONS.contains(action)) {
+            return;
+        }
+        throw new IllegalArgumentException(
+                "Unknown action " + action + "; Supported actions: " + VALID_INTENT_ACTIONS);
+    }
+
+    @Override
     public void onSaveInstanceState(Bundle outState) {
+        outState.putString(KEY_ACTION, mAction);
+        outState.putParcelable(KEY_URI, mLookupUri);
+        outState.putBoolean(KEY_AUTO_ADD_TO_DEFAULT_GROUP, mAutoAddToDefaultGroup);
+        outState.putBoolean(KEY_DISABLE_DELETE_MENU_OPTION, mDisableDeleteMenuOption);
+        outState.putBoolean(KEY_NEW_LOCAL_PROFILE, mNewLocalProfile);
+        if (mMaterialPalette != null) {
+            outState.putParcelable(KEY_MATERIAL_PALETTE, mMaterialPalette);
+        }
+        outState.putLong(KEY_PHOTO_ID, mPhotoId);
+
+        outState.putParcelable(KEY_VIEW_ID_GENERATOR, mViewIdGenerator);
+
+        outState.putParcelableArrayList(KEY_RAW_CONTACTS, mRawContacts == null ?
+                Lists.<RawContact>newArrayList() : Lists.newArrayList(mRawContacts));
+        // NOTE: mGroupMetaData is not saved
+
+        if (hasValidState()) {
+            // Store entities with modifications
+            outState.putParcelable(KEY_EDIT_STATE, mState);
+        }
+        outState.putInt(KEY_STATUS, mStatus);
+        outState.putBoolean(KEY_HAS_NEW_CONTACT, mHasNewContact);
+        outState.putBoolean(KEY_NEW_CONTACT_READY, mNewContactDataReady);
+        outState.putBoolean(KEY_IS_EDIT, mIsEdit);
+        outState.putBoolean(KEY_EXISTING_CONTACT_READY, mExistingContactDataReady);
+        outState.putBoolean(KEY_RAW_CONTACT_DISPLAY_ALONE_IS_READ_ONLY,
+                mRawContactDisplayAloneIsReadOnly);
+
+        outState.putBoolean(KEY_IS_USER_PROFILE, mIsUserProfile);
+
+        // Phone specific options
+        outState.putBoolean(KEY_SEND_TO_VOICE_MAIL_STATE, mSendToVoicemailState);
+        outState.putBoolean(KEY_ARE_PHONE_OPTIONS_CHANGEABLE, mArePhoneOptionsChangable);
+        outState.putString(KEY_CUSTOM_RINGTONE, mCustomRingtone);
+
+        outState.putBoolean(KEY_ENABLED, mEnabled);
+
+        // Aggregation PopupWindow
+        outState.putLong(KEY_AGGREGATION_SUGGESTIONS_RAW_CONTACT_ID,
+                mAggregationSuggestionsRawContactId);
+
+        // Join Activity
+        outState.putLong(KEY_CONTACT_ID_FOR_JOIN, mContactIdForJoin);
+
+        outState.putString(KEY_READ_ONLY_DISPLAY_NAME, mReadOnlyDisplayName);
+
         outState.putLong(KEY_PHOTO_RAW_CONTACT_ID, mPhotoRawContactId);
         outState.putParcelable(KEY_UPDATED_PHOTOS, mUpdatedPhotos);
         super.onSaveInstanceState(outState);
     }
 
     @Override
+    public void onStop() {
+        super.onStop();
+        UiClosables.closeQuietly(mAggregationSuggestionPopup);
+    }
+
+    @Override
+    public void onDestroy() {
+        super.onDestroy();
+        if (mAggregationSuggestionEngine != null) {
+            mAggregationSuggestionEngine.quit();
+        }
+    }
+
+    @Override
+    public void onActivityResult(int requestCode, int resultCode, Intent data) {
+        switch (requestCode) {
+            case REQUEST_CODE_JOIN: {
+                // Ignore failed requests
+                if (resultCode != Activity.RESULT_OK) return;
+                if (data != null) {
+                    final long contactId = ContentUris.parseId(data.getData());
+                    if (hasPendingChanges()) {
+                        // Ask the user if they want to save changes before doing the join
+                        JoinContactConfirmationDialogFragment.show(this, contactId);
+                    } else {
+                        // Do the join immediately
+                        joinAggregate(contactId);
+                    }
+                }
+                break;
+            }
+            case REQUEST_CODE_ACCOUNTS_CHANGED: {
+                // Bail if the account selector was not successful.
+                if (resultCode != Activity.RESULT_OK) {
+                    if (mListener != null) {
+                        mListener.onReverted();
+                    }
+                    return;
+                }
+                // If there's an account specified, use it.
+                if (data != null) {
+                    AccountWithDataSet account = data.getParcelableExtra(
+                            Intents.Insert.EXTRA_ACCOUNT);
+                    if (account != null) {
+                        createContact(account);
+                        return;
+                    }
+                }
+                // If there isn't an account specified, then this is likely a phone-local
+                // contact, so we should continue setting up the editor by automatically selecting
+                // the most appropriate account.
+                createContact();
+                break;
+            }
+            case REQUEST_CODE_PICK_RINGTONE: {
+                if (data != null) {
+                    final Uri pickedUri = data.getParcelableExtra(
+                            RingtoneManager.EXTRA_RINGTONE_PICKED_URI);
+                    onRingtonePicked(pickedUri);
+                }
+                break;
+            }
+        }
+    }
+
+    private void onRingtonePicked(Uri pickedUri) {
+        mCustomRingtone = EditorUiUtils.getRingtoneStringFromUri(pickedUri, CURRENT_API_VERSION);
+        Intent intent = ContactSaveService.createSetRingtone(
+                mContext, mLookupUri, mCustomRingtone);
+        mContext.startService(intent);
+    }
+
+    //
+    // Options menu
+    //
+
+    private void setStateForPhoneMenuItems(Contact contact) {
+        if (contact != null) {
+            mSendToVoicemailState = contact.isSendToVoicemail();
+            mCustomRingtone = contact.getCustomRingtone();
+            mArePhoneOptionsChangable = !contact.isDirectoryEntry()
+                    && PhoneCapabilityTester.isPhone(mContext);
+        }
+    }
+
+    @Override
+    public void onCreateOptionsMenu(Menu menu, final MenuInflater inflater) {
+        inflater.inflate(R.menu.edit_contact, menu);
+    }
+
+    @Override
+    public void onPrepareOptionsMenu(Menu menu) {
+        // This supports the keyboard shortcut to save changes to a contact but shouldn't be visible
+        // because the custom action bar contains the "save" button now (not the overflow menu).
+        // TODO: Find a better way to handle shortcuts, i.e. onKeyDown()?
+        final MenuItem saveMenu = menu.findItem(R.id.menu_save);
+        final MenuItem splitMenu = menu.findItem(R.id.menu_split);
+        final MenuItem joinMenu = menu.findItem(R.id.menu_join);
+        final MenuItem helpMenu = menu.findItem(R.id.menu_help);
+        final MenuItem sendToVoiceMailMenu = menu.findItem(R.id.menu_send_to_voicemail);
+        final MenuItem ringToneMenu = menu.findItem(R.id.menu_set_ringtone);
+        final MenuItem deleteMenu = menu.findItem(R.id.menu_delete);
+
+        // Set visibility of menus
+
+        // help menu depending on whether this is inserting or editing
+        if (Intent.ACTION_INSERT.equals(mAction) || mRawContactIdToDisplayAlone != -1) {
+            HelpUtils.prepareHelpMenuItem(mContext, helpMenu, R.string.help_url_people_add);
+            splitMenu.setVisible(false);
+            joinMenu.setVisible(false);
+            deleteMenu.setVisible(false);
+        } else if (Intent.ACTION_EDIT.equals(mAction)) {
+            HelpUtils.prepareHelpMenuItem(mContext, helpMenu, R.string.help_url_people_edit);
+            splitMenu.setVisible(canUnlinkRawContacts());
+            // Cannot join a user profile
+            joinMenu.setVisible(!isEditingUserProfile());
+            deleteMenu.setVisible(!mDisableDeleteMenuOption && !isEditingUserProfile());
+        } else {
+            // something else, so don't show the help menu
+            helpMenu.setVisible(false);
+        }
+
+        // Save menu is invisible when there's only one read only contact in the editor.
+        saveMenu.setVisible(!mRawContactDisplayAloneIsReadOnly);
+        if (saveMenu.isVisible()) {
+            // Since we're using a custom action layout we have to manually hook up the handler.
+            saveMenu.getActionView().setOnClickListener(new View.OnClickListener() {
+                @Override
+                public void onClick(View v) {
+                    onOptionsItemSelected(saveMenu);
+                }
+            });
+        }
+
+        if (mRawContactIdToDisplayAlone != -1 || mIsUserProfile) {
+            sendToVoiceMailMenu.setVisible(false);
+            ringToneMenu.setVisible(false);
+        } else {
+            // Hide telephony-related settings (ringtone, send to voicemail)
+            // if we don't have a telephone or are editing a new contact.
+            sendToVoiceMailMenu.setChecked(mSendToVoicemailState);
+            sendToVoiceMailMenu.setVisible(mArePhoneOptionsChangable);
+            ringToneMenu.setVisible(mArePhoneOptionsChangable);
+        }
+
+        int size = menu.size();
+        for (int i = 0; i < size; i++) {
+            menu.getItem(i).setEnabled(mEnabled);
+        }
+    }
+
+    @Override
     public boolean onOptionsItemSelected(MenuItem item) {
         if (item.getItemId() == android.R.id.home) {
             return revert();
         }
-        return super.onOptionsItemSelected(item);
+
+        final Activity activity = getActivity();
+        if (activity == null || activity.isFinishing() || activity.isDestroyed()) {
+            // If we no longer are attached to a running activity want to
+            // drain this event.
+            return true;
+        }
+
+        switch (item.getItemId()) {
+            case R.id.menu_save:
+                return save(SaveMode.CLOSE);
+            case R.id.menu_delete:
+                if (mListener != null) mListener.onDeleteRequested(mLookupUri);
+                return true;
+            case R.id.menu_split:
+                return doSplitContactAction();
+            case R.id.menu_join:
+                return doJoinContactAction();
+            case R.id.menu_set_ringtone:
+                doPickRingtone();
+                return true;
+            case R.id.menu_send_to_voicemail:
+                // Update state and save
+                mSendToVoicemailState = !mSendToVoicemailState;
+                item.setChecked(mSendToVoicemailState);
+                final Intent intent = ContactSaveService.createSetSendToVoicemail(
+                        mContext, mLookupUri, mSendToVoicemailState);
+                mContext.startService(intent);
+                return true;
+        }
+
+        return false;
     }
 
     @Override
+    public boolean revert() {
+        if (mState.isEmpty() || !hasPendingChanges()) {
+            onCancelEditConfirmed();
+        } else {
+            CancelEditDialogFragment.show(this);
+        }
+        return true;
+    }
+
+    @Override
+    public void onCancelEditConfirmed() {
+        // When this Fragment is closed we don't want it to auto-save
+        mStatus = Status.CLOSING;
+        if (mListener != null) {
+            mListener.onReverted();
+        }
+    }
+
+    @Override
+    public void onSplitContactConfirmed(boolean hasPendingChanges) {
+        if (mState.isEmpty()) {
+            // This may happen when this Fragment is recreated by the system during users
+            // confirming the split action (and thus this method is called just before onCreate()),
+            // for example.
+            Log.e(TAG, "mState became null during the user's confirming split action. " +
+                    "Cannot perform the save action.");
+            return;
+        }
+
+        if (!hasPendingChanges && mHasNewContact) {
+            // If the user didn't add anything new, we don't want to split out the newly created
+            // raw contact into a name-only contact so remove them.
+            final Iterator<RawContactDelta> iterator = mState.iterator();
+            while (iterator.hasNext()) {
+                final RawContactDelta rawContactDelta = iterator.next();
+                if (rawContactDelta.getRawContactId() < 0) {
+                    iterator.remove();
+                }
+            }
+        }
+        mState.markRawContactsForSplitting();
+        save(SaveMode.SPLIT);
+    }
+
+    private boolean doSplitContactAction() {
+        if (!hasValidState()) return false;
+
+        SplitContactConfirmationDialogFragment.show(this, hasPendingChanges());
+        return true;
+    }
+
+    private boolean doJoinContactAction() {
+        if (!hasValidState() || mLookupUri == null) {
+            return false;
+        }
+
+        // If we just started creating a new contact and haven't added any data, it's too
+        // early to do a join
+        if (mState.size() == 1 && mState.get(0).isContactInsert()
+                && !hasPendingChanges()) {
+            Toast.makeText(mContext, R.string.toast_join_with_empty_contact,
+                    Toast.LENGTH_LONG).show();
+            return true;
+        }
+
+        showJoinAggregateActivity(mLookupUri);
+        return true;
+    }
+
+    @Override
+    public void onJoinContactConfirmed(long joinContactId) {
+        doSaveAction(SaveMode.JOIN, joinContactId);
+    }
+
+    private void doPickRingtone() {
+        final Intent intent = new Intent(RingtoneManager.ACTION_RINGTONE_PICKER);
+        // Allow user to pick 'Default'
+        intent.putExtra(RingtoneManager.EXTRA_RINGTONE_SHOW_DEFAULT, true);
+        // Show only ringtones
+        intent.putExtra(RingtoneManager.EXTRA_RINGTONE_TYPE, RingtoneManager.TYPE_RINGTONE);
+        // Allow the user to pick a silent ringtone
+        intent.putExtra(RingtoneManager.EXTRA_RINGTONE_SHOW_SILENT, true);
+
+        final Uri ringtoneUri = EditorUiUtils.getRingtoneUriFromString(mCustomRingtone,
+                CURRENT_API_VERSION);
+
+        // Put checkmark next to the current ringtone for this contact
+        intent.putExtra(RingtoneManager.EXTRA_RINGTONE_EXISTING_URI, ringtoneUri);
+
+        // Launch!
+        try {
+            startActivityForResult(intent, REQUEST_CODE_PICK_RINGTONE);
+        } catch (ActivityNotFoundException ex) {
+            Toast.makeText(mContext, R.string.missing_app, Toast.LENGTH_SHORT).show();
+        }
+    }
+
+    @Override
+    public boolean save(int saveMode) {
+        if (!hasValidState() || mStatus != Status.EDITING) {
+            return false;
+        }
+
+        // If we are about to close the editor - there is no need to refresh the data
+        if (saveMode == SaveMode.CLOSE || saveMode == SaveMode.COMPACT
+                || saveMode == SaveMode.SPLIT) {
+            getLoaderManager().destroyLoader(LOADER_CONTACT);
+        }
+
+        mStatus = Status.SAVING;
+
+        if (!hasPendingChanges()) {
+            if (mLookupUri == null && saveMode == SaveMode.RELOAD) {
+                // We don't have anything to save and there isn't even an existing contact yet.
+                // Nothing to do, simply go back to editing mode
+                mStatus = Status.EDITING;
+                return true;
+            }
+            onSaveCompleted(/* hadChanges =*/ false, saveMode,
+                    /* saveSucceeded =*/ mLookupUri != null, mLookupUri, /* joinContactId =*/ null);
+            return true;
+        }
+
+        setEnabled(false);
+
+        return doSaveAction(saveMode, /* joinContactId */ null);
+    }
+
+    //
+    // State accessor methods
+    //
+
+    /**
+     * Check if our internal {@link #mState} is valid, usually checked before
+     * performing user actions.
+     */
+    private boolean hasValidState() {
+        return mState.size() > 0;
+    }
+
+    private boolean isEditingUserProfile() {
+        return mNewLocalProfile || mIsUserProfile;
+    }
+
+    /**
+     * Whether the contact being edited spans multiple raw contacts.
+     * The may also span multiple accounts.
+     */
+    private boolean isEditingMultipleRawContacts() {
+        return mState.size() > 1;
+    }
+
+    /**
+     * Whether the contact being edited is composed of a single read-only raw contact
+     * aggregated with a newly created writable raw contact.
+     */
+    private boolean isEditingReadOnlyRawContactWithNewContact() {
+        return mHasNewContact && mState.size() == 2;
+    }
+
+    /**
+     * Return true if there are any edits to the current contact which need to
+     * be saved.
+     */
+    private boolean hasPendingRawContactChanges(Set<String> excludedMimeTypes) {
+        final AccountTypeManager accountTypes = AccountTypeManager.getInstance(mContext);
+        return RawContactModifier.hasChanges(mState, accountTypes, excludedMimeTypes);
+    }
+
+    /**
+     * We allow unlinking only if there is more than one raw contact, it is not a user-profile,
+     * and unlinking won't result in an empty contact.  For the empty contact case, we only guard
+     * against this when there is a single read-only contact in the aggregate.  If the user
+     * has joined >1 read-only contacts together, we allow them to unlink it, even if they have
+     * never added their own information and unlinking will create a name only contact.
+     */
+    private boolean canUnlinkRawContacts() {
+        return isEditingMultipleRawContacts()
+                && !isEditingUserProfile()
+                && !isEditingReadOnlyRawContactWithNewContact();
+    }
+
+    /**
+     * Determines if changes were made in the editor that need to be saved, while taking into
+     * account that name changes are not real for read-only contacts.
+     * See go/editing-read-only-contacts
+     */
+    private boolean hasPendingChanges() {
+        if (mReadOnlyNameEditorView != null && mReadOnlyDisplayName != null) {
+            // We created a new raw contact delta with a default display name.
+            // We must test for pending changes while ignoring the default display name.
+            final String displayName = mReadOnlyNameEditorView.getDisplayName();
+            if (mReadOnlyDisplayName.equals(displayName)) {
+                final Set<String> excludedMimeTypes = new HashSet<>();
+                excludedMimeTypes.add(StructuredName.CONTENT_ITEM_TYPE);
+                return hasPendingRawContactChanges(excludedMimeTypes);
+            }
+            return true;
+        }
+        return hasPendingRawContactChanges(/* excludedMimeTypes =*/ null);
+    }
+
+    /**
+     * Whether editor inputs and the options menu should be enabled.
+     */
+    private boolean isEnabled() {
+        return mEnabled;
+    }
+
+    /**
+     * Returns the palette extra that was passed in.
+     */
+    private MaterialColorMapUtils.MaterialPalette getMaterialPalette() {
+        return mMaterialPalette;
+    }
+
+    //
+    // Account creation
+    //
+
+    private void selectAccountAndCreateContact() {
+        // If this is a local profile, then skip the logic about showing the accounts changed
+        // activity and create a phone-local contact.
+        if (mNewLocalProfile) {
+            createContact(null);
+            return;
+        }
+
+        // If there is no default account or the accounts have changed such that we need to
+        // prompt the user again, then launch the account prompt.
+        if (mEditorUtils.shouldShowAccountChangedNotification()) {
+            Intent intent = new Intent(mContext, ContactEditorAccountsChangedActivity.class);
+            // Prevent a second instance from being started on rotates
+            intent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
+            mStatus = Status.SUB_ACTIVITY;
+            startActivityForResult(intent, REQUEST_CODE_ACCOUNTS_CHANGED);
+        } else {
+            // Otherwise, there should be a default account. Then either create a local contact
+            // (if default account is null) or create a contact with the specified account.
+            AccountWithDataSet defaultAccount = mEditorUtils.getDefaultAccount();
+            createContact(defaultAccount);
+        }
+    }
+
+    /**
+     * Create a contact by automatically selecting the first account. If there's no available
+     * account, a device-local contact should be created.
+     */
+    private void createContact() {
+        final List<AccountWithDataSet> accounts =
+                AccountTypeManager.getInstance(mContext).getAccounts(true);
+        // No Accounts available. Create a phone-local contact.
+        if (accounts.isEmpty()) {
+            createContact(null);
+            return;
+        }
+
+        // We have an account switcher in "create-account" screen, so don't need to ask a user to
+        // select an account here.
+        createContact(accounts.get(0));
+    }
+
+    /**
+     * Shows account creation screen associated with a given account.
+     *
+     * @param account may be null to signal a device-local contact should be created.
+     */
+    private void createContact(AccountWithDataSet account) {
+        final AccountTypeManager accountTypes = AccountTypeManager.getInstance(mContext);
+        final AccountType accountType = accountTypes.getAccountTypeForAccount(account);
+
+        if (accountType.getCreateContactActivityClassName() != null) {
+            if (mListener != null) {
+                mListener.onCustomCreateContactActivityRequested(account, mIntentExtras);
+            }
+        } else {
+            setStateForNewContact(account, accountType, isEditingUserProfile());
+        }
+    }
+
+    //
+    // Data binding
+    //
+
+    private void setState(Contact contact) {
+        // If we have already loaded data, we do not want to change it here to not confuse the user
+        if (!mState.isEmpty()) {
+            Log.v(TAG, "Ignoring background change. This will have to be rebased later");
+            return;
+        }
+
+        // Prune raw contacts besides the one we want to edit
+        if (mRawContactIdToDisplayAlone > 0) {
+            final ImmutableList.Builder<RawContact> rawContactsBuilder =
+                    new ImmutableList.Builder<>();
+            for (RawContact rawContact : contact.getRawContacts()) {
+                if (rawContact.getId() == mRawContactIdToDisplayAlone) {
+                    rawContactsBuilder.add(rawContact);
+                    break;
+                }
+            }
+            mRawContacts = rawContactsBuilder.build();
+            Log.v(TAG, "Raw contact deltas trimmed from " + contact.getRawContacts().size() +
+                    " to " + mRawContacts.size());
+        } else {
+            mRawContacts = contact.getRawContacts();
+        }
+
+        // See if this edit operation needs to be redirected to a custom editor
+        if (mRawContacts.size() == 1) {
+            RawContact rawContact = mRawContacts.get(0);
+            String type = rawContact.getAccountTypeString();
+            String dataSet = rawContact.getDataSet();
+            AccountType accountType = rawContact.getAccountType(mContext);
+            if (accountType.getEditContactActivityClassName() != null &&
+                    !accountType.areContactsWritable()) {
+                if (mListener != null) {
+                    String name = rawContact.getAccountName();
+                    long rawContactId = rawContact.getId();
+                    mListener.onCustomEditContactActivityRequested(
+                            new AccountWithDataSet(name, type, dataSet),
+                            ContentUris.withAppendedId(RawContacts.CONTENT_URI, rawContactId),
+                            mIntentExtras, true);
+                }
+                return;
+            }
+        }
+
+        String readOnlyDisplayName = null;
+        // Check for writable raw contacts.  If there are none, then we need to create one so user
+        // can edit.  For the user profile case, there is already an editable contact.
+        if (!contact.isUserProfile() && !contact.isWritableContact(mContext)) {
+            mHasNewContact = true;
+
+            // This is potentially an asynchronous call and will add deltas to list.
+            selectAccountAndCreateContact();
+
+            readOnlyDisplayName = contact.getDisplayName();
+        } else {
+            mHasNewContact = false;
+        }
+
+        // This also adds deltas to list.  If readOnlyDisplayName is null at this point it is
+        // simply ignored later on by the editor.
+        setStateForExistingContact(readOnlyDisplayName, contact.isUserProfile(), mRawContacts);
+    }
+
+    /**
+     * Prepare {@link #mState} for a newly created phone-local contact.
+     */
+    private void setStateForNewContact(AccountWithDataSet account, AccountType accountType,
+            boolean isUserProfile) {
+        setStateForNewContact(account, accountType, /* oldState =*/ null,
+                /* oldAccountType =*/ null, isUserProfile);
+    }
+
+    /**
+     * Prepare {@link #mState} for a newly created phone-local contact, migrating the state
+     * specified by oldState and oldAccountType.
+     */
+    private void setStateForNewContact(AccountWithDataSet account, AccountType accountType,
+            RawContactDelta oldState, AccountType oldAccountType, boolean isUserProfile) {
+        mStatus = Status.EDITING;
+        mState.add(createNewRawContactDelta(account, accountType, oldState, oldAccountType));
+        mIsUserProfile = isUserProfile;
+        mNewContactDataReady = true;
+        bindEditors();
+    }
+
+    /**
+     * Returns a {@link RawContactDelta} for a new contact suitable for addition into
+     * {@link #mState}.
+     *
+     * If oldState and oldAccountType are specified, the state specified by those parameters
+     * is migrated to the result {@link RawContactDelta}.
+     */
+    private RawContactDelta createNewRawContactDelta(AccountWithDataSet account,
+            AccountType accountType, RawContactDelta oldState, AccountType oldAccountType) {
+        final RawContact rawContact = new RawContact();
+        if (account != null) {
+            rawContact.setAccount(account);
+        } else {
+            rawContact.setAccountToLocal();
+        }
+
+        final RawContactDelta result = new RawContactDelta(
+                ValuesDelta.fromAfter(rawContact.getValues()));
+        if (oldState == null) {
+            // Parse any values from incoming intent
+            RawContactModifier.parseExtras(mContext, accountType, result, mIntentExtras);
+        } else {
+            RawContactModifier.migrateStateForNewContact(
+                    mContext, oldState, result, oldAccountType, accountType);
+        }
+
+        // Ensure we have some default fields (if the account type does not support a field,
+        // ensureKind will not add it, so it is safe to add e.g. Event)
+        RawContactModifier.ensureKindExists(result, accountType, Phone.CONTENT_ITEM_TYPE);
+        RawContactModifier.ensureKindExists(result, accountType, Email.CONTENT_ITEM_TYPE);
+        RawContactModifier.ensureKindExists(result, accountType, Organization.CONTENT_ITEM_TYPE);
+        RawContactModifier.ensureKindExists(result, accountType, Event.CONTENT_ITEM_TYPE);
+        RawContactModifier.ensureKindExists(result, accountType,
+                StructuredPostal.CONTENT_ITEM_TYPE);
+
+        // Set the correct URI for saving the contact as a profile
+        if (mNewLocalProfile) {
+            result.setProfileQueryUri();
+        }
+
+        return result;
+    }
+
+    /**
+     * Prepare {@link #mState} for an existing contact.
+     */
+    private void setStateForExistingContact(String readOnlyDisplayName, boolean isUserProfile,
+            ImmutableList<RawContact> rawContacts) {
+        setEnabled(true);
+        mReadOnlyDisplayName = readOnlyDisplayName;
+
+        mState.addAll(rawContacts.iterator());
+        setIntentExtras(mIntentExtras);
+        mIntentExtras = null;
+
+        // For user profile, change the contacts query URI
+        mIsUserProfile = isUserProfile;
+        boolean localProfileExists = false;
+
+        if (mIsUserProfile) {
+            for (RawContactDelta rawContactDelta : mState) {
+                // For profile contacts, we need a different query URI
+                rawContactDelta.setProfileQueryUri();
+                // Try to find a local profile contact
+                if (rawContactDelta.getValues().getAsString(RawContacts.ACCOUNT_TYPE) == null) {
+                    localProfileExists = true;
+                }
+            }
+            // Editor should always present a local profile for editing
+            // TODO(wjang): Need to figure out when this case comes up.  We can't do this if we're
+            // going to prune all but the one raw contact that we're trying to display by itself.
+            if (!localProfileExists && mRawContactIdToDisplayAlone <= 0) {
+                mState.add(createLocalRawContactDelta());
+            }
+        }
+        mExistingContactDataReady = true;
+        bindEditors();
+    }
+
+    /**
+     * Set the enabled state of editors.
+     */
+    private void setEnabled(boolean enabled) {
+        if (mEnabled != enabled) {
+            mEnabled = enabled;
+
+            // Enable/disable editors
+            if (mContent != null) {
+                int count = mContent.getChildCount();
+                for (int i = 0; i < count; i++) {
+                    mContent.getChildAt(i).setEnabled(enabled);
+                }
+            }
+
+            // Enable/disable aggregation suggestion vies
+            if (mAggregationSuggestionView != null) {
+                LinearLayout itemList = (LinearLayout) mAggregationSuggestionView.findViewById(
+                        R.id.aggregation_suggestions);
+                int count = itemList.getChildCount();
+                for (int i = 0; i < count; i++) {
+                    itemList.getChildAt(i).setEnabled(enabled);
+                }
+            }
+
+            // Maybe invalidate the options menu
+            final Activity activity = getActivity();
+            if (activity != null) activity.invalidateOptionsMenu();
+        }
+    }
+
+    /**
+     * Returns a {@link RawContactDelta} for a local contact suitable for addition into
+     * {@link #mState}.
+     */
+    private static RawContactDelta createLocalRawContactDelta() {
+        final RawContact rawContact = new RawContact();
+        rawContact.setAccountToLocal();
+
+        final RawContactDelta result = new RawContactDelta(
+                ValuesDelta.fromAfter(rawContact.getValues()));
+        result.setProfileQueryUri();
+
+        return result;
+    }
+
+    /**
+     * Bind editors using {@link #mState} and other members initialized from the loaded (or new)
+     * Contact.
+     */
     protected void bindEditors() {
         if (!isReadyToBindEditors()) {
             return;
@@ -125,6 +1404,16 @@
         invalidateOptionsMenu();
     }
 
+    /**
+     * Invalidates the options menu if we are still associated with an Activity.
+     */
+    private void invalidateOptionsMenu() {
+        final Activity activity = getActivity();
+        if (activity != null) {
+            activity.invalidateOptionsMenu();
+        }
+    }
+
     private boolean isReadyToBindEditors() {
         if (mState.isEmpty()) {
             if (Log.isLoggable(TAG, Log.VERBOSE)) {
@@ -147,19 +1436,339 @@
         return true;
     }
 
+    /**
+     * Removes a current editor ({@link #mState}) and rebinds new editor for a new account.
+     * Some of old data are reused with new restriction enforced by the new account.
+     *
+     * @param oldState Old data being edited.
+     * @param oldAccount Old account associated with oldState.
+     * @param newAccount New account to be used.
+     */
+    private void rebindEditorsForNewContact(
+            RawContactDelta oldState, AccountWithDataSet oldAccount,
+            AccountWithDataSet newAccount) {
+        AccountTypeManager accountTypes = AccountTypeManager.getInstance(mContext);
+        AccountType oldAccountType = accountTypes.getAccountTypeForAccount(oldAccount);
+        AccountType newAccountType = accountTypes.getAccountTypeForAccount(newAccount);
+
+        if (newAccountType.getCreateContactActivityClassName() != null) {
+            Log.w(TAG, "external activity called in rebind situation");
+            if (mListener != null) {
+                mListener.onCustomCreateContactActivityRequested(newAccount, mIntentExtras);
+            }
+        } else {
+            mExistingContactDataReady = false;
+            mNewContactDataReady = false;
+            mState = new RawContactDeltaList();
+            setStateForNewContact(newAccount, newAccountType, oldState, oldAccountType,
+                    isEditingUserProfile());
+            if (mIsEdit) {
+                setStateForExistingContact(mReadOnlyDisplayName, isEditingUserProfile(),
+                        mRawContacts);
+            }
+        }
+    }
+
+    //
+    // ContactEditor
+    //
+
     @Override
+    public void setListener(Listener listener) {
+        mListener = listener;
+    }
+
+    @Override
+    public void load(String action, Uri lookupUri, Bundle intentExtras) {
+        mAction = action;
+        mLookupUri = lookupUri;
+        mIntentExtras = intentExtras;
+
+        if (mIntentExtras != null) {
+            mAutoAddToDefaultGroup =
+                    mIntentExtras.containsKey(INTENT_EXTRA_ADD_TO_DEFAULT_DIRECTORY);
+            mNewLocalProfile =
+                    mIntentExtras.getBoolean(INTENT_EXTRA_NEW_LOCAL_PROFILE);
+            mDisableDeleteMenuOption =
+                    mIntentExtras.getBoolean(INTENT_EXTRA_DISABLE_DELETE_MENU_OPTION);
+            if (mIntentExtras.containsKey(INTENT_EXTRA_MATERIAL_PALETTE_PRIMARY_COLOR)
+                    && mIntentExtras.containsKey(INTENT_EXTRA_MATERIAL_PALETTE_SECONDARY_COLOR)) {
+                mMaterialPalette = new MaterialColorMapUtils.MaterialPalette(
+                        mIntentExtras.getInt(INTENT_EXTRA_MATERIAL_PALETTE_PRIMARY_COLOR),
+                        mIntentExtras.getInt(INTENT_EXTRA_MATERIAL_PALETTE_SECONDARY_COLOR));
+            }
+            // If the user selected a different photo, don't restore the one from the Intent
+            if (mPhotoId < 0) {
+                mPhotoId = mIntentExtras.getLong(INTENT_EXTRA_PHOTO_ID);
+            }
+            mRawContactIdToDisplayAlone = mIntentExtras.getLong(
+                    INTENT_EXTRA_RAW_CONTACT_ID_TO_DISPLAY_ALONE, -1);
+            mRawContactDisplayAloneIsReadOnly = mIntentExtras.getBoolean(
+                    INTENT_EXTRA_RAW_CONTACT_DISPLAY_ALONE_IS_READ_ONLY);
+        }
+    }
+
+    @Override
+    public void setIntentExtras(Bundle extras) {
+        if (extras == null || extras.size() == 0) {
+            return;
+        }
+
+        final AccountTypeManager accountTypes = AccountTypeManager.getInstance(mContext);
+        for (RawContactDelta state : mState) {
+            final AccountType type = state.getAccountType(accountTypes);
+            if (type.areContactsWritable()) {
+                // Apply extras to the first writable raw contact only
+                RawContactModifier.parseExtras(mContext, type, state, extras);
+                break;
+            }
+        }
+    }
+
+    @Override
+    public void onJoinCompleted(Uri uri) {
+        onSaveCompleted(false, SaveMode.RELOAD, uri != null, uri, /* joinContactId */ null);
+    }
+
+    @Override
+    public void onSaveCompleted(boolean hadChanges, int saveMode, boolean saveSucceeded,
+            Uri contactLookupUri, Long joinContactId) {
+        if (hadChanges) {
+            if (saveSucceeded) {
+                switch (saveMode) {
+                    case SaveMode.JOIN:
+                        break;
+                    case SaveMode.SPLIT:
+                        Toast.makeText(mContext, R.string.contactUnlinkedToast, Toast.LENGTH_SHORT)
+                                .show();
+                        break;
+                    default:
+                        Toast.makeText(mContext, R.string.contactSavedToast, Toast.LENGTH_SHORT)
+                                .show();
+                }
+
+            } else {
+                Toast.makeText(mContext, R.string.contactSavedErrorToast, Toast.LENGTH_LONG).show();
+            }
+        }
+        switch (saveMode) {
+            case SaveMode.CLOSE: {
+                final Intent resultIntent;
+                if (saveSucceeded && contactLookupUri != null) {
+                    final Uri lookupUri = ContactEditorUtils.maybeConvertToLegacyLookupUri(
+                            mContext, contactLookupUri, mLookupUri);
+                    resultIntent = ImplicitIntentsUtil.composeQuickContactIntent(mContext,
+                            lookupUri, QuickContactActivity.MODE_FULLY_EXPANDED);
+                    resultIntent.putExtra(QuickContactActivity.EXTRA_PREVIOUS_SCREEN_TYPE,
+                            ScreenType.EDITOR);
+                    resultIntent.putExtra(QuickContactActivity.EXTRA_CONTACT_EDITED, true);
+                } else {
+                    resultIntent = null;
+                }
+                // It is already saved, so prevent it from being saved again
+                mStatus = Status.CLOSING;
+                if (mListener != null) mListener.onSaveFinished(resultIntent);
+                break;
+            }
+            case SaveMode.COMPACT: {
+                // It is already saved, so prevent it from being saved again
+                mStatus = Status.CLOSING;
+                if (mListener != null) mListener.onSaveFinished(/* resultIntent= */ null);
+                break;
+            }
+            case SaveMode.JOIN:
+                if (saveSucceeded && contactLookupUri != null && joinContactId != null) {
+                    joinAggregate(joinContactId);
+                }
+                break;
+            case SaveMode.RELOAD:
+                if (saveSucceeded && contactLookupUri != null) {
+                    // If this was in INSERT, we are changing into an EDIT now.
+                    // If it already was an EDIT, we are changing to the new Uri now
+                    mState = new RawContactDeltaList();
+                    load(Intent.ACTION_EDIT, contactLookupUri, null);
+                    mStatus = Status.LOADING;
+                    getLoaderManager().restartLoader(LOADER_CONTACT, null, mContactLoaderListener);
+                }
+                break;
+
+            case SaveMode.SPLIT:
+                mStatus = Status.CLOSING;
+                if (mListener != null) {
+                    mListener.onContactSplit(contactLookupUri);
+                } else {
+                    Log.d(TAG, "No listener registered, can not call onSplitFinished");
+                }
+                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 || !isAdded()) {
+            return;
+        }
+
+        mContactIdForJoin = ContentUris.parseId(contactLookupUri);
+        final Intent intent = new Intent(mContext, ContactSelectionActivity.class);
+        intent.setAction(UiIntentActions.PICK_JOIN_CONTACT_ACTION);
+        intent.putExtra(UiIntentActions.TARGET_CONTACT_ID_EXTRA_KEY, mContactIdForJoin);
+        startActivityForResult(intent, REQUEST_CODE_JOIN);
+    }
+
+    //
+    // Aggregation PopupWindow
+    //
+
+    /**
+     * Triggers an asynchronous search for aggregation suggestions.
+     */
+    protected void acquireAggregationSuggestions(Context context,
+            long rawContactId, ValuesDelta valuesDelta) {
+        if (mAggregationSuggestionsRawContactId != rawContactId
+                && mAggregationSuggestionView != null) {
+            mAggregationSuggestionView.setVisibility(View.GONE);
+            mAggregationSuggestionView = null;
+            mAggregationSuggestionEngine.reset();
+        }
+
+        mAggregationSuggestionsRawContactId = rawContactId;
+
+        if (mAggregationSuggestionEngine == null) {
+            mAggregationSuggestionEngine = new AggregationSuggestionEngine(context);
+            mAggregationSuggestionEngine.setListener(this);
+            mAggregationSuggestionEngine.start();
+        }
+
+        mAggregationSuggestionEngine.setContactId(getContactId());
+
+        mAggregationSuggestionEngine.onNameChange(valuesDelta);
+    }
+
+    /**
+     * Returns the contact ID for the currently edited contact or 0 if the contact is new.
+     */
+    private long getContactId() {
+        for (RawContactDelta rawContact : mState) {
+            Long contactId = rawContact.getValues().getAsLong(RawContacts.CONTACT_ID);
+            if (contactId != null) {
+                return contactId;
+            }
+        }
+        return 0;
+    }
+
+    @Override
+    public void onAggregationSuggestionChange() {
+        final Activity activity = getActivity();
+        if ((activity != null && activity.isFinishing())
+                || !isVisible() ||  mState.isEmpty() || mStatus != Status.EDITING) {
+            return;
+        }
+
+        UiClosables.closeQuietly(mAggregationSuggestionPopup);
+
+        if (mAggregationSuggestionEngine.getSuggestedContactCount() == 0) {
+            return;
+        }
+
+        final View anchorView = getAggregationAnchorView(mAggregationSuggestionsRawContactId);
+        if (anchorView == null) {
+            return; // Raw contact deleted?
+        }
+        mAggregationSuggestionPopup = new ListPopupWindow(mContext, null);
+        mAggregationSuggestionPopup.setAnchorView(anchorView);
+        mAggregationSuggestionPopup.setWidth(anchorView.getWidth());
+        mAggregationSuggestionPopup.setInputMethodMode(ListPopupWindow.INPUT_METHOD_NOT_NEEDED);
+        mAggregationSuggestionPopup.setAdapter(
+                new AggregationSuggestionAdapter(
+                        getActivity(),
+                        mState.size() == 1 && mState.get(0).isContactInsert(),
+                        /* listener =*/ this,
+                        mAggregationSuggestionEngine.getSuggestions()));
+        mAggregationSuggestionPopup.setOnItemClickListener(new AdapterView.OnItemClickListener() {
+            @Override
+            public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
+                final AggregationSuggestionView suggestionView = (AggregationSuggestionView) view;
+                suggestionView.handleItemClickEvent();
+                UiClosables.closeQuietly(mAggregationSuggestionPopup);
+                mAggregationSuggestionPopup = null;
+            }
+        });
+        mAggregationSuggestionPopup.show();
+    }
+
+    /**
+     * Returns the raw contact editor view for the given rawContactId that should be used as the
+     * anchor for aggregation suggestions.
+     */
     protected View getAggregationAnchorView(long rawContactId) {
         return getContent().getAggregationAnchorView();
     }
 
     @Override
+    public void onJoinAction(long contactId, List<Long> rawContactIdList) {
+        final long rawContactIds[] = new long[rawContactIdList.size()];
+        for (int i = 0; i < rawContactIds.length; i++) {
+            rawContactIds[i] = rawContactIdList.get(i);
+        }
+        try {
+            JoinSuggestedContactDialogFragment.show(this, rawContactIds);
+        } catch (Exception ignored) {
+            // No problem - the activity is no longer available to display the dialog
+        }
+    }
+
+    /**
+     * Joins the suggested contact (specified by the id's of constituent raw
+     * contacts), save all changes, and stay in the editor.
+     */
+    public void doJoinSuggestedContact(long[] rawContactIds) {
+        if (!hasValidState() || mStatus != Status.EDITING) {
+            return;
+        }
+
+        mState.setJoinWithRawContacts(rawContactIds);
+        save(SaveMode.RELOAD);
+    }
+
+    @Override
+    public void onEditAction(Uri contactLookupUri) {
+        SuggestionEditConfirmationDialogFragment.show(this, contactLookupUri);
+    }
+
+    /**
+     * Abandons the currently edited contact and switches to editing the suggested
+     * one, transferring all the data there
+     */
+    public void doEditSuggestedContact(Uri contactUri) {
+        if (mListener != null) {
+            // make sure we don't save this contact when closing down
+            mStatus = Status.CLOSING;
+            mListener.onEditOtherContactRequested(
+                    contactUri, mState.get(0).getContentValues());
+        }
+    }
+
+    /**
+     * Sets group metadata on all bound editors.
+     */
     protected void setGroupMetaData() {
         if (mGroupMetaData != null) {
             getContent().setGroupMetaData(mGroupMetaData);
         }
     }
 
-    @Override
+    /**
+     * Persist the accumulated editor deltas.
+     *
+     * @param joinContactId the raw contact ID to join the contact being saved to after the save,
+     *         may be null.
+     */
     protected boolean doSaveAction(int saveMode, Long joinContactId) {
         final Intent intent = ContactSaveService.createSaveContactIntent(mContext, mState,
                 SAVE_MODE_EXTRA_KEY, saveMode, isEditingUserProfile(),
@@ -169,7 +1778,22 @@
         return startSaveService(mContext, intent, saveMode);
     }
 
-    @Override
+    private boolean startSaveService(Context context, Intent intent, int saveMode) {
+        final boolean result = ContactSaveService.startService(
+                context, intent, saveMode);
+        if (!result) {
+            onCancelEditConfirmed();
+        }
+        return result;
+    }
+
+    //
+    // Join Activity
+    //
+
+    /**
+     * Performs aggregation with the contact selected by the user from suggestions or A-Z list.
+     */
     protected void joinAggregate(final long contactId) {
         final Intent intent = ContactSaveService.createJoinContactsIntent(
                 mContext, mContactIdForJoin, contactId, CompactContactEditorActivity.class,
diff --git a/src/com/android/contacts/editor/ContactEditorBaseFragment.java b/src/com/android/contacts/editor/ContactEditorBaseFragment.java
deleted file mode 100644
index 8f89252..0000000
--- a/src/com/android/contacts/editor/ContactEditorBaseFragment.java
+++ /dev/null
@@ -1,1738 +0,0 @@
-/*
- * Copyright (C) 2015 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.editor;
-
-import android.accounts.Account;
-import android.app.Activity;
-import android.app.Fragment;
-import android.app.LoaderManager;
-import android.content.ActivityNotFoundException;
-import android.content.ContentUris;
-import android.content.ContentValues;
-import android.content.Context;
-import android.content.CursorLoader;
-import android.content.Intent;
-import android.content.Loader;
-import android.database.Cursor;
-import android.media.RingtoneManager;
-import android.net.Uri;
-import android.os.Bundle;
-import android.os.SystemClock;
-import android.provider.ContactsContract;
-import android.provider.ContactsContract.CommonDataKinds.Email;
-import android.provider.ContactsContract.CommonDataKinds.Event;
-import android.provider.ContactsContract.CommonDataKinds.Organization;
-import android.provider.ContactsContract.CommonDataKinds.Phone;
-import android.provider.ContactsContract.CommonDataKinds.StructuredName;
-import android.provider.ContactsContract.CommonDataKinds.StructuredPostal;
-import android.provider.ContactsContract.Contacts;
-import android.provider.ContactsContract.Intents;
-import android.provider.ContactsContract.RawContacts;
-import android.util.Log;
-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.AdapterView;
-import android.widget.BaseAdapter;
-import android.widget.LinearLayout;
-import android.widget.ListPopupWindow;
-import android.widget.Toast;
-
-import com.android.contacts.ContactSaveService;
-import com.android.contacts.GroupMetaDataLoader;
-import com.android.contacts.R;
-import com.android.contacts.activities.CompactContactEditorActivity;
-import com.android.contacts.activities.CompactContactEditorActivity.ContactEditor;
-import com.android.contacts.activities.ContactEditorAccountsChangedActivity;
-import com.android.contacts.activities.ContactSelectionActivity;
-import com.android.contacts.common.logging.ScreenEvent.ScreenType;
-import com.android.contacts.common.model.AccountTypeManager;
-import com.android.contacts.common.model.Contact;
-import com.android.contacts.common.model.ContactLoader;
-import com.android.contacts.common.model.RawContact;
-import com.android.contacts.common.model.RawContactDelta;
-import com.android.contacts.common.model.RawContactDeltaList;
-import com.android.contacts.common.model.RawContactModifier;
-import com.android.contacts.common.model.ValuesDelta;
-import com.android.contacts.common.model.account.AccountType;
-import com.android.contacts.common.model.account.AccountWithDataSet;
-import com.android.contacts.common.util.ImplicitIntentsUtil;
-import com.android.contacts.common.util.MaterialColorMapUtils;
-import com.android.contacts.editor.AggregationSuggestionEngine.Suggestion;
-import com.android.contacts.list.UiIntentActions;
-import com.android.contacts.quickcontact.QuickContactActivity;
-import com.android.contacts.util.HelpUtils;
-import com.android.contacts.util.PhoneCapabilityTester;
-import com.android.contacts.util.UiClosables;
-
-import com.google.common.collect.ImmutableList;
-import com.google.common.collect.Lists;
-
-import java.util.ArrayList;
-import java.util.HashSet;
-import java.util.Iterator;
-import java.util.List;
-import java.util.Set;
-
-/**
- * Base Fragment for contact editors.
- */
-abstract public class ContactEditorBaseFragment extends Fragment implements
-        ContactEditor, SplitContactConfirmationDialogFragment.Listener,
-        JoinContactConfirmationDialogFragment.Listener,
-        AggregationSuggestionEngine.Listener, AggregationSuggestionView.Listener,
-        CancelEditDialogFragment.Listener {
-
-    static final String TAG = "ContactEditor";
-
-    protected static final int LOADER_CONTACT = 1;
-    protected static final int LOADER_GROUPS = 2;
-
-    private static final List<String> VALID_INTENT_ACTIONS = new ArrayList<String>() {{
-        add(Intent.ACTION_EDIT);
-        add(Intent.ACTION_INSERT);
-        add(CompactContactEditorActivity.ACTION_SAVE_COMPLETED);
-    }};
-
-    private static final String KEY_ACTION = "action";
-    private static final String KEY_URI = "uri";
-    private static final String KEY_AUTO_ADD_TO_DEFAULT_GROUP = "autoAddToDefaultGroup";
-    private static final String KEY_DISABLE_DELETE_MENU_OPTION = "disableDeleteMenuOption";
-    private static final String KEY_NEW_LOCAL_PROFILE = "newLocalProfile";
-    private static final String KEY_MATERIAL_PALETTE = "materialPalette";
-    private static final String KEY_PHOTO_ID = "photoId";
-
-    private static final String KEY_VIEW_ID_GENERATOR = "viewidgenerator";
-
-    private static final String KEY_RAW_CONTACTS = "rawContacts";
-
-    private static final String KEY_EDIT_STATE = "state";
-    private static final String KEY_STATUS = "status";
-
-    private static final String KEY_HAS_NEW_CONTACT = "hasNewContact";
-    private static final String KEY_NEW_CONTACT_READY = "newContactDataReady";
-
-    private static final String KEY_IS_EDIT = "isEdit";
-    private static final String KEY_EXISTING_CONTACT_READY = "existingContactDataReady";
-
-    private static final String KEY_RAW_CONTACT_DISPLAY_ALONE_IS_READ_ONLY = "isReadOnly";
-
-    // Phone option menus
-    private static final String KEY_SEND_TO_VOICE_MAIL_STATE = "sendToVoicemailState";
-    private static final String KEY_ARE_PHONE_OPTIONS_CHANGEABLE = "arePhoneOptionsChangable";
-    private static final String KEY_CUSTOM_RINGTONE = "customRingtone";
-
-    private static final String KEY_IS_USER_PROFILE = "isUserProfile";
-
-    private static final String KEY_ENABLED = "enabled";
-
-    // Aggregation PopupWindow
-    private static final String KEY_AGGREGATION_SUGGESTIONS_RAW_CONTACT_ID =
-            "aggregationSuggestionsRawContactId";
-
-    // Join Activity
-    private static final String KEY_CONTACT_ID_FOR_JOIN = "contactidforjoin";
-
-    private static final String KEY_READ_ONLY_DISPLAY_NAME = "readOnlyDisplayName";
-
-    protected static final int REQUEST_CODE_JOIN = 0;
-    protected static final int REQUEST_CODE_ACCOUNTS_CHANGED = 1;
-    protected static final int REQUEST_CODE_PICK_RINGTONE = 2;
-
-    private static final int CURRENT_API_VERSION = android.os.Build.VERSION.SDK_INT;
-
-    /**
-     * An intent extra that forces the editor to add the edited contact
-     * to the default group (e.g. "My Contacts").
-     */
-    public static final String INTENT_EXTRA_ADD_TO_DEFAULT_DIRECTORY = "addToDefaultDirectory";
-
-    public static final String INTENT_EXTRA_NEW_LOCAL_PROFILE = "newLocalProfile";
-
-    public static final String INTENT_EXTRA_DISABLE_DELETE_MENU_OPTION =
-            "disableDeleteMenuOption";
-
-    /**
-     * Intent key to pass the photo palette primary color calculated by
-     * {@link com.android.contacts.quickcontact.QuickContactActivity} to the editor and between
-     * the compact and fully expanded editors.
-     */
-    public static final String INTENT_EXTRA_MATERIAL_PALETTE_PRIMARY_COLOR =
-            "material_palette_primary_color";
-
-    /**
-     * Intent key to pass the photo palette secondary color calculated by
-     * {@link com.android.contacts.quickcontact.QuickContactActivity} to the editor and between
-     * the compact and fully expanded editors.
-     */
-    public static final String INTENT_EXTRA_MATERIAL_PALETTE_SECONDARY_COLOR =
-            "material_palette_secondary_color";
-
-    /**
-     * Intent key to pass the ID of the photo to display on the editor.
-     */
-    public static final String INTENT_EXTRA_PHOTO_ID = "photo_id";
-
-    /**
-     * Intent key to pass the ID of the raw contact id that should be displayed in the full editor
-     * by itself.
-     */
-    public static final String INTENT_EXTRA_RAW_CONTACT_ID_TO_DISPLAY_ALONE =
-            "raw_contact_id_to_display_alone";
-
-    /**
-     * Intent key to pass the boolean value of if the raw contact id that should be displayed
-     * in the full editor by itself is read-only.
-     */
-    public static final String INTENT_EXTRA_RAW_CONTACT_DISPLAY_ALONE_IS_READ_ONLY =
-            "raw_contact_display_alone_is_read_only";
-
-    /**
-     * Intent extra to specify a {@link ContactEditor.SaveMode}.
-     */
-    public static final String SAVE_MODE_EXTRA_KEY = "saveMode";
-
-    /**
-     * Intent extra key for the contact ID to join the current contact to after saving.
-     */
-    public static final String JOIN_CONTACT_ID_EXTRA_KEY = "joinContactId";
-
-    /**
-     * Callbacks for Activities that host contact editors Fragments.
-     */
-    public interface Listener {
-
-        /**
-         * Contact was not found, so somehow close this fragment. This is raised after a contact
-         * is removed via Menu/Delete
-         */
-        void onContactNotFound();
-
-        /**
-         * Contact was split, so we can close now.
-         *
-         * @param newLookupUri The lookup uri of the new contact that should be shown to the user.
-         *                     The editor tries best to chose the most natural contact here.
-         */
-        void onContactSplit(Uri newLookupUri);
-
-        /**
-         * User has tapped Revert, close the fragment now.
-         */
-        void onReverted();
-
-        /**
-         * Contact was saved and the Fragment can now be closed safely.
-         */
-        void onSaveFinished(Intent resultIntent);
-
-        /**
-         * User switched to editing a different contact (a suggestion from the
-         * aggregation engine).
-         */
-        void onEditOtherContactRequested(Uri contactLookupUri,
-                ArrayList<ContentValues> contentValues);
-
-        /**
-         * Contact is being created for an external account that provides its own
-         * new contact activity.
-         */
-        void onCustomCreateContactActivityRequested(AccountWithDataSet account,
-                Bundle intentExtras);
-
-        /**
-         * The edited raw contact belongs to an external account that provides
-         * its own edit activity.
-         *
-         * @param redirect indicates that the current editor should be closed
-         *                 before the custom editor is shown.
-         */
-        void onCustomEditContactActivityRequested(AccountWithDataSet account, Uri rawContactUri,
-                Bundle intentExtras, boolean redirect);
-
-        /**
-         * User has requested that contact be deleted.
-         */
-        void onDeleteRequested(Uri contactUri);
-    }
-
-    /**
-     * Adapter for aggregation suggestions displayed in a PopupWindow when
-     * editor fields change.
-     */
-    protected static final class AggregationSuggestionAdapter extends BaseAdapter {
-        private final LayoutInflater mLayoutInflater;
-        private final boolean mSetNewContact;
-        private final AggregationSuggestionView.Listener mListener;
-        private final List<AggregationSuggestionEngine.Suggestion> mSuggestions;
-
-        public AggregationSuggestionAdapter(Activity activity, boolean setNewContact,
-                AggregationSuggestionView.Listener listener, List<Suggestion> suggestions) {
-            mLayoutInflater = activity.getLayoutInflater();
-            mSetNewContact = setNewContact;
-            mListener = listener;
-            mSuggestions = suggestions;
-        }
-
-        @Override
-        public View getView(int position, View convertView, ViewGroup parent) {
-            final Suggestion suggestion = (Suggestion) getItem(position);
-            final AggregationSuggestionView suggestionView =
-                    (AggregationSuggestionView) mLayoutInflater.inflate(
-                            R.layout.aggregation_suggestions_item, null);
-            suggestionView.setNewContact(mSetNewContact);
-            suggestionView.setListener(mListener);
-            suggestionView.bindSuggestion(suggestion);
-            return suggestionView;
-        }
-
-        @Override
-        public long getItemId(int position) {
-            return position;
-        }
-
-        @Override
-        public Object getItem(int position) {
-            return mSuggestions.get(position);
-        }
-
-        @Override
-        public int getCount() {
-            return mSuggestions.size();
-        }
-    }
-
-    protected Context mContext;
-    protected Listener mListener;
-
-    //
-    // Views
-    //
-    protected LinearLayout mContent;
-    protected View mAggregationSuggestionView;
-    protected ListPopupWindow mAggregationSuggestionPopup;
-
-    //
-    // Parameters passed in on {@link #load}
-    //
-    protected String mAction;
-    protected Uri mLookupUri;
-    protected Bundle mIntentExtras;
-    protected boolean mAutoAddToDefaultGroup;
-    protected boolean mDisableDeleteMenuOption;
-    protected boolean mNewLocalProfile;
-    protected MaterialColorMapUtils.MaterialPalette mMaterialPalette;
-    protected long mPhotoId = -1;
-
-    //
-    // Helpers
-    //
-    protected ContactEditorUtils mEditorUtils;
-    protected RawContactDeltaComparator mComparator;
-    protected ViewIdGenerator mViewIdGenerator;
-    private AggregationSuggestionEngine mAggregationSuggestionEngine;
-
-    //
-    // Loaded data
-    //
-    // Used to store existing contact data so it can be re-applied during a rebind call,
-    // i.e. account switch.
-    protected ImmutableList<RawContact> mRawContacts;
-    protected Cursor mGroupMetaData;
-
-    //
-    // Editor state
-    //
-    protected RawContactDeltaList mState;
-    protected int mStatus;
-    protected long mRawContactIdToDisplayAlone = -1;
-    protected boolean mRawContactDisplayAloneIsReadOnly = false;
-
-    // Whether to show the new contact blank form and if it's corresponding delta is ready.
-    protected boolean mHasNewContact;
-    protected AccountWithDataSet mAccountWithDataSet;
-    protected boolean mNewContactDataReady;
-    protected boolean mNewContactAccountChanged;
-
-    // Whether it's an edit of existing contact and if it's corresponding delta is ready.
-    protected boolean mIsEdit;
-    protected boolean mExistingContactDataReady;
-
-    // Whether we are editing the "me" profile
-    protected boolean mIsUserProfile;
-
-    // Phone specific option menu items
-    private boolean mSendToVoicemailState;
-    private boolean mArePhoneOptionsChangable;
-    private String mCustomRingtone;
-
-    // Whether editor views and options menu items should be enabled
-    private boolean mEnabled = true;
-
-    // Aggregation PopupWindow
-    private long mAggregationSuggestionsRawContactId;
-
-    // Join Activity
-    protected long mContactIdForJoin;
-
-    // Used to pre-populate the editor with a display name when a user edits a read-only contact.
-    protected String mReadOnlyDisplayName;
-
-    //
-    // Not saved/restored on rotates
-    //
-
-    // The name editor view for the new raw contact that was created so that the user can
-    // edit a read-only contact (to which the new raw contact was joined)
-    protected StructuredNameEditorView mReadOnlyNameEditorView;
-
-    /**
-     * The contact data loader listener.
-     */
-    protected final LoaderManager.LoaderCallbacks<Contact> mContactLoaderListener =
-            new LoaderManager.LoaderCallbacks<Contact>() {
-
-                protected long mLoaderStartTime;
-
-                @Override
-                public Loader<Contact> onCreateLoader(int id, Bundle args) {
-                    mLoaderStartTime = SystemClock.elapsedRealtime();
-                    return new ContactLoader(mContext, mLookupUri, true);
-                }
-
-                @Override
-                public void onLoadFinished(Loader<Contact> loader, Contact contact) {
-                    final long loaderCurrentTime = SystemClock.elapsedRealtime();
-                    Log.v(TAG, "Time needed for loading: " + (loaderCurrentTime-mLoaderStartTime));
-                    if (!contact.isLoaded()) {
-                        // Item has been deleted. Close activity without saving again.
-                        Log.i(TAG, "No contact found. Closing activity");
-                        mStatus = Status.CLOSING;
-                        if (mListener != null) mListener.onContactNotFound();
-                        return;
-                    }
-
-                    mStatus = Status.EDITING;
-                    mLookupUri = contact.getLookupUri();
-                    final long setDataStartTime = SystemClock.elapsedRealtime();
-                    setState(contact);
-                    setStateForPhoneMenuItems(contact);
-                    final long setDataEndTime = SystemClock.elapsedRealtime();
-
-                    Log.v(TAG, "Time needed for setting UI: " + (setDataEndTime - setDataStartTime));
-                }
-
-                @Override
-                public void onLoaderReset(Loader<Contact> loader) {
-                }
-            };
-
-    /**
-     * The groups meta data loader listener.
-     */
-    protected final LoaderManager.LoaderCallbacks<Cursor> mGroupsLoaderListener =
-            new LoaderManager.LoaderCallbacks<Cursor>() {
-
-                @Override
-                public CursorLoader onCreateLoader(int id, Bundle args) {
-                    return new GroupMetaDataLoader(mContext, ContactsContract.Groups.CONTENT_URI);
-                }
-
-                @Override
-                public void onLoadFinished(Loader<Cursor> loader, Cursor data) {
-                    mGroupMetaData = data;
-                    setGroupMetaData();
-                }
-
-                @Override
-                public void onLoaderReset(Loader<Cursor> loader) {
-                }
-            };
-
-    @Override
-    public void onAttach(Activity activity) {
-        super.onAttach(activity);
-        mContext = activity;
-        mEditorUtils = ContactEditorUtils.getInstance(mContext);
-        mComparator = new RawContactDeltaComparator(mContext);
-    }
-
-    @Override
-    public void onCreate(Bundle savedState) {
-        if (savedState != null) {
-            // Restore mUri before calling super.onCreate so that onInitializeLoaders
-            // would already have a uri and an action to work with
-            mAction = savedState.getString(KEY_ACTION);
-            mLookupUri = savedState.getParcelable(KEY_URI);
-        }
-
-        super.onCreate(savedState);
-
-        if (savedState == null) {
-            mViewIdGenerator = new ViewIdGenerator();
-        } else {
-            mViewIdGenerator = savedState.getParcelable(KEY_VIEW_ID_GENERATOR);
-
-            mAutoAddToDefaultGroup = savedState.getBoolean(KEY_AUTO_ADD_TO_DEFAULT_GROUP);
-            mDisableDeleteMenuOption = savedState.getBoolean(KEY_DISABLE_DELETE_MENU_OPTION);
-            mNewLocalProfile = savedState.getBoolean(KEY_NEW_LOCAL_PROFILE);
-            mMaterialPalette = savedState.getParcelable(KEY_MATERIAL_PALETTE);
-            mPhotoId = savedState.getLong(KEY_PHOTO_ID);
-
-            mRawContacts = ImmutableList.copyOf(savedState.<RawContact>getParcelableArrayList(
-                    KEY_RAW_CONTACTS));
-            // NOTE: mGroupMetaData is not saved/restored
-
-            // Read state from savedState. No loading involved here
-            mState = savedState.<RawContactDeltaList> getParcelable(KEY_EDIT_STATE);
-            mStatus = savedState.getInt(KEY_STATUS);
-            mRawContactDisplayAloneIsReadOnly = savedState.getBoolean(
-                    KEY_RAW_CONTACT_DISPLAY_ALONE_IS_READ_ONLY);
-
-            mHasNewContact = savedState.getBoolean(KEY_HAS_NEW_CONTACT);
-            mNewContactDataReady = savedState.getBoolean(KEY_NEW_CONTACT_READY);
-
-            mIsEdit = savedState.getBoolean(KEY_IS_EDIT);
-            mExistingContactDataReady = savedState.getBoolean(KEY_EXISTING_CONTACT_READY);
-
-            mIsUserProfile = savedState.getBoolean(KEY_IS_USER_PROFILE);
-
-            // Phone specific options menus
-            mSendToVoicemailState = savedState.getBoolean(KEY_SEND_TO_VOICE_MAIL_STATE);
-            mArePhoneOptionsChangable = savedState.getBoolean(KEY_ARE_PHONE_OPTIONS_CHANGEABLE);
-            mCustomRingtone = savedState.getString(KEY_CUSTOM_RINGTONE);
-
-            mEnabled = savedState.getBoolean(KEY_ENABLED);
-
-            // Aggregation PopupWindow
-            mAggregationSuggestionsRawContactId = savedState.getLong(
-                    KEY_AGGREGATION_SUGGESTIONS_RAW_CONTACT_ID);
-
-            // Join Activity
-            mContactIdForJoin = savedState.getLong(KEY_CONTACT_ID_FOR_JOIN);
-
-            mReadOnlyDisplayName = savedState.getString(KEY_READ_ONLY_DISPLAY_NAME);
-        }
-
-        // mState can still be null because it may not have have finished loading before
-        // onSaveInstanceState was called.
-        if (mState == null) {
-            mState = new RawContactDeltaList();
-        }
-    }
-
-    @Override
-    public void onActivityCreated(Bundle savedInstanceState) {
-        super.onActivityCreated(savedInstanceState);
-
-        validateAction(mAction);
-
-        if (mState.isEmpty()) {
-            // The delta list may not have finished loading before orientation change happens.
-            // In this case, there will be a saved state but deltas will be missing.  Reload from
-            // database.
-            if (Intent.ACTION_EDIT.equals(mAction)) {
-                // Either
-                // 1) orientation change but load never finished.
-                // 2) not an orientation change so data needs to be loaded for first time.
-                getLoaderManager().initLoader(LOADER_CONTACT, null, mContactLoaderListener);
-                getLoaderManager().initLoader(LOADER_GROUPS, null, mGroupsLoaderListener);
-            }
-        } else {
-            // Orientation change, we already have mState, it was loaded by onCreate
-            bindEditors();
-        }
-
-        // Handle initial actions only when existing state missing
-        if (savedInstanceState == null) {
-            final Account account = mIntentExtras == null ? null :
-                    (Account) mIntentExtras.getParcelable(Intents.Insert.EXTRA_ACCOUNT);
-            final String dataSet = mIntentExtras == null ? null :
-                    mIntentExtras.getString(Intents.Insert.EXTRA_DATA_SET);
-            if (account != null) {
-                mAccountWithDataSet = new AccountWithDataSet(account.name, account.type, dataSet);
-            }
-
-            if (Intent.ACTION_EDIT.equals(mAction)) {
-                mIsEdit = true;
-            } else if (Intent.ACTION_INSERT.equals(mAction)) {
-                mHasNewContact = true;
-                if (mAccountWithDataSet != null) {
-                    createContact(mAccountWithDataSet);
-                } else {
-                    // No Account specified. Let the user choose
-                    // Load Accounts async so that we can present them
-                    selectAccountAndCreateContact();
-                }
-            }
-        }
-    }
-
-    /**
-     * Checks if the requested action is valid.
-     *
-     * @param action The action to test.
-     * @throws IllegalArgumentException when the action is invalid.
-     */
-    private static void validateAction(String action) {
-        if (VALID_INTENT_ACTIONS.contains(action)) {
-            return;
-        }
-        throw new IllegalArgumentException(
-                "Unknown action " + action + "; Supported actions: " + VALID_INTENT_ACTIONS);
-    }
-
-    @Override
-    public void onSaveInstanceState(Bundle outState) {
-        outState.putString(KEY_ACTION, mAction);
-        outState.putParcelable(KEY_URI, mLookupUri);
-        outState.putBoolean(KEY_AUTO_ADD_TO_DEFAULT_GROUP, mAutoAddToDefaultGroup);
-        outState.putBoolean(KEY_DISABLE_DELETE_MENU_OPTION, mDisableDeleteMenuOption);
-        outState.putBoolean(KEY_NEW_LOCAL_PROFILE, mNewLocalProfile);
-        if (mMaterialPalette != null) {
-            outState.putParcelable(KEY_MATERIAL_PALETTE, mMaterialPalette);
-        }
-        outState.putLong(KEY_PHOTO_ID, mPhotoId);
-
-        outState.putParcelable(KEY_VIEW_ID_GENERATOR, mViewIdGenerator);
-
-        outState.putParcelableArrayList(KEY_RAW_CONTACTS, mRawContacts == null ?
-                Lists.<RawContact>newArrayList() : Lists.newArrayList(mRawContacts));
-        // NOTE: mGroupMetaData is not saved
-
-        if (hasValidState()) {
-            // Store entities with modifications
-            outState.putParcelable(KEY_EDIT_STATE, mState);
-        }
-        outState.putInt(KEY_STATUS, mStatus);
-        outState.putBoolean(KEY_HAS_NEW_CONTACT, mHasNewContact);
-        outState.putBoolean(KEY_NEW_CONTACT_READY, mNewContactDataReady);
-        outState.putBoolean(KEY_IS_EDIT, mIsEdit);
-        outState.putBoolean(KEY_EXISTING_CONTACT_READY, mExistingContactDataReady);
-        outState.putBoolean(KEY_RAW_CONTACT_DISPLAY_ALONE_IS_READ_ONLY,
-                mRawContactDisplayAloneIsReadOnly);
-
-        outState.putBoolean(KEY_IS_USER_PROFILE, mIsUserProfile);
-
-        // Phone specific options
-        outState.putBoolean(KEY_SEND_TO_VOICE_MAIL_STATE, mSendToVoicemailState);
-        outState.putBoolean(KEY_ARE_PHONE_OPTIONS_CHANGEABLE, mArePhoneOptionsChangable);
-        outState.putString(KEY_CUSTOM_RINGTONE, mCustomRingtone);
-
-        outState.putBoolean(KEY_ENABLED, mEnabled);
-
-        // Aggregation PopupWindow
-        outState.putLong(KEY_AGGREGATION_SUGGESTIONS_RAW_CONTACT_ID,
-                mAggregationSuggestionsRawContactId);
-
-        // Join Activity
-        outState.putLong(KEY_CONTACT_ID_FOR_JOIN, mContactIdForJoin);
-
-        outState.putString(KEY_READ_ONLY_DISPLAY_NAME, mReadOnlyDisplayName);
-
-        super.onSaveInstanceState(outState);
-    }
-
-    @Override
-    public void onStop() {
-        super.onStop();
-        UiClosables.closeQuietly(mAggregationSuggestionPopup);
-    }
-
-    @Override
-    public void onDestroy() {
-        super.onDestroy();
-        if (mAggregationSuggestionEngine != null) {
-            mAggregationSuggestionEngine.quit();
-        }
-    }
-
-    @Override
-    public void onActivityResult(int requestCode, int resultCode, Intent data) {
-        switch (requestCode) {
-            case REQUEST_CODE_JOIN: {
-                // Ignore failed requests
-                if (resultCode != Activity.RESULT_OK) return;
-                if (data != null) {
-                    final long contactId = ContentUris.parseId(data.getData());
-                    if (hasPendingChanges()) {
-                        // Ask the user if they want to save changes before doing the join
-                        JoinContactConfirmationDialogFragment.show(this, contactId);
-                    } else {
-                        // Do the join immediately
-                        joinAggregate(contactId);
-                    }
-                }
-                break;
-            }
-            case REQUEST_CODE_ACCOUNTS_CHANGED: {
-                // Bail if the account selector was not successful.
-                if (resultCode != Activity.RESULT_OK) {
-                    if (mListener != null) {
-                        mListener.onReverted();
-                    }
-                    return;
-                }
-                // If there's an account specified, use it.
-                if (data != null) {
-                    AccountWithDataSet account = data.getParcelableExtra(
-                            Intents.Insert.EXTRA_ACCOUNT);
-                    if (account != null) {
-                        createContact(account);
-                        return;
-                    }
-                }
-                // If there isn't an account specified, then this is likely a phone-local
-                // contact, so we should continue setting up the editor by automatically selecting
-                // the most appropriate account.
-                createContact();
-                break;
-            }
-            case REQUEST_CODE_PICK_RINGTONE: {
-                if (data != null) {
-                    final Uri pickedUri = data.getParcelableExtra(
-                            RingtoneManager.EXTRA_RINGTONE_PICKED_URI);
-                    onRingtonePicked(pickedUri);
-                }
-                break;
-            }
-        }
-    }
-
-    private void onRingtonePicked(Uri pickedUri) {
-        mCustomRingtone = EditorUiUtils.getRingtoneStringFromUri(pickedUri, CURRENT_API_VERSION);
-        Intent intent = ContactSaveService.createSetRingtone(
-                mContext, mLookupUri, mCustomRingtone);
-        mContext.startService(intent);
-    }
-
-    //
-    // Options menu
-    //
-
-    private void setStateForPhoneMenuItems(Contact contact) {
-        if (contact != null) {
-            mSendToVoicemailState = contact.isSendToVoicemail();
-            mCustomRingtone = contact.getCustomRingtone();
-            mArePhoneOptionsChangable = !contact.isDirectoryEntry()
-                    && PhoneCapabilityTester.isPhone(mContext);
-        }
-    }
-
-    /**
-     * Invalidates the options menu if we are still associated with an Activity.
-     */
-    protected void invalidateOptionsMenu() {
-        final Activity activity = getActivity();
-        if (activity != null) {
-            activity.invalidateOptionsMenu();
-        }
-    }
-
-    @Override
-    public Context getContext() {
-        return getActivity();
-    }
-
-    @Override
-    public void onCreateOptionsMenu(Menu menu, final MenuInflater inflater) {
-        inflater.inflate(R.menu.edit_contact, menu);
-    }
-
-    @Override
-    public void onPrepareOptionsMenu(Menu menu) {
-        // This supports the keyboard shortcut to save changes to a contact but shouldn't be visible
-        // because the custom action bar contains the "save" button now (not the overflow menu).
-        // TODO: Find a better way to handle shortcuts, i.e. onKeyDown()?
-        final MenuItem saveMenu = menu.findItem(R.id.menu_save);
-        final MenuItem splitMenu = menu.findItem(R.id.menu_split);
-        final MenuItem joinMenu = menu.findItem(R.id.menu_join);
-        final MenuItem helpMenu = menu.findItem(R.id.menu_help);
-        final MenuItem sendToVoiceMailMenu = menu.findItem(R.id.menu_send_to_voicemail);
-        final MenuItem ringToneMenu = menu.findItem(R.id.menu_set_ringtone);
-        final MenuItem deleteMenu = menu.findItem(R.id.menu_delete);
-
-        // Set visibility of menus
-
-        // help menu depending on whether this is inserting or editing
-        if (Intent.ACTION_INSERT.equals(mAction) || mRawContactIdToDisplayAlone != -1) {
-            HelpUtils.prepareHelpMenuItem(mContext, helpMenu, R.string.help_url_people_add);
-            splitMenu.setVisible(false);
-            joinMenu.setVisible(false);
-            deleteMenu.setVisible(false);
-        } else if (Intent.ACTION_EDIT.equals(mAction)) {
-            HelpUtils.prepareHelpMenuItem(mContext, helpMenu, R.string.help_url_people_edit);
-            splitMenu.setVisible(canUnlinkRawContacts());
-            // Cannot join a user profile
-            joinMenu.setVisible(!isEditingUserProfile());
-            deleteMenu.setVisible(!mDisableDeleteMenuOption && !isEditingUserProfile());
-        } else {
-            // something else, so don't show the help menu
-            helpMenu.setVisible(false);
-        }
-
-        // Save menu is invisible when there's only one read only contact in the editor.
-        saveMenu.setVisible(!mRawContactDisplayAloneIsReadOnly);
-        if (saveMenu.isVisible()) {
-            // Since we're using a custom action layout we have to manually hook up the handler.
-            saveMenu.getActionView().setOnClickListener(new View.OnClickListener() {
-                @Override
-                public void onClick(View v) {
-                    onOptionsItemSelected(saveMenu);
-                }
-            });
-        }
-
-        if (mRawContactIdToDisplayAlone != -1 || mIsUserProfile) {
-            sendToVoiceMailMenu.setVisible(false);
-            ringToneMenu.setVisible(false);
-        } else {
-            // Hide telephony-related settings (ringtone, send to voicemail)
-            // if we don't have a telephone or are editing a new contact.
-            sendToVoiceMailMenu.setChecked(mSendToVoicemailState);
-            sendToVoiceMailMenu.setVisible(mArePhoneOptionsChangable);
-            ringToneMenu.setVisible(mArePhoneOptionsChangable);
-        }
-
-        int size = menu.size();
-        for (int i = 0; i < size; i++) {
-            menu.getItem(i).setEnabled(mEnabled);
-        }
-    }
-
-    @Override
-    public boolean onOptionsItemSelected(MenuItem item) {
-        final Activity activity = getActivity();
-        if (activity == null || activity.isFinishing() || activity.isDestroyed()) {
-            // If we no longer are attached to a running activity want to
-            // drain this event.
-            return true;
-        }
-
-        switch (item.getItemId()) {
-            case R.id.menu_save:
-                return save(SaveMode.CLOSE);
-            case R.id.menu_delete:
-                if (mListener != null) mListener.onDeleteRequested(mLookupUri);
-                return true;
-            case R.id.menu_split:
-                return doSplitContactAction();
-            case R.id.menu_join:
-                return doJoinContactAction();
-            case R.id.menu_set_ringtone:
-                doPickRingtone();
-                return true;
-            case R.id.menu_send_to_voicemail:
-                // Update state and save
-                mSendToVoicemailState = !mSendToVoicemailState;
-                item.setChecked(mSendToVoicemailState);
-                final Intent intent = ContactSaveService.createSetSendToVoicemail(
-                        mContext, mLookupUri, mSendToVoicemailState);
-                mContext.startService(intent);
-                return true;
-        }
-
-        return false;
-    }
-
-    @Override
-    public boolean revert() {
-        if (mState.isEmpty() || !hasPendingChanges()) {
-            onCancelEditConfirmed();
-        } else {
-            CancelEditDialogFragment.show(this);
-        }
-        return true;
-    }
-
-    @Override
-    public void onCancelEditConfirmed() {
-        // When this Fragment is closed we don't want it to auto-save
-        mStatus = Status.CLOSING;
-        if (mListener != null) {
-            mListener.onReverted();
-        }
-    }
-
-    @Override
-    public void onSplitContactConfirmed(boolean hasPendingChanges) {
-        if (mState.isEmpty()) {
-            // This may happen when this Fragment is recreated by the system during users
-            // confirming the split action (and thus this method is called just before onCreate()),
-            // for example.
-            Log.e(TAG, "mState became null during the user's confirming split action. " +
-                    "Cannot perform the save action.");
-            return;
-        }
-
-        if (!hasPendingChanges && mHasNewContact) {
-            // If the user didn't add anything new, we don't want to split out the newly created
-            // raw contact into a name-only contact so remove them.
-            final Iterator<RawContactDelta> iterator = mState.iterator();
-            while (iterator.hasNext()) {
-                final RawContactDelta rawContactDelta = iterator.next();
-                if (rawContactDelta.getRawContactId() < 0) {
-                    iterator.remove();
-                }
-            }
-        }
-        mState.markRawContactsForSplitting();
-        save(SaveMode.SPLIT);
-    }
-
-    private boolean doSplitContactAction() {
-        if (!hasValidState()) return false;
-
-        SplitContactConfirmationDialogFragment.show(this, hasPendingChanges());
-        return true;
-    }
-
-    private boolean doJoinContactAction() {
-        if (!hasValidState() || mLookupUri == null) {
-            return false;
-        }
-
-        // If we just started creating a new contact and haven't added any data, it's too
-        // early to do a join
-        if (mState.size() == 1 && mState.get(0).isContactInsert()
-                && !hasPendingChanges()) {
-            Toast.makeText(mContext, R.string.toast_join_with_empty_contact,
-                    Toast.LENGTH_LONG).show();
-            return true;
-        }
-
-        showJoinAggregateActivity(mLookupUri);
-        return true;
-    }
-
-    @Override
-    public void onJoinContactConfirmed(long joinContactId) {
-        doSaveAction(SaveMode.JOIN, joinContactId);
-    }
-
-    private void doPickRingtone() {
-        final Intent intent = new Intent(RingtoneManager.ACTION_RINGTONE_PICKER);
-        // Allow user to pick 'Default'
-        intent.putExtra(RingtoneManager.EXTRA_RINGTONE_SHOW_DEFAULT, true);
-        // Show only ringtones
-        intent.putExtra(RingtoneManager.EXTRA_RINGTONE_TYPE, RingtoneManager.TYPE_RINGTONE);
-        // Allow the user to pick a silent ringtone
-        intent.putExtra(RingtoneManager.EXTRA_RINGTONE_SHOW_SILENT, true);
-
-        final Uri ringtoneUri = EditorUiUtils.getRingtoneUriFromString(mCustomRingtone,
-                CURRENT_API_VERSION);
-
-        // Put checkmark next to the current ringtone for this contact
-        intent.putExtra(RingtoneManager.EXTRA_RINGTONE_EXISTING_URI, ringtoneUri);
-
-        // Launch!
-        try {
-            startActivityForResult(intent, REQUEST_CODE_PICK_RINGTONE);
-        } catch (ActivityNotFoundException ex) {
-            Toast.makeText(mContext, R.string.missing_app, Toast.LENGTH_SHORT).show();
-        }
-    }
-
-    @Override
-    public boolean save(int saveMode) {
-        if (!hasValidState() || mStatus != Status.EDITING) {
-            return false;
-        }
-
-        // If we are about to close the editor - there is no need to refresh the data
-        if (saveMode == SaveMode.CLOSE || saveMode == SaveMode.COMPACT
-                || saveMode == SaveMode.SPLIT) {
-            getLoaderManager().destroyLoader(LOADER_CONTACT);
-        }
-
-        mStatus = Status.SAVING;
-
-        if (!hasPendingChanges()) {
-            if (mLookupUri == null && saveMode == SaveMode.RELOAD) {
-                // We don't have anything to save and there isn't even an existing contact yet.
-                // Nothing to do, simply go back to editing mode
-                mStatus = Status.EDITING;
-                return true;
-            }
-            onSaveCompleted(/* hadChanges =*/ false, saveMode,
-                    /* saveSucceeded =*/ mLookupUri != null, mLookupUri, /* joinContactId =*/ null);
-            return true;
-        }
-
-        setEnabled(false);
-
-        return doSaveAction(saveMode, /* joinContactId */ null);
-    }
-
-    /**
-     * Persist the accumulated editor deltas.
-     *
-     * @param joinContactId the raw contact ID to join the contact being saved to after the save,
-     *         may be null.
-     */
-    abstract protected boolean doSaveAction(int saveMode, Long joinContactId);
-
-    protected boolean startSaveService(Context context, Intent intent, int saveMode) {
-        final boolean result = ContactSaveService.startService(
-                context, intent, saveMode);
-        if (!result) {
-            onCancelEditConfirmed();
-        }
-        return result;
-    }
-
-    //
-    // State accessor methods
-    //
-
-    /**
-     * Check if our internal {@link #mState} is valid, usually checked before
-     * performing user actions.
-     */
-    protected boolean hasValidState() {
-        return mState.size() > 0;
-    }
-
-    protected boolean isEditingUserProfile() {
-        return mNewLocalProfile || mIsUserProfile;
-    }
-
-    /**
-     * Whether the contact being edited spans multiple raw contacts.
-     * The may also span multiple accounts.
-     */
-    public boolean isEditingMultipleRawContacts() {
-        return mState.size() > 1;
-    }
-
-    /**
-     * Whether the contact being edited is composed of a single read-only raw contact
-     * aggregated with a newly created writable raw contact.
-     */
-    protected boolean isEditingReadOnlyRawContactWithNewContact() {
-        return mHasNewContact && mState.size() == 2;
-    }
-
-    /**
-     * Return true if there are any edits to the current contact which need to
-     * be saved.
-     */
-    protected boolean hasPendingRawContactChanges(Set<String> excludedMimeTypes) {
-        final AccountTypeManager accountTypes = AccountTypeManager.getInstance(mContext);
-        return RawContactModifier.hasChanges(mState, accountTypes, excludedMimeTypes);
-    }
-
-    /**
-     * We allow unlinking only if there is more than one raw contact, it is not a user-profile,
-     * and unlinking won't result in an empty contact.  For the empty contact case, we only guard
-     * against this when there is a single read-only contact in the aggregate.  If the user
-     * has joined >1 read-only contacts together, we allow them to unlink it, even if they have
-     * never added their own information and unlinking will create a name only contact.
-     */
-    protected boolean canUnlinkRawContacts() {
-        return isEditingMultipleRawContacts()
-                && !isEditingUserProfile()
-                && !isEditingReadOnlyRawContactWithNewContact();
-    }
-
-    /**
-     * Determines if changes were made in the editor that need to be saved, while taking into
-     * account that name changes are not real for read-only contacts.
-     * See go/editing-read-only-contacts
-     */
-    protected boolean hasPendingChanges() {
-        if (mReadOnlyNameEditorView != null && mReadOnlyDisplayName != null) {
-            // We created a new raw contact delta with a default display name.
-            // We must test for pending changes while ignoring the default display name.
-            final String displayName = mReadOnlyNameEditorView.getDisplayName();
-            if (mReadOnlyDisplayName.equals(displayName)) {
-                final Set<String> excludedMimeTypes = new HashSet<>();
-                excludedMimeTypes.add(StructuredName.CONTENT_ITEM_TYPE);
-                return hasPendingRawContactChanges(excludedMimeTypes);
-            }
-            return true;
-        }
-        return hasPendingRawContactChanges(/* excludedMimeTypes =*/ null);
-    }
-
-    /**
-     * Whether editor inputs and the options menu should be enabled.
-     */
-    protected boolean isEnabled() {
-        return mEnabled;
-    }
-
-    /**
-     * Returns the palette extra that was passed in.
-     */
-    protected MaterialColorMapUtils.MaterialPalette getMaterialPalette() {
-        return mMaterialPalette;
-    }
-
-    //
-    // Account creation
-    //
-
-    private void selectAccountAndCreateContact() {
-        // If this is a local profile, then skip the logic about showing the accounts changed
-        // activity and create a phone-local contact.
-        if (mNewLocalProfile) {
-            createContact(null);
-            return;
-        }
-
-        // If there is no default account or the accounts have changed such that we need to
-        // prompt the user again, then launch the account prompt.
-        if (mEditorUtils.shouldShowAccountChangedNotification()) {
-            Intent intent = new Intent(mContext, ContactEditorAccountsChangedActivity.class);
-            // Prevent a second instance from being started on rotates
-            intent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
-            mStatus = Status.SUB_ACTIVITY;
-            startActivityForResult(intent, REQUEST_CODE_ACCOUNTS_CHANGED);
-        } else {
-            // Otherwise, there should be a default account. Then either create a local contact
-            // (if default account is null) or create a contact with the specified account.
-            AccountWithDataSet defaultAccount = mEditorUtils.getDefaultAccount();
-            createContact(defaultAccount);
-        }
-    }
-
-    /**
-     * Create a contact by automatically selecting the first account. If there's no available
-     * account, a device-local contact should be created.
-     */
-    protected void createContact() {
-        final List<AccountWithDataSet> accounts =
-                AccountTypeManager.getInstance(mContext).getAccounts(true);
-        // No Accounts available. Create a phone-local contact.
-        if (accounts.isEmpty()) {
-            createContact(null);
-            return;
-        }
-
-        // We have an account switcher in "create-account" screen, so don't need to ask a user to
-        // select an account here.
-        createContact(accounts.get(0));
-    }
-
-    /**
-     * Shows account creation screen associated with a given account.
-     *
-     * @param account may be null to signal a device-local contact should be created.
-     */
-    protected void createContact(AccountWithDataSet account) {
-        final AccountTypeManager accountTypes = AccountTypeManager.getInstance(mContext);
-        final AccountType accountType = accountTypes.getAccountTypeForAccount(account);
-
-        if (accountType.getCreateContactActivityClassName() != null) {
-            if (mListener != null) {
-                mListener.onCustomCreateContactActivityRequested(account, mIntentExtras);
-            }
-        } else {
-            setStateForNewContact(account, accountType, isEditingUserProfile());
-        }
-    }
-
-    //
-    // Data binding
-    //
-
-    private void setState(Contact contact) {
-        // If we have already loaded data, we do not want to change it here to not confuse the user
-        if (!mState.isEmpty()) {
-            Log.v(TAG, "Ignoring background change. This will have to be rebased later");
-            return;
-        }
-
-        // Prune raw contacts besides the one we want to edit
-        if (mRawContactIdToDisplayAlone > 0) {
-            final ImmutableList.Builder<RawContact> rawContactsBuilder =
-                    new ImmutableList.Builder<>();
-            for (RawContact rawContact : contact.getRawContacts()) {
-                if (rawContact.getId() == mRawContactIdToDisplayAlone) {
-                    rawContactsBuilder.add(rawContact);
-                    break;
-                }
-            }
-            mRawContacts = rawContactsBuilder.build();
-            Log.v(TAG, "Raw contact deltas trimmed from " + contact.getRawContacts().size() +
-                    " to " + mRawContacts.size());
-        } else {
-            mRawContacts = contact.getRawContacts();
-        }
-
-        // See if this edit operation needs to be redirected to a custom editor
-        if (mRawContacts.size() == 1) {
-            RawContact rawContact = mRawContacts.get(0);
-            String type = rawContact.getAccountTypeString();
-            String dataSet = rawContact.getDataSet();
-            AccountType accountType = rawContact.getAccountType(mContext);
-            if (accountType.getEditContactActivityClassName() != null &&
-                    !accountType.areContactsWritable()) {
-                if (mListener != null) {
-                    String name = rawContact.getAccountName();
-                    long rawContactId = rawContact.getId();
-                    mListener.onCustomEditContactActivityRequested(
-                            new AccountWithDataSet(name, type, dataSet),
-                            ContentUris.withAppendedId(RawContacts.CONTENT_URI, rawContactId),
-                            mIntentExtras, true);
-                }
-                return;
-            }
-        }
-
-        String readOnlyDisplayName = null;
-        // Check for writable raw contacts.  If there are none, then we need to create one so user
-        // can edit.  For the user profile case, there is already an editable contact.
-        if (!contact.isUserProfile() && !contact.isWritableContact(mContext)) {
-            mHasNewContact = true;
-
-            // This is potentially an asynchronous call and will add deltas to list.
-            selectAccountAndCreateContact();
-
-            readOnlyDisplayName = contact.getDisplayName();
-        } else {
-            mHasNewContact = false;
-        }
-
-        // This also adds deltas to list.  If readOnlyDisplayName is null at this point it is
-        // simply ignored later on by the editor.
-        setStateForExistingContact(readOnlyDisplayName, contact.isUserProfile(), mRawContacts);
-    }
-
-    /**
-     * Prepare {@link #mState} for a newly created phone-local contact.
-     */
-    private void setStateForNewContact(AccountWithDataSet account, AccountType accountType,
-            boolean isUserProfile) {
-        setStateForNewContact(account, accountType, /* oldState =*/ null,
-                /* oldAccountType =*/ null, isUserProfile);
-    }
-
-    /**
-     * Prepare {@link #mState} for a newly created phone-local contact, migrating the state
-     * specified by oldState and oldAccountType.
-     */
-    protected void setStateForNewContact(AccountWithDataSet account, AccountType accountType,
-            RawContactDelta oldState, AccountType oldAccountType, boolean isUserProfile) {
-        mStatus = Status.EDITING;
-        mState.add(createNewRawContactDelta(account, accountType, oldState, oldAccountType));
-        mIsUserProfile = isUserProfile;
-        mNewContactDataReady = true;
-        bindEditors();
-    }
-
-    /**
-     * Returns a {@link RawContactDelta} for a new contact suitable for addition into
-     * {@link #mState}.
-     *
-     * If oldState and oldAccountType are specified, the state specified by those parameters
-     * is migrated to the result {@link RawContactDelta}.
-     */
-    private RawContactDelta createNewRawContactDelta(AccountWithDataSet account,
-            AccountType accountType, RawContactDelta oldState, AccountType oldAccountType) {
-        final RawContact rawContact = new RawContact();
-        if (account != null) {
-            rawContact.setAccount(account);
-        } else {
-            rawContact.setAccountToLocal();
-        }
-
-        final RawContactDelta result = new RawContactDelta(
-                ValuesDelta.fromAfter(rawContact.getValues()));
-        if (oldState == null) {
-            // Parse any values from incoming intent
-            RawContactModifier.parseExtras(mContext, accountType, result, mIntentExtras);
-        } else {
-            RawContactModifier.migrateStateForNewContact(
-                    mContext, oldState, result, oldAccountType, accountType);
-        }
-
-        // Ensure we have some default fields (if the account type does not support a field,
-        // ensureKind will not add it, so it is safe to add e.g. Event)
-        RawContactModifier.ensureKindExists(result, accountType, Phone.CONTENT_ITEM_TYPE);
-        RawContactModifier.ensureKindExists(result, accountType, Email.CONTENT_ITEM_TYPE);
-        RawContactModifier.ensureKindExists(result, accountType, Organization.CONTENT_ITEM_TYPE);
-        RawContactModifier.ensureKindExists(result, accountType, Event.CONTENT_ITEM_TYPE);
-        RawContactModifier.ensureKindExists(result, accountType,
-                StructuredPostal.CONTENT_ITEM_TYPE);
-
-        // Set the correct URI for saving the contact as a profile
-        if (mNewLocalProfile) {
-            result.setProfileQueryUri();
-        }
-
-        return result;
-    }
-
-    /**
-     * Prepare {@link #mState} for an existing contact.
-     */
-    protected void setStateForExistingContact(String readOnlyDisplayName, boolean isUserProfile,
-            ImmutableList<RawContact> rawContacts) {
-        setEnabled(true);
-        mReadOnlyDisplayName = readOnlyDisplayName;
-
-        mState.addAll(rawContacts.iterator());
-        setIntentExtras(mIntentExtras);
-        mIntentExtras = null;
-
-        // For user profile, change the contacts query URI
-        mIsUserProfile = isUserProfile;
-        boolean localProfileExists = false;
-
-        if (mIsUserProfile) {
-            for (RawContactDelta rawContactDelta : mState) {
-                // For profile contacts, we need a different query URI
-                rawContactDelta.setProfileQueryUri();
-                // Try to find a local profile contact
-                if (rawContactDelta.getValues().getAsString(RawContacts.ACCOUNT_TYPE) == null) {
-                    localProfileExists = true;
-                }
-            }
-            // Editor should always present a local profile for editing
-            // TODO(wjang): Need to figure out when this case comes up.  We can't do this if we're
-            // going to prune all but the one raw contact that we're trying to display by itself.
-            if (!localProfileExists && mRawContactIdToDisplayAlone <= 0) {
-                mState.add(createLocalRawContactDelta());
-            }
-        }
-        mExistingContactDataReady = true;
-        bindEditors();
-    }
-
-    /**
-     * Returns a {@link RawContactDelta} for a local contact suitable for addition into
-     * {@link #mState}.
-     */
-    private static RawContactDelta createLocalRawContactDelta() {
-        final RawContact rawContact = new RawContact();
-        rawContact.setAccountToLocal();
-
-        final RawContactDelta result = new RawContactDelta(
-                ValuesDelta.fromAfter(rawContact.getValues()));
-        result.setProfileQueryUri();
-
-        return result;
-    }
-
-    /**
-     * Sets group metadata on all bound editors.
-     */
-    abstract protected void setGroupMetaData();
-
-    /**
-     * Bind editors using {@link #mState} and other members initialized from the loaded (or new)
-     * Contact.
-     */
-    abstract protected void bindEditors();
-
-    /**
-     * Set the enabled state of editors.
-     */
-    private void setEnabled(boolean enabled) {
-        if (mEnabled != enabled) {
-            mEnabled = enabled;
-
-            // Enable/disable editors
-            if (mContent != null) {
-                int count = mContent.getChildCount();
-                for (int i = 0; i < count; i++) {
-                    mContent.getChildAt(i).setEnabled(enabled);
-                }
-            }
-
-            // Enable/disable aggregation suggestion vies
-            if (mAggregationSuggestionView != null) {
-                LinearLayout itemList = (LinearLayout) mAggregationSuggestionView.findViewById(
-                        R.id.aggregation_suggestions);
-                int count = itemList.getChildCount();
-                for (int i = 0; i < count; i++) {
-                    itemList.getChildAt(i).setEnabled(enabled);
-                }
-            }
-
-            // Maybe invalidate the options menu
-            final Activity activity = getActivity();
-            if (activity != null) activity.invalidateOptionsMenu();
-        }
-    }
-
-    /**
-     * Removes a current editor ({@link #mState}) and rebinds new editor for a new account.
-     * Some of old data are reused with new restriction enforced by the new account.
-     *
-     * @param oldState Old data being edited.
-     * @param oldAccount Old account associated with oldState.
-     * @param newAccount New account to be used.
-     */
-    protected void rebindEditorsForNewContact(
-            RawContactDelta oldState, AccountWithDataSet oldAccount,
-            AccountWithDataSet newAccount) {
-        AccountTypeManager accountTypes = AccountTypeManager.getInstance(mContext);
-        AccountType oldAccountType = accountTypes.getAccountTypeForAccount(oldAccount);
-        AccountType newAccountType = accountTypes.getAccountTypeForAccount(newAccount);
-
-        if (newAccountType.getCreateContactActivityClassName() != null) {
-            Log.w(TAG, "external activity called in rebind situation");
-            if (mListener != null) {
-                mListener.onCustomCreateContactActivityRequested(newAccount, mIntentExtras);
-            }
-        } else {
-            mExistingContactDataReady = false;
-            mNewContactDataReady = false;
-            mState = new RawContactDeltaList();
-            setStateForNewContact(newAccount, newAccountType, oldState, oldAccountType,
-                    isEditingUserProfile());
-            if (mIsEdit) {
-                setStateForExistingContact(mReadOnlyDisplayName, isEditingUserProfile(),
-                        mRawContacts);
-            }
-        }
-    }
-
-    //
-    // ContactEditor
-    //
-
-    @Override
-    public void setListener(Listener listener) {
-        mListener = listener;
-    }
-
-    @Override
-    public void load(String action, Uri lookupUri, Bundle intentExtras) {
-        mAction = action;
-        mLookupUri = lookupUri;
-        mIntentExtras = intentExtras;
-
-        if (mIntentExtras != null) {
-            mAutoAddToDefaultGroup =
-                    mIntentExtras.containsKey(INTENT_EXTRA_ADD_TO_DEFAULT_DIRECTORY);
-            mNewLocalProfile =
-                    mIntentExtras.getBoolean(INTENT_EXTRA_NEW_LOCAL_PROFILE);
-            mDisableDeleteMenuOption =
-                    mIntentExtras.getBoolean(INTENT_EXTRA_DISABLE_DELETE_MENU_OPTION);
-            if (mIntentExtras.containsKey(INTENT_EXTRA_MATERIAL_PALETTE_PRIMARY_COLOR)
-                    && mIntentExtras.containsKey(INTENT_EXTRA_MATERIAL_PALETTE_SECONDARY_COLOR)) {
-                mMaterialPalette = new MaterialColorMapUtils.MaterialPalette(
-                        mIntentExtras.getInt(INTENT_EXTRA_MATERIAL_PALETTE_PRIMARY_COLOR),
-                        mIntentExtras.getInt(INTENT_EXTRA_MATERIAL_PALETTE_SECONDARY_COLOR));
-            }
-            // If the user selected a different photo, don't restore the one from the Intent
-            if (mPhotoId < 0) {
-                mPhotoId = mIntentExtras.getLong(INTENT_EXTRA_PHOTO_ID);
-            }
-            mRawContactIdToDisplayAlone = mIntentExtras.getLong(
-                    INTENT_EXTRA_RAW_CONTACT_ID_TO_DISPLAY_ALONE, -1);
-            mRawContactDisplayAloneIsReadOnly = mIntentExtras.getBoolean(
-                    INTENT_EXTRA_RAW_CONTACT_DISPLAY_ALONE_IS_READ_ONLY);
-        }
-    }
-
-    @Override
-    public void setIntentExtras(Bundle extras) {
-        if (extras == null || extras.size() == 0) {
-            return;
-        }
-
-        final AccountTypeManager accountTypes = AccountTypeManager.getInstance(mContext);
-        for (RawContactDelta state : mState) {
-            final AccountType type = state.getAccountType(accountTypes);
-            if (type.areContactsWritable()) {
-                // Apply extras to the first writable raw contact only
-                RawContactModifier.parseExtras(mContext, type, state, extras);
-                break;
-            }
-        }
-    }
-
-    @Override
-    public void onJoinCompleted(Uri uri) {
-        onSaveCompleted(false, SaveMode.RELOAD, uri != null, uri, /* joinContactId */ null);
-    }
-
-    @Override
-    public void onSaveCompleted(boolean hadChanges, int saveMode, boolean saveSucceeded,
-            Uri contactLookupUri, Long joinContactId) {
-        if (hadChanges) {
-            if (saveSucceeded) {
-                switch (saveMode) {
-                    case SaveMode.JOIN:
-                        break;
-                    case SaveMode.SPLIT:
-                        Toast.makeText(mContext, R.string.contactUnlinkedToast, Toast.LENGTH_SHORT)
-                                .show();
-                        break;
-                    default:
-                        Toast.makeText(mContext, R.string.contactSavedToast, Toast.LENGTH_SHORT)
-                                .show();
-                }
-
-            } else {
-                Toast.makeText(mContext, R.string.contactSavedErrorToast, Toast.LENGTH_LONG).show();
-            }
-        }
-        switch (saveMode) {
-            case SaveMode.CLOSE: {
-                final Intent resultIntent;
-                if (saveSucceeded && contactLookupUri != null) {
-                    final Uri lookupUri = maybeConvertToLegacyLookupUri(
-                            mContext, contactLookupUri, mLookupUri);
-                    resultIntent = ImplicitIntentsUtil.composeQuickContactIntent(mContext,
-                            lookupUri, QuickContactActivity.MODE_FULLY_EXPANDED);
-                    resultIntent.putExtra(QuickContactActivity.EXTRA_PREVIOUS_SCREEN_TYPE,
-                            ScreenType.EDITOR);
-                    resultIntent.putExtra(QuickContactActivity.EXTRA_CONTACT_EDITED, true);
-                } else {
-                    resultIntent = null;
-                }
-                // It is already saved, so prevent it from being saved again
-                mStatus = Status.CLOSING;
-                if (mListener != null) mListener.onSaveFinished(resultIntent);
-                break;
-            }
-            case SaveMode.COMPACT: {
-                // It is already saved, so prevent it from being saved again
-                mStatus = Status.CLOSING;
-                if (mListener != null) mListener.onSaveFinished(/* resultIntent= */ null);
-                break;
-            }
-            case SaveMode.JOIN:
-                if (saveSucceeded && contactLookupUri != null && joinContactId != null) {
-                    joinAggregate(joinContactId);
-                }
-                break;
-            case SaveMode.RELOAD:
-                if (saveSucceeded && contactLookupUri != null) {
-                    // If this was in INSERT, we are changing into an EDIT now.
-                    // If it already was an EDIT, we are changing to the new Uri now
-                    mState = new RawContactDeltaList();
-                    load(Intent.ACTION_EDIT, contactLookupUri, null);
-                    mStatus = Status.LOADING;
-                    getLoaderManager().restartLoader(LOADER_CONTACT, null, mContactLoaderListener);
-                }
-                break;
-
-            case SaveMode.SPLIT:
-                mStatus = Status.CLOSING;
-                if (mListener != null) {
-                    mListener.onContactSplit(contactLookupUri);
-                } else {
-                    Log.d(TAG, "No listener registered, can not call onSplitFinished");
-                }
-                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 || !isAdded()) {
-            return;
-        }
-
-        mContactIdForJoin = ContentUris.parseId(contactLookupUri);
-        final Intent intent = new Intent(mContext, ContactSelectionActivity.class);
-        intent.setAction(UiIntentActions.PICK_JOIN_CONTACT_ACTION);
-        intent.putExtra(UiIntentActions.TARGET_CONTACT_ID_EXTRA_KEY, mContactIdForJoin);
-        startActivityForResult(intent, REQUEST_CODE_JOIN);
-    }
-
-    //
-    // Aggregation PopupWindow
-    //
-
-    /**
-     * Triggers an asynchronous search for aggregation suggestions.
-     */
-    protected void acquireAggregationSuggestions(Context context,
-            long rawContactId, ValuesDelta valuesDelta) {
-        if (mAggregationSuggestionsRawContactId != rawContactId
-                && mAggregationSuggestionView != null) {
-            mAggregationSuggestionView.setVisibility(View.GONE);
-            mAggregationSuggestionView = null;
-            mAggregationSuggestionEngine.reset();
-        }
-
-        mAggregationSuggestionsRawContactId = rawContactId;
-
-        if (mAggregationSuggestionEngine == null) {
-            mAggregationSuggestionEngine = new AggregationSuggestionEngine(context);
-            mAggregationSuggestionEngine.setListener(this);
-            mAggregationSuggestionEngine.start();
-        }
-
-        mAggregationSuggestionEngine.setContactId(getContactId());
-
-        mAggregationSuggestionEngine.onNameChange(valuesDelta);
-    }
-
-    /**
-     * Returns the contact ID for the currently edited contact or 0 if the contact is new.
-     */
-    private long getContactId() {
-        for (RawContactDelta rawContact : mState) {
-            Long contactId = rawContact.getValues().getAsLong(RawContacts.CONTACT_ID);
-            if (contactId != null) {
-                return contactId;
-            }
-        }
-        return 0;
-    }
-
-    @Override
-    public void onAggregationSuggestionChange() {
-        final Activity activity = getActivity();
-        if ((activity != null && activity.isFinishing())
-                || !isVisible() ||  mState.isEmpty() || mStatus != Status.EDITING) {
-            return;
-        }
-
-        UiClosables.closeQuietly(mAggregationSuggestionPopup);
-
-        if (mAggregationSuggestionEngine.getSuggestedContactCount() == 0) {
-            return;
-        }
-
-        final View anchorView = getAggregationAnchorView(mAggregationSuggestionsRawContactId);
-        if (anchorView == null) {
-            return; // Raw contact deleted?
-        }
-        mAggregationSuggestionPopup = new ListPopupWindow(mContext, null);
-        mAggregationSuggestionPopup.setAnchorView(anchorView);
-        mAggregationSuggestionPopup.setWidth(anchorView.getWidth());
-        mAggregationSuggestionPopup.setInputMethodMode(ListPopupWindow.INPUT_METHOD_NOT_NEEDED);
-        mAggregationSuggestionPopup.setAdapter(
-                new AggregationSuggestionAdapter(
-                        getActivity(),
-                        mState.size() == 1 && mState.get(0).isContactInsert(),
-                        /* listener =*/ this,
-                        mAggregationSuggestionEngine.getSuggestions()));
-        mAggregationSuggestionPopup.setOnItemClickListener(new AdapterView.OnItemClickListener() {
-            @Override
-            public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
-                final AggregationSuggestionView suggestionView = (AggregationSuggestionView) view;
-                suggestionView.handleItemClickEvent();
-                UiClosables.closeQuietly(mAggregationSuggestionPopup);
-                mAggregationSuggestionPopup = null;
-            }
-        });
-        mAggregationSuggestionPopup.show();
-    }
-
-    /**
-     * Returns the raw contact editor view for the given rawContactId that should be used as the
-     * anchor for aggregation suggestions.
-     */
-    abstract protected View getAggregationAnchorView(long rawContactId);
-
-    /**
-     * Whether the given raw contact ID matches the one used to last load aggregation
-     * suggestions.
-     */
-    protected boolean isAggregationSuggestionRawContactId(long rawContactId) {
-        return mAggregationSuggestionsRawContactId == rawContactId;
-    }
-
-    @Override
-    public void onJoinAction(long contactId, List<Long> rawContactIdList) {
-        final long rawContactIds[] = new long[rawContactIdList.size()];
-        for (int i = 0; i < rawContactIds.length; i++) {
-            rawContactIds[i] = rawContactIdList.get(i);
-        }
-        try {
-            JoinSuggestedContactDialogFragment.show(this, rawContactIds);
-        } catch (Exception ignored) {
-            // No problem - the activity is no longer available to display the dialog
-        }
-    }
-
-    /**
-     * Joins the suggested contact (specified by the id's of constituent raw
-     * contacts), save all changes, and stay in the editor.
-     */
-    protected void doJoinSuggestedContact(long[] rawContactIds) {
-        if (!hasValidState() || mStatus != Status.EDITING) {
-            return;
-        }
-
-        mState.setJoinWithRawContacts(rawContactIds);
-        save(SaveMode.RELOAD);
-    }
-
-    @Override
-    public void onEditAction(Uri contactLookupUri) {
-        SuggestionEditConfirmationDialogFragment.show(this, contactLookupUri);
-    }
-
-    /**
-     * Abandons the currently edited contact and switches to editing the suggested
-     * one, transferring all the data there
-     */
-    protected void doEditSuggestedContact(Uri contactUri) {
-        if (mListener != null) {
-            // make sure we don't save this contact when closing down
-            mStatus = Status.CLOSING;
-            mListener.onEditOtherContactRequested(
-                    contactUri, mState.get(0).getContentValues());
-        }
-    }
-
-    //
-    // Join Activity
-    //
-
-    /**
-     * Performs aggregation with the contact selected by the user from suggestions or A-Z list.
-     */
-    abstract protected void joinAggregate(long contactId);
-
-    //
-    // Utility methods
-    //
-
-    /**
-     * Returns a legacy version of the given contactLookupUri if a legacy Uri was originally
-     * passed to the contact editor.
-     *
-     * @param contactLookupUri The Uri to possibly convert to legacy format.
-     * @param requestLookupUri The lookup Uri originally passed to the contact editor
-     *                         (via Intent data), may be null.
-     */
-    protected static Uri maybeConvertToLegacyLookupUri(Context context, Uri contactLookupUri,
-            Uri requestLookupUri) {
-        final String legacyAuthority = "contacts";
-        final String requestAuthority = requestLookupUri == null
-                ? null : requestLookupUri.getAuthority();
-        if (legacyAuthority.equals(requestAuthority)) {
-            // Build a legacy Uri if that is what was requested by caller
-            final long contactId = ContentUris.parseId(Contacts.lookupContact(
-                    context.getContentResolver(), contactLookupUri));
-            final Uri legacyContentUri = Uri.parse("content://contacts/people");
-            return ContentUris.withAppendedId(legacyContentUri, contactId);
-        }
-        // Otherwise pass back a lookup-style Uri
-        return contactLookupUri;
-    }
-}
diff --git a/src/com/android/contacts/editor/ContactEditorUtils.java b/src/com/android/contacts/editor/ContactEditorUtils.java
index 1b0da05..ced4868 100644
--- a/src/com/android/contacts/editor/ContactEditorUtils.java
+++ b/src/com/android/contacts/editor/ContactEditorUtils.java
@@ -19,9 +19,12 @@
 import android.accounts.Account;
 import android.accounts.AccountManager;
 import android.app.Activity;
+import android.content.ContentUris;
 import android.content.Context;
 import android.content.Intent;
 import android.content.SharedPreferences;
+import android.net.Uri;
+import android.provider.ContactsContract;
 import android.text.TextUtils;
 import android.util.Log;
 
@@ -78,6 +81,30 @@
         return sInstance;
     }
 
+    /**
+     * Returns a legacy version of the given contactLookupUri if a legacy Uri was originally
+     * passed to the contact editor.
+     *
+     * @param contactLookupUri The Uri to possibly convert to legacy format.
+     * @param requestLookupUri The lookup Uri originally passed to the contact editor
+     *                         (via Intent data), may be null.
+     */
+    static Uri maybeConvertToLegacyLookupUri(Context context, Uri contactLookupUri,
+            Uri requestLookupUri) {
+        final String legacyAuthority = "contacts";
+        final String requestAuthority = requestLookupUri == null
+                ? null : requestLookupUri.getAuthority();
+        if (legacyAuthority.equals(requestAuthority)) {
+            // Build a legacy Uri if that is what was requested by caller
+            final long contactId = ContentUris.parseId(ContactsContract.Contacts.lookupContact(
+                    context.getContentResolver(), contactLookupUri));
+            final Uri legacyContentUri = Uri.parse("content://contacts/people");
+            return ContentUris.withAppendedId(legacyContentUri, contactId);
+        }
+        // Otherwise pass back a lookup-style Uri
+        return contactLookupUri;
+    }
+
     void cleanupForTest() {
         mPrefs.edit().remove(mDefaultAccountKey).remove(KEY_KNOWN_ACCOUNTS)
                 .remove(mAnythingSavedKey).apply();
diff --git a/src/com/android/contacts/editor/EditorIntents.java b/src/com/android/contacts/editor/EditorIntents.java
index 74120e6..af6ef77 100644
--- a/src/com/android/contacts/editor/EditorIntents.java
+++ b/src/com/android/contacts/editor/EditorIntents.java
@@ -69,7 +69,7 @@
         final Intent intent = new Intent(Intent.ACTION_INSERT, Contacts.CONTENT_URI,
                 context, CompactContactEditorActivity.class);
         intent.putExtra(
-                ContactEditorBaseFragment.INTENT_EXTRA_NEW_LOCAL_PROFILE, isNewLocalProfile);
+                CompactContactEditorFragment.INTENT_EXTRA_NEW_LOCAL_PROFILE, isNewLocalProfile);
         if (rawContactDeltaList != null || displayName != null || phoneticName != null) {
             putRawContactDeltaValues(intent, rawContactDeltaList, displayName, phoneticName);
         }
@@ -86,7 +86,7 @@
                 CompactContactEditorActivity.class);
         intent.setFlags(Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS
                 | Intent.FLAG_ACTIVITY_FORWARD_RESULT);
-        intent.putExtra(ContactEditorBaseFragment.INTENT_EXTRA_ADD_TO_DEFAULT_DIRECTORY, "");
+        intent.putExtra(CompactContactEditorFragment.INTENT_EXTRA_ADD_TO_DEFAULT_DIRECTORY, "");
 
         // Pass on all the data that has been entered so far
         if (contentValues != null && contentValues.size() != 0) {
@@ -102,26 +102,28 @@
             Uri contactLookupUri, long rawContactId, boolean isReadOnly) {
         final Intent intent = new Intent(Intent.ACTION_EDIT, contactLookupUri, context,
                 CompactContactEditorActivity.class);
-        intent.putExtra(ContactEditorBaseFragment.INTENT_EXTRA_RAW_CONTACT_ID_TO_DISPLAY_ALONE,
+        intent.putExtra(CompactContactEditorFragment.INTENT_EXTRA_RAW_CONTACT_ID_TO_DISPLAY_ALONE,
                 rawContactId);
         intent.putExtra(
-                ContactEditorBaseFragment.INTENT_EXTRA_RAW_CONTACT_DISPLAY_ALONE_IS_READ_ONLY,
+                CompactContactEditorFragment.INTENT_EXTRA_RAW_CONTACT_DISPLAY_ALONE_IS_READ_ONLY,
                 isReadOnly);
         return intent;
     }
 
     private static void putMaterialPalette(Intent intent, MaterialPalette materialPalette) {
         if (materialPalette != null) {
-            intent.putExtra(ContactEditorBaseFragment.INTENT_EXTRA_MATERIAL_PALETTE_PRIMARY_COLOR,
+            intent.putExtra(
+                    CompactContactEditorFragment.INTENT_EXTRA_MATERIAL_PALETTE_PRIMARY_COLOR,
                     materialPalette.mPrimaryColor);
-            intent.putExtra(ContactEditorBaseFragment.INTENT_EXTRA_MATERIAL_PALETTE_SECONDARY_COLOR,
+            intent.putExtra(
+                    CompactContactEditorFragment.INTENT_EXTRA_MATERIAL_PALETTE_SECONDARY_COLOR,
                     materialPalette.mSecondaryColor);
         }
     }
 
     private static void putPhotoId(Intent intent, long photoId) {
         if (photoId >= 0) {
-            intent.putExtra(ContactEditorBaseFragment.INTENT_EXTRA_PHOTO_ID, photoId);
+            intent.putExtra(CompactContactEditorFragment.INTENT_EXTRA_PHOTO_ID, photoId);
         }
     }
 
diff --git a/src/com/android/contacts/editor/EditorUiUtils.java b/src/com/android/contacts/editor/EditorUiUtils.java
index 8772cbb..aa35636 100644
--- a/src/com/android/contacts/editor/EditorUiUtils.java
+++ b/src/com/android/contacts/editor/EditorUiUtils.java
@@ -291,4 +291,5 @@
                 bitmap, size, size, /* filter =*/ false);
         return ContactPhotoUtils.compressBitmap(bitmapScaled);
     }
+
 }
diff --git a/src/com/android/contacts/editor/JoinContactConfirmationDialogFragment.java b/src/com/android/contacts/editor/JoinContactConfirmationDialogFragment.java
index 55a066e..dab7085 100644
--- a/src/com/android/contacts/editor/JoinContactConfirmationDialogFragment.java
+++ b/src/com/android/contacts/editor/JoinContactConfirmationDialogFragment.java
@@ -48,7 +48,7 @@
     /**
      * @param joinContactId The raw contact ID of the contact to join to after confirmation.
      */
-    public static void show(ContactEditorBaseFragment fragment, long joinContactId) {
+    public static void show(CompactContactEditorFragment fragment, long joinContactId) {
         final Bundle args = new Bundle();
         args.putLong(ARG_JOIN_CONTACT_ID, joinContactId);
 
diff --git a/src/com/android/contacts/editor/JoinSuggestedContactDialogFragment.java b/src/com/android/contacts/editor/JoinSuggestedContactDialogFragment.java
index 4d35332..a057a5b 100644
--- a/src/com/android/contacts/editor/JoinSuggestedContactDialogFragment.java
+++ b/src/com/android/contacts/editor/JoinSuggestedContactDialogFragment.java
@@ -28,7 +28,7 @@
 
     private static final String ARG_RAW_CONTACT_IDS = "rawContactIds";
 
-    public static void show(ContactEditorBaseFragment fragment, long[] rawContactIds) {
+    public static void show(CompactContactEditorFragment fragment, long[] rawContactIds) {
         final Bundle args = new Bundle();
         args.putLongArray(ARG_RAW_CONTACT_IDS, rawContactIds);
 
@@ -47,8 +47,8 @@
                         new DialogInterface.OnClickListener() {
                             @Override
                             public void onClick(DialogInterface dialog, int whichButton) {
-                                ContactEditorBaseFragment targetFragment =
-                                        (ContactEditorBaseFragment) getTargetFragment();
+                                CompactContactEditorFragment targetFragment =
+                                        (CompactContactEditorFragment) getTargetFragment();
                                 long rawContactIds[] =
                                         getArguments().getLongArray(ARG_RAW_CONTACT_IDS);
                                 targetFragment.doJoinSuggestedContact(rawContactIds);
@@ -58,4 +58,4 @@
                 .setNegativeButton(android.R.string.no, null)
                 .create();
     }
-}
\ No newline at end of file
+}
diff --git a/src/com/android/contacts/editor/SplitContactConfirmationDialogFragment.java b/src/com/android/contacts/editor/SplitContactConfirmationDialogFragment.java
index f3d0ef4..41d45a1 100644
--- a/src/com/android/contacts/editor/SplitContactConfirmationDialogFragment.java
+++ b/src/com/android/contacts/editor/SplitContactConfirmationDialogFragment.java
@@ -49,7 +49,7 @@
         void onSplitContactConfirmed(boolean hasPendingChanges);
     }
 
-    public static void show(ContactEditorBaseFragment fragment, boolean hasPendingChanges) {
+    public static void show(CompactContactEditorFragment fragment, boolean hasPendingChanges) {
         final Bundle args = new Bundle();
         args.putBoolean(ARG_HAS_PENDING_CHANGES, hasPendingChanges);
 
diff --git a/src/com/android/contacts/editor/SuggestionEditConfirmationDialogFragment.java b/src/com/android/contacts/editor/SuggestionEditConfirmationDialogFragment.java
index c13d5ea..31437b4 100644
--- a/src/com/android/contacts/editor/SuggestionEditConfirmationDialogFragment.java
+++ b/src/com/android/contacts/editor/SuggestionEditConfirmationDialogFragment.java
@@ -29,7 +29,7 @@
 
     private static final String ARG_CONTACT_URI = "contactUri";
 
-    public static void show(ContactEditorBaseFragment fragment, Uri contactUri) {
+    public static void show(CompactContactEditorFragment fragment, Uri contactUri) {
         final Bundle args = new Bundle();
         args.putParcelable(ARG_CONTACT_URI, contactUri);
 
@@ -49,8 +49,8 @@
                         new DialogInterface.OnClickListener() {
                             @Override
                             public void onClick(DialogInterface dialog, int whichButton) {
-                                final ContactEditorBaseFragment targetFragment =
-                                        (ContactEditorBaseFragment) getTargetFragment();
+                                final CompactContactEditorFragment targetFragment =
+                                        (CompactContactEditorFragment) getTargetFragment();
                                 final Uri contactUri =
                                         getArguments().getParcelable(ARG_CONTACT_URI);
                                 targetFragment.doEditSuggestedContact(contactUri);
diff --git a/src/com/android/contacts/quickcontact/QuickContactActivity.java b/src/com/android/contacts/quickcontact/QuickContactActivity.java
index 047f37f..f4e75e1 100644
--- a/src/com/android/contacts/quickcontact/QuickContactActivity.java
+++ b/src/com/android/contacts/quickcontact/QuickContactActivity.java
@@ -154,7 +154,7 @@
 import com.android.contacts.detail.ContactDisplayUtils;
 import com.android.contacts.editor.AggregationSuggestionEngine;
 import com.android.contacts.editor.AggregationSuggestionEngine.Suggestion;
-import com.android.contacts.editor.ContactEditorBaseFragment;
+import com.android.contacts.editor.CompactContactEditorFragment;
 import com.android.contacts.editor.EditorIntents;
 import com.android.contacts.interactions.CalendarInteractionsLoader;
 import com.android.contacts.interactions.CallLogInteractionsLoader;
@@ -2951,8 +2951,8 @@
                     intent.putExtra(Intents.Insert.DATA, values);
 
                     // If the contact can only export to the same account, add it to the intent.
-                    // Otherwise the ContactEditorBaseFragment will show a dialog for selecting an
-                    // account.
+                    // Otherwise the CompactContactEditorFragment will show a dialog for selecting
+                    // an account.
                     if (mContactData.getDirectoryExportSupport() ==
                             Directory.EXPORT_SUPPORT_SAME_ACCOUNT_ONLY) {
                         intent.putExtra(Intents.Insert.EXTRA_ACCOUNT,
@@ -2965,7 +2965,7 @@
                     // Add this flag to disable the delete menu option on directory contact joins
                     // with local contacts. The delete option is ambiguous when joining contacts.
                     intent.putExtra(
-                            ContactEditorBaseFragment.INTENT_EXTRA_DISABLE_DELETE_MENU_OPTION,
+                            CompactContactEditorFragment.INTENT_EXTRA_DISABLE_DELETE_MENU_OPTION,
                             true);
 
                     intent.setPackage(getPackageName());