Merge "Don't use hidden QuickContact APIs"
diff --git a/res/values-am/strings.xml b/res/values-am/strings.xml
index 5e00118..5b7a735 100644
--- a/res/values-am/strings.xml
+++ b/res/values-am/strings.xml
@@ -69,7 +69,7 @@
     <string name="readOnlyContactDeleteConfirmation" msgid="2137170726670196909">"ይህ ዕውቂያ ከብዙ መለያዎች መረጃ ይዟል።ከንባብ-ብቻ መለያዎች ውስጥ ያሉ ዕውቂያዎች ይደበቃሉ፣አይሰረዙም።"</string>
     <string name="multipleContactDeleteConfirmation" msgid="938900978442960800">"ይህን ዕውቂያ መሰረዝ ከብዙ መለያዎች ውስጥ መረጃ ይሰርዛል።"</string>
     <string name="deleteConfirmation" msgid="811706994761610640">"ይህ ማንቂያ ይሰረዛል።"</string>
-    <string name="menu_discard" msgid="6854657936970228164">"ለውጦችን አስወግድ"</string>
+    <string name="menu_discard" msgid="6854657936970228164">"ለውጦችን ጣለው"</string>
     <string name="invalidContactMessage" msgid="8215051456181842274">"ዕውቅያው የለም።"</string>
     <string name="createContactShortcutSuccessful" msgid="7874133287558150877">"የእውቂያ መግብር መነሻ ማያ ገጽ ላይ ታክሏል።"</string>
     <string name="pickerNewContactHeader" msgid="7750705279843568147">"አዲስ ዕውቂያ ፍጠር"</string>
@@ -203,7 +203,7 @@
     <string name="set_default" msgid="4417505153468300351">"ነባሪ አዘጋጅ"</string>
     <string name="clear_default" msgid="7193185801596678067">"ነባሪ አጽዳ"</string>
     <string name="toast_text_copied" msgid="5143776250008541719">"ፅሁፍ ገልብጧል"</string>
-    <string name="cancel_confirmation_dialog_message" msgid="5885724679874403115">" ለውጦችህ ይወገዱ?"</string>
+    <string name="cancel_confirmation_dialog_message" msgid="5885724679874403115">"ለውጦችዎ ይጣሉ?"</string>
     <string name="call_type_and_date" msgid="747163730039311423">"<xliff:g id="CALL_TYPE">%1$s</xliff:g> <xliff:g id="CALL_SHORT_DATE">%2$s</xliff:g>"</string>
     <string name="profile_display_name" msgid="4127389543625918771">"መገለጫዬን አዘጋጅ"</string>
     <string name="enter_contact_name" msgid="1738391320566349924">"የግለሰቡን ስም ተይብ"</string>
diff --git a/res/values-uk/strings.xml b/res/values-uk/strings.xml
index 4a9ea8c..9b159cd 100644
--- a/res/values-uk/strings.xml
+++ b/res/values-uk/strings.xml
@@ -113,7 +113,7 @@
     <string name="callBack" msgid="5498224409038809224">"Передзвонити"</string>
     <string name="callAgain" msgid="3197312117049874778">"Набрати знову"</string>
     <string name="returnCall" msgid="8171961914203617813">"Зворот. виклик"</string>
-    <string name="add_contact_dlg_message_fmt" msgid="7986472669444326576">"Дод.\" <xliff:g id="EMAIL">%s</xliff:g>\" до контактів?"</string>
+    <string name="add_contact_dlg_message_fmt" msgid="7986472669444326576">"Додати в контакти <xliff:g id="EMAIL">%s</xliff:g>?"</string>
     <string name="description_contact_photo" msgid="3387458082667894062">"фото контакту"</string>
     <string name="description_plus_button" msgid="515164827856229880">"плюс"</string>
     <string name="exporting_contact_list_progress" msgid="560522409559101193">"<xliff:g id="CURRENT_NUMBER">%s</xliff:g> із <xliff:g id="TOTAL_NUMBER">%s</xliff:g> контактів"</string>
diff --git a/src/com/android/contacts/editor/CancelEditDialogFragment.java b/src/com/android/contacts/editor/CancelEditDialogFragment.java
new file mode 100644
index 0000000..300759e
--- /dev/null
+++ b/src/com/android/contacts/editor/CancelEditDialogFragment.java
@@ -0,0 +1,71 @@
+/*
+ * 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 com.android.contacts.R;
+
+import android.app.AlertDialog;
+import android.app.Dialog;
+import android.app.DialogFragment;
+import android.content.DialogInterface;
+import android.os.Bundle;
+
+/**
+ * Asks the user whether to cancel editing the contact.
+ */
+public class CancelEditDialogFragment extends DialogFragment {
+
+    private static final String TAG = "cancelEditor";
+
+    /**
+     * Shows a {@link CancelEditDialogFragment} after setting the given Fragment as the
+     * target of the dialog.
+     */
+    public static void show(ContactEditorBaseFragment fragment) {
+        final CancelEditDialogFragment dialog = new CancelEditDialogFragment();
+        dialog.setTargetFragment(fragment, 0);
+        dialog.show(fragment.getFragmentManager(), TAG);
+    }
+
+    @Override
+    public Dialog onCreateDialog(Bundle savedInstanceState) {
+        return new AlertDialog.Builder(getActivity())
+                .setIconAttribute(android.R.attr.alertDialogIcon)
+                .setMessage(R.string.cancel_confirmation_dialog_message)
+                .setPositiveButton(android.R.string.ok, new DialogInterface.OnClickListener() {
+                            @Override
+                            public void onClick(DialogInterface dialogInterface, int which) {
+                                final Listener targetListener = (Listener) getTargetFragment();
+                                targetListener.onCancelEditConfirmed();
+                            }
+                        }
+                )
+                .setNegativeButton(android.R.string.cancel, null)
+                .create();
+    }
+
+    /**
+     * Callbacks for {@link CancelEditDialogFragment} hosts.
+     */
+    public interface Listener {
+
+        /**
+         * Invoked when the user confirms that they want to cancel editing the contact.
+         */
+        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 5361155..aee1f73 100644
--- a/src/com/android/contacts/editor/CompactContactEditorFragment.java
+++ b/src/com/android/contacts/editor/CompactContactEditorFragment.java
@@ -16,12 +16,14 @@
 
 package com.android.contacts.editor;
 
+import com.google.common.collect.ImmutableList;
+
 import com.android.contacts.R;
 import com.android.contacts.activities.ContactEditorBaseActivity.ContactEditor;
-import com.android.contacts.editor.ContactEditorBaseFragment.Listener;
+import com.android.contacts.common.model.RawContact;
+import com.android.contacts.common.model.account.AccountType;
+import com.android.contacts.common.model.account.AccountWithDataSet;
 
-import android.app.Activity;
-import android.content.Context;
 import android.content.Intent;
 import android.net.Uri;
 import android.os.Bundle;
@@ -36,28 +38,10 @@
 public class CompactContactEditorFragment extends ContactEditorBaseFragment
         implements ContactEditor {
 
-    private Context mContext;
-    private Listener mListener;
-
-    private String mAction;
-    private Uri mLookupUri;
-    private Bundle mIntentExtras;
-
-    private LinearLayout mContent;
-
-    @Override
-    public void onAttach(Activity activity) {
-        super.onAttach(activity);
-        mContext = activity;
-    }
-
-    @Override
-    public void setListener(Listener listener) {
-        mListener = listener;
-    }
-
     @Override
     public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedState) {
+        setHasOptionsMenu(true);
+
         final View view = inflater.inflate(
                 R.layout.compact_contact_editor_fragment, container, false);
         mContent = (LinearLayout) view.findViewById(R.id.editors);
@@ -65,12 +49,39 @@
     }
 
     @Override
-    public void load(String action, Uri lookupUri, Bundle intentExtras) {
-        mAction = action;
-        mLookupUri = lookupUri;
-        mIntentExtras = intentExtras;
+    public void onActivityResult(int requestCode, int resultCode, Intent data) {
+        if (mStatus == Status.SUB_ACTIVITY) {
+            mStatus = Status.EDITING;
+        }
+        super.onActivityResult(requestCode, resultCode, data);
     }
 
+    //
+    // ContactEditorBaseFragment
+    //
+
+    @Override
+    protected void bindEditorsForExistingContact(String displayName, boolean isUserProfile,
+            ImmutableList<RawContact> rawContacts) {
+    }
+
+    @Override
+    protected void bindEditorsForNewContact(AccountWithDataSet account,
+            AccountType accountType) {
+    }
+
+    @Override
+    protected void bindEditors() {
+    }
+
+    @Override
+    protected void bindGroupMetaData() {
+    }
+
+    //
+    // ContactEditor
+    //
+
     @Override
     public void setIntentExtras(Bundle extras) {
     }
diff --git a/src/com/android/contacts/editor/ContactEditorBaseFragment.java b/src/com/android/contacts/editor/ContactEditorBaseFragment.java
index 3eadaed..4b7af5b 100644
--- a/src/com/android/contacts/editor/ContactEditorBaseFragment.java
+++ b/src/com/android/contacts/editor/ContactEditorBaseFragment.java
@@ -16,28 +16,112 @@
 
 package com.android.contacts.editor;
 
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.Lists;
+
+import com.android.contacts.ContactSaveService;
+import com.android.contacts.GroupMetaDataLoader;
+import com.android.contacts.R;
+import com.android.contacts.activities.ContactEditorAccountsChangedActivity;
+import com.android.contacts.activities.ContactEditorBaseActivity;
+import com.android.contacts.activities.ContactEditorBaseActivity.ContactEditor;
+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.RawContactDeltaList;
+import com.android.contacts.common.model.RawContactModifier;
+import com.android.contacts.common.model.account.AccountType;
 import com.android.contacts.common.model.account.AccountWithDataSet;
 import com.android.contacts.quickcontact.QuickContactActivity;
+import com.android.contacts.util.HelpUtils;
+import com.android.contacts.util.PhoneCapabilityTester;
 
+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.Rect;
+import android.media.RingtoneManager;
 import android.net.Uri;
 import android.os.Bundle;
+import android.os.SystemClock;
+import android.provider.ContactsContract;
 import android.provider.ContactsContract.Contacts;
+import android.provider.ContactsContract.Intents;
 import android.provider.ContactsContract.QuickContact;
+import android.provider.ContactsContract.RawContacts;
+import android.util.Log;
+import android.view.Menu;
+import android.view.MenuInflater;
+import android.view.MenuItem;
+import android.widget.LinearLayout;
+import android.widget.Toast;
 
 import java.util.ArrayList;
+import java.util.List;
 
 /**
  * Base Fragment for contact editors.
  */
-abstract public class ContactEditorBaseFragment extends Fragment {
+abstract public class ContactEditorBaseFragment extends Fragment implements
+        ContactEditor, SplitContactConfirmationDialogFragment.Listener {
 
-    protected static final String TAG = "ContactCompactEditor";
+    protected static final String TAG = "ContactEditor";
+
+    protected static final int LOADER_DATA = 1;
+    protected static final int LOADER_GROUPS = 2;
+
+    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_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";
+
+    // 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";
+
+    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;
+
+    /**
+     * 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";
 
     /**
      * Callbacks for Activities that host contact editors Fragments.
@@ -98,6 +182,662 @@
         void onDeleteRequested(Uri contactUri);
     }
 
+    protected Context mContext;
+    protected Listener mListener;
+
+    protected LinearLayout mContent;
+
+    //
+    // Parameters passed in on {@link #load}
+    //
+    protected String mAction;
+    protected Uri mLookupUri;
+    protected Bundle mIntentExtras;
+    protected boolean mAutoAddToDefaultGroup;
+    protected boolean mDisableDeleteMenuOption;
+    protected boolean mNewLocalProfile;
+
+    //
+    // Helpers
+    //
+    protected ContactEditorUtils mEditorUtils;
+    protected RawContactDeltaComparator mComparator;
+    protected ViewIdGenerator mViewIdGenerator;
+
+    //
+    // Loaded data
+    //
+    // Used to temporarily store existing contact data during a rebind call (i.e. account switch)
+    protected ImmutableList<RawContact> mRawContacts;
+    protected Cursor mGroupMetaData;
+
+    //
+    // Contact editor state
+    //
+    protected RawContactDeltaList mState;
+    protected int mStatus;
+
+    // Whether to show the new contact blank form and if it's corresponding delta is ready.
+    protected boolean mHasNewContact;
+    protected boolean mNewContactDataReady;
+
+    // Whether it's an edit of existing contact and if it's corresponding delta is ready.
+    protected boolean mIsEdit;
+    protected boolean mExistingContactDataReady;
+
+    // Phone specific option menus
+    private boolean mSendToVoicemailState;
+    private boolean mArePhoneOptionsChangable;
+    private String mCustomRingtone;
+
+    protected boolean mIsUserProfile;
+
+    // Whether editors and options menu items are enabled
+    protected boolean mEnabled = true;
+
+    /**
+     * The contact data loader listener.
+     */
+    protected final LoaderManager.LoaderCallbacks<Contact> mDataLoaderListener =
+            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 data) {
+                    final long loaderCurrentTime = SystemClock.elapsedRealtime();
+                    Log.v(TAG, "Time needed for loading: " + (loaderCurrentTime-mLoaderStartTime));
+                    if (!data.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 = data.getLookupUri();
+                    final long setDataStartTime = SystemClock.elapsedRealtime();
+                    setData(data);
+                    final long setDataEndTime = SystemClock.elapsedRealtime();
+
+                    Log.v(TAG, "Time needed for setting UI: " + (setDataEndTime - setDataStartTime));
+                }
+
+                @Override
+                public void onLoaderReset(Loader<Contact> loader) {
+                }
+            };
+
+    /**
+     * The group meta data loader listener.
+     */
+    protected final LoaderManager.LoaderCallbacks<Cursor> mGroupLoaderListener =
+            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;
+                    bindGroupMetaData();
+                }
+
+                @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);
+
+            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);
+
+            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);
+
+            // 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);
+
+            mIsUserProfile = savedState.getBoolean(KEY_IS_USER_PROFILE);
+
+            mEnabled = savedState.getBoolean(KEY_ENABLED);
+        }
+
+        // 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.
+                // or
+                // 2) not an orientation change.  data needs to be loaded for first time.
+                getLoaderManager().initLoader(LOADER_DATA, null, mDataLoaderListener);
+            }
+        } else {
+            // Orientation change, we already have mState, it was loaded by onCreate
+            bindEditors();
+        }
+
+        // Handle initial actions only when existing state missing
+        if (savedInstanceState == null) {
+            if (Intent.ACTION_EDIT.equals(mAction)) {
+                mIsEdit = true;
+            } else if (Intent.ACTION_INSERT.equals(mAction)) {
+                mHasNewContact = true;
+                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) {
+                    // Account specified in Intent
+                    createContact(new AccountWithDataSet(account.name, account.type, dataSet));
+                } 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 (Intent.ACTION_EDIT.equals(action) || Intent.ACTION_INSERT.equals(action) ||
+                ContactEditorBaseActivity.ACTION_SAVE_COMPLETED.equals(action)) {
+            return;
+        }
+        throw new IllegalArgumentException("Unknown Action String " + action +
+                ". Only support " + Intent.ACTION_EDIT + " or " + Intent.ACTION_INSERT + " or " +
+                ContactEditorBaseActivity.ACTION_SAVE_COMPLETED);
+    }
+
+    @Override
+    public void onStart() {
+        getLoaderManager().initLoader(LOADER_GROUPS, null, mGroupLoaderListener);
+        super.onStart();
+    }
+
+    @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);
+
+        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);
+
+        // 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_IS_USER_PROFILE, mIsUserProfile);
+        outState.putBoolean(KEY_ENABLED, mEnabled);
+
+        super.onSaveInstanceState(outState);
+    }
+
+
+    @Override
+    public void onActivityResult(int requestCode, int resultCode, Intent data) {
+        switch (requestCode) {
+            case REQUEST_CODE_ACCOUNTS_CHANGED: {
+                // Bail if the account selector was not successful.
+                if (resultCode != Activity.RESULT_OK) {
+                    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);
+                    handleRingtonePicked(pickedUri);
+                }
+                break;
+            }
+        }
+    }
+
+    private void handleRingtonePicked(Uri pickedUri) {
+        if (pickedUri == null || RingtoneManager.isDefault(pickedUri)) {
+            mCustomRingtone = null;
+        } else {
+            mCustomRingtone = pickedUri.toString();
+        }
+        Intent intent = ContactSaveService.createSetRingtone(
+                mContext, mLookupUri, mCustomRingtone);
+        mContext.startService(intent);
+    }
+
+    @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 doneMenu = menu.findItem(R.id.menu_done);
+        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 discardMenu = menu.findItem(R.id.menu_discard);
+        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);
+        deleteMenu.setShowAsAction(MenuItem.SHOW_AS_ACTION_ALWAYS);
+        deleteMenu.setIcon(R.drawable.ic_delete_white_24dp);
+
+        // Set visibility of menus
+        doneMenu.setVisible(false);
+
+        // Discard menu is only available if at least one raw contact is editable
+        discardMenu.setVisible(mState != null &&
+                mState.getFirstWritableRawContact(mContext) != null);
+
+        // help menu depending on whether this is inserting or editing
+        if (Intent.ACTION_INSERT.equals(mAction)) {
+            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);
+            // Split only if more than one raw profile and not a user profile
+            splitMenu.setVisible(mState.size() > 1 && !isEditingUserProfile());
+            // Cannot join a user profile
+            joinMenu.setVisible(!isEditingUserProfile());
+            deleteMenu.setVisible(!mDisableDeleteMenuOption);
+        } else {
+            // something else, so don't show the help menu
+            helpMenu.setVisible(false);
+        }
+
+        // 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) {
+        switch (item.getItemId()) {
+            case android.R.id.home:
+            case R.id.menu_done:
+                return save(SaveMode.CLOSE);
+            case R.id.menu_discard:
+                return revert();
+            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;
+    }
+
+    private boolean revert() {
+        if (mState.isEmpty() || !hasPendingChanges()) {
+            onSplitContactConfirmed();
+        } else {
+            CancelEditDialogFragment.show(this);
+        }
+        return true;
+    }
+
+    @Override
+    public void onSplitContactConfirmed() {
+        // When this Fragment is closed we don't want it to auto-save
+        mStatus = Status.CLOSING;
+        if (mListener != null) mListener.onReverted();
+    }
+
+    private boolean doSplitContactAction() {
+        if (!hasValidState()) return false;
+
+        SplitContactConfirmationDialogFragment.show(this);
+        return true;
+    }
+
+    private boolean doJoinContactAction() {
+        if (!hasValidState()) {
+            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;
+        }
+
+        return save(SaveMode.JOIN);
+    }
+
+    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;
+        if (mCustomRingtone != null) {
+            ringtoneUri = Uri.parse(mCustomRingtone);
+        } else {
+            // Otherwise pick default ringtone Uri so that something is selected.
+            ringtoneUri = RingtoneManager.getDefaultUri(RingtoneManager.TYPE_RINGTONE);
+        }
+
+        // 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();
+        }
+    }
+
+    /**
+     * 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;
+    }
+
+    /**
+     * Return true if there are any edits to the current contact which need to
+     * be saved.
+     */
+    protected boolean hasPendingChanges() {
+        final AccountTypeManager accountTypes = AccountTypeManager.getInstance(mContext);
+        return RawContactModifier.hasChanges(mState, accountTypes);
+    }
+
+    //
+    // 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);
+            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 {
+            bindEditorsForNewContact(account, accountType);
+        }
+    }
+
+    //
+    // Data binding
+    //
+
+    private void setData(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;
+        }
+
+        // See if this edit operation needs to be redirected to a custom editor
+        mRawContacts = contact.getRawContacts();
+        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 displayName = 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();
+            displayName = contact.getDisplayName();
+        }
+
+        // This also adds deltas to list
+        // If displayName is null at this point it is simply ignored later on by the editor.
+        bindEditorsForExistingContact(displayName, contact.isUserProfile(),
+                mRawContacts);
+
+        bindMenuItemsForPhone(contact);
+    }
+
+    private void bindMenuItemsForPhone(Contact contact) {
+        if (contact != null) {
+            mSendToVoicemailState = contact.isSendToVoicemail();
+            mCustomRingtone = contact.getCustomRingtone();
+            mArePhoneOptionsChangable = !contact.isDirectoryEntry()
+                    && PhoneCapabilityTester.isPhone(mContext);
+        }
+    }
+
+    // TODO: add javadocs after these are finalized
+    abstract protected void bindEditorsForExistingContact(String displayName, boolean isUserProfile,
+            ImmutableList<RawContact> rawContacts);
+    abstract protected void bindEditorsForNewContact(AccountWithDataSet account,
+            final AccountType accountType);
+    abstract protected void bindEditors();
+    abstract protected void bindGroupMetaData();
+
+    //
+    // 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);
+        }
+    }
+
     /**
      * Returns a legacy version of the given contactLookupUri if a legacy Uri was originally
      * passed to the contact editor.
diff --git a/src/com/android/contacts/editor/ContactEditorFragment.java b/src/com/android/contacts/editor/ContactEditorFragment.java
index 0a8a967..7fb4378 100644
--- a/src/com/android/contacts/editor/ContactEditorFragment.java
+++ b/src/com/android/contacts/editor/ContactEditorFragment.java
@@ -16,41 +16,29 @@
 
 package com.android.contacts.editor;
 
-import android.accounts.Account;
 import android.app.Activity;
 import android.app.AlertDialog;
 import android.app.Dialog;
 import android.app.DialogFragment;
-import android.app.LoaderManager;
-import android.app.LoaderManager.LoaderCallbacks;
-import android.content.ActivityNotFoundException;
 import android.content.ContentUris;
 import android.content.Context;
-import android.content.CursorLoader;
 import android.content.DialogInterface;
 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.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.Photo;
 import android.provider.ContactsContract.CommonDataKinds.StructuredPostal;
-import android.provider.ContactsContract.Groups;
 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;
@@ -61,16 +49,10 @@
 import android.widget.Toast;
 
 import com.android.contacts.ContactSaveService;
-import com.android.contacts.GroupMetaDataLoader;
 import com.android.contacts.R;
-import com.android.contacts.activities.ContactEditorAccountsChangedActivity;
 import com.android.contacts.activities.ContactEditorActivity;
 import com.android.contacts.activities.ContactEditorBaseActivity.ContactEditor;
-import com.android.contacts.activities.ContactEditorBaseActivity.ContactEditor.SaveMode;
-import com.android.contacts.activities.ContactEditorBaseActivity.ContactEditor.Status;
 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;
@@ -85,8 +67,6 @@
 import com.android.contacts.editor.Editor.EditorListener;
 import com.android.contacts.list.UiIntentActions;
 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;
@@ -105,100 +85,31 @@
         AggregationSuggestionEngine.Listener, AggregationSuggestionView.Listener,
         RawContactReadOnlyEditorView.Listener {
 
-    private static final int LOADER_DATA = 1;
-    private static final int LOADER_GROUPS = 2;
-
-    private static final String KEY_URI = "uri";
-    private static final String KEY_ACTION = "action";
-    private static final String KEY_EDIT_STATE = "state";
-    private static final String KEY_RAW_CONTACT_ID_REQUESTING_PHOTO = "photorequester";
-    private static final String KEY_VIEW_ID_GENERATOR = "viewidgenerator";
-    private static final String KEY_CURRENT_PHOTO_URI = "currentphotouri";
+    // Joins
     private static final String KEY_CONTACT_ID_FOR_JOIN = "contactidforjoin";
     private static final String KEY_CONTACT_WRITABLE_FOR_JOIN = "contactwritableforjoin";
-    private static final String KEY_SHOW_JOIN_SUGGESTIONS = "showJoinSuggestions";
-    private static final String KEY_ENABLED = "enabled";
-    private static final String KEY_STATUS = "status";
-    private static final String KEY_NEW_LOCAL_PROFILE = "newLocalProfile";
-    private static final String KEY_IS_USER_PROFILE = "isUserProfile";
-    private static final String KEY_DISABLE_DELETE_MENU_OPTION = "disableDeleteMenuOption";
-    private static final String KEY_UPDATED_PHOTOS = "updatedPhotos";
-    private static final String KEY_IS_EDIT = "isEdit";
-    private static final String KEY_HAS_NEW_CONTACT = "hasNewContact";
-    private static final String KEY_NEW_CONTACT_READY = "newContactDataReady";
-    private static final String KEY_EXISTING_CONTACT_READY = "existingContactDataReady";
-    private static final String KEY_RAW_CONTACTS = "rawContacts";
-    private static final String KEY_SEND_TO_VOICE_MAIL_STATE = "sendToVoicemailState";
-    private static final String KEY_CUSTOM_RINGTONE = "customRingtone";
-    private static final String KEY_ARE_PHONE_OPTIONS_CHANGEABLE = "arePhoneOptionsChangable";
+
     private static final String KEY_EXPANDED_EDITORS = "expandedEditors";
 
+    // Photos
+    private static final String KEY_RAW_CONTACT_ID_REQUESTING_PHOTO = "photorequester";
+    private static final String KEY_CURRENT_PHOTO_URI = "currentphotouri";
+    private static final String KEY_UPDATED_PHOTOS = "updatedPhotos";
+
+    // Aggregations
+    private static final String KEY_AGGREGATION_SUGGESTIONS_RAW_CONTACT_ID =
+            "aggregationSuggestionsRawContactId";
+
     public static final String SAVE_MODE_EXTRA_KEY = "saveMode";
 
-    /**
-     * 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";
-
-    private static final int REQUEST_CODE_JOIN = 0;
-    private static final int REQUEST_CODE_ACCOUNTS_CHANGED = 1;
-    private static final int REQUEST_CODE_PICK_RINGTONE = 2;
-
-    private Context mContext;
-    private Listener mListener;
-
-    private LinearLayout mContent;
-
-    //
-    // Parameters passed in on {@link #load}
-    //
-    private String mAction;
-    private Uri mLookupUri;
-    private Bundle mIntentExtras;
-    private boolean mAutoAddToDefaultGroup;
-    private boolean mDisableDeleteMenuOption = false;
-    private boolean mNewLocalProfile = false;
-
     //
     // Helpers
     //
-    private ContactEditorUtils mEditorUtils;
-    private RawContactDeltaComparator mComparator;
-    private ViewIdGenerator mViewIdGenerator;
     private AggregationSuggestionEngine mAggregationSuggestionEngine;
 
     //
-    // Loaded data
-    //
-    // Used to temporarily store existing contact data during a rebind call (i.e. account switch)
-    private ImmutableList<RawContact> mRawContacts;
-    private Cursor mGroupMetaData;
-
-    //
     // Contact editor state
     //
-    private RawContactDeltaList mState;
-    private int mStatus;
-
-    // Whether to show the new contact blank form and if it's corresponding delta is ready.
-    private boolean mHasNewContact = false;
-    private boolean mNewContactDataReady = false;
-
-    // Whether it's an edit of existing contact and if it's corresponding delta is ready.
-    private boolean mIsEdit = false;
-    private boolean mExistingContactDataReady = false;
-
-    // Variables related to phone specific option menus
-    private boolean mSendToVoicemailState;
-    private boolean mArePhoneOptionsChangable;
-    private String mCustomRingtone;
-
     // Joins
     private long mContactIdForJoin;
     private boolean mContactWritableForJoin;
@@ -206,11 +117,6 @@
     // Used to store which raw contact editors have been expanded. Keyed on raw contact ids.
     private HashMap<Long, Boolean> mExpandedEditors = new HashMap<Long, Boolean>();
 
-    private boolean mIsUserProfile = false;
-
-    // Whether editors and options menu items are enabled
-    private boolean mEnabled = true;
-
     // Whether the name editor should receive focus after being bound
     private boolean mRequestFocus;
 
@@ -315,14 +221,6 @@
     }
 
     @Override
-    public void onAttach(Activity activity) {
-        super.onAttach(activity);
-        mContext = activity;
-        mEditorUtils = ContactEditorUtils.getInstance(mContext);
-        mComparator = new RawContactDeltaComparator(mContext);
-    }
-
-    @Override
     public void onStop() {
         super.onStop();
 
@@ -354,186 +252,35 @@
     }
 
     @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.
-                // or
-                // 2) not an orientation change.  data needs to be loaded for first time.
-                getLoaderManager().initLoader(LOADER_DATA, null, mDataLoaderListener);
-            }
-        } else {
-            // Orientation change, we already have mState, it was loaded by onCreate
-            bindEditors();
-        }
-
-        // Handle initial actions only when existing state missing
-        if (savedInstanceState == null) {
-            if (Intent.ACTION_EDIT.equals(mAction)) {
-                mIsEdit = true;
-            } else if (Intent.ACTION_INSERT.equals(mAction)) {
-                mHasNewContact = true;
-                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) {
-                    // Account specified in Intent
-                    createContact(new AccountWithDataSet(account.name, account.type, dataSet));
-                } 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 void validateAction(String action) {
-        if (Intent.ACTION_EDIT.equals(action) || Intent.ACTION_INSERT.equals(action) ||
-                ContactEditorActivity.ACTION_SAVE_COMPLETED.equals(action)) {
-            return;
-        }
-        throw new IllegalArgumentException("Unknown Action String " + mAction +
-                ". Only support " + Intent.ACTION_EDIT + " or " + Intent.ACTION_INSERT + " or " +
-                ContactEditorActivity.ACTION_SAVE_COMPLETED);
-    }
-
-    @Override
     public void onStart() {
         getLoaderManager().initLoader(LOADER_GROUPS, null, mGroupLoaderListener);
         super.onStart();
     }
 
     @Override
-    public void load(String action, Uri lookupUri, Bundle intentExtras) {
-        mAction = action;
-        mLookupUri = lookupUri;
-        mIntentExtras = intentExtras;
-        mAutoAddToDefaultGroup = mIntentExtras != null
-                &&  mIntentExtras.containsKey(INTENT_EXTRA_ADD_TO_DEFAULT_DIRECTORY);
-        mNewLocalProfile = mIntentExtras != null
-                && mIntentExtras.getBoolean(INTENT_EXTRA_NEW_LOCAL_PROFILE);
-        mDisableDeleteMenuOption = mIntentExtras != null
-                && mIntentExtras.getBoolean(INTENT_EXTRA_DISABLE_DELETE_MENU_OPTION);
-    }
-
-    @Override
-    public void setListener(Listener value) {
-        mListener = value;
-    }
-
-    @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
-            mLookupUri = savedState.getParcelable(KEY_URI);
-            mAction = savedState.getString(KEY_ACTION);
-        }
-
         super.onCreate(savedState);
 
-        if (savedState == null) {
-            // If savedState is non-null, onRestoreInstanceState() will restore the generator.
-            mViewIdGenerator = new ViewIdGenerator();
-        } else {
-            // Read state from savedState. No loading involved here
-            mState = savedState.<RawContactDeltaList> getParcelable(KEY_EDIT_STATE);
-            mRawContactIdRequestingPhoto = savedState.getLong(
-                    KEY_RAW_CONTACT_ID_REQUESTING_PHOTO);
-            mViewIdGenerator = savedState.getParcelable(KEY_VIEW_ID_GENERATOR);
-            mCurrentPhotoUri = savedState.getParcelable(KEY_CURRENT_PHOTO_URI);
+        if (savedState != null) {
+            // Joins
             mContactIdForJoin = savedState.getLong(KEY_CONTACT_ID_FOR_JOIN);
             mContactWritableForJoin = savedState.getBoolean(KEY_CONTACT_WRITABLE_FOR_JOIN);
-            mAggregationSuggestionsRawContactId = savedState.getLong(KEY_SHOW_JOIN_SUGGESTIONS);
-            mEnabled = savedState.getBoolean(KEY_ENABLED);
-            mStatus = savedState.getInt(KEY_STATUS);
-            mNewLocalProfile = savedState.getBoolean(KEY_NEW_LOCAL_PROFILE);
-            mDisableDeleteMenuOption = savedState.getBoolean(KEY_DISABLE_DELETE_MENU_OPTION);
-            mIsUserProfile = savedState.getBoolean(KEY_IS_USER_PROFILE);
-            mUpdatedPhotos = savedState.getParcelable(KEY_UPDATED_PHOTOS);
-            mIsEdit = savedState.getBoolean(KEY_IS_EDIT);
-            mHasNewContact = savedState.getBoolean(KEY_HAS_NEW_CONTACT);
-            mNewContactDataReady = savedState.getBoolean(KEY_NEW_CONTACT_READY);
-            mExistingContactDataReady = savedState.getBoolean(KEY_EXISTING_CONTACT_READY);
-            mRawContacts = ImmutableList.copyOf(savedState.<RawContact>getParcelableArrayList(
-                    KEY_RAW_CONTACTS));
-            mSendToVoicemailState = savedState.getBoolean(KEY_SEND_TO_VOICE_MAIL_STATE);
-            mCustomRingtone =  savedState.getString(KEY_CUSTOM_RINGTONE);
-            mArePhoneOptionsChangable =  savedState.getBoolean(KEY_ARE_PHONE_OPTIONS_CHANGEABLE);
+
             mExpandedEditors = (HashMap<Long, Boolean>)
                     savedState.getSerializable(KEY_EXPANDED_EDITORS);
+
+            // NOTE: mRequestFocus and mDefaultDisplayName are not saved/restored
+
+            // Photos
+            mRawContactIdRequestingPhoto = savedState.getLong(
+                    KEY_RAW_CONTACT_ID_REQUESTING_PHOTO);
+            mCurrentPhotoUri = savedState.getParcelable(KEY_CURRENT_PHOTO_URI);
+            mUpdatedPhotos = savedState.getParcelable(KEY_UPDATED_PHOTOS);
+
+            // Aggregations
+            mAggregationSuggestionsRawContactId = savedState.getLong(
+                    KEY_AGGREGATION_SUGGESTIONS_RAW_CONTACT_ID);
         }
-
-        // mState can still be null because it may not have have finished loading before
-        // onSaveInstanceState was called.
-        if (mState == null) {
-            mState = new RawContactDeltaList();
-        }
-    }
-
-    public void setData(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;
-        }
-
-        // See if this edit operation needs to be redirected to a custom editor
-        mRawContacts = contact.getRawContacts();
-        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 displayName = 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();
-            displayName = contact.getDisplayName();
-        }
-
-        // This also adds deltas to list
-        // If displayName is null at this point it is simply ignored later on by the editor.
-        bindEditorsForExistingContact(displayName, contact.isUserProfile(),
-                mRawContacts);
-
-        bindMenuItemsForPhone(contact);
     }
 
     @Override
@@ -546,7 +293,8 @@
         updatedExpandedEditorsMap();
     }
 
-    private void bindEditorsForExistingContact(String displayName, boolean isUserProfile,
+    @Override
+    protected void bindEditorsForExistingContact(String displayName, boolean isUserProfile,
             ImmutableList<RawContact> rawContacts) {
         setEnabled(true);
         mDefaultDisplayName = displayName;
@@ -584,20 +332,7 @@
         bindEditors();
     }
 
-    private void bindMenuItemsForPhone(Contact contact) {
-        mSendToVoicemailState = contact.isSendToVoicemail();
-        mCustomRingtone = contact.getCustomRingtone();
-        mArePhoneOptionsChangable = arePhoneOptionsChangable(contact);
-    }
-
-    private boolean arePhoneOptionsChangable(Contact contact) {
-        return contact != null && !contact.isDirectoryEntry()
-                && PhoneCapabilityTester.isPhone(mContext);
-    }
-
-    /**
-     * Merges extras from the intent.
-     */
+    @Override
     public void setIntentExtras(Bundle extras) {
         if (extras == null || extras.size() == 0) {
             return;
@@ -614,64 +349,6 @@
         }
     }
 
-    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);
-            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 {
-            bindEditorsForNewContact(account, accountType);
-        }
-    }
-
     /**
      * 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.
@@ -703,7 +380,8 @@
         }
     }
 
-    private void bindEditorsForNewContact(AccountWithDataSet account,
+    @Override
+    protected void bindEditorsForNewContact(AccountWithDataSet account,
             final AccountType accountType) {
         bindEditorsForNewContact(account, accountType, null, null);
     }
@@ -748,7 +426,8 @@
         bindEditors();
     }
 
-    private void bindEditors() {
+    @Override
+    protected void bindEditors() {
         // bindEditors() can only bind views if there is data in mState, so immediately return
         // if mState is null
         if (mState.isEmpty()) {
@@ -954,7 +633,8 @@
         }
     }
 
-    private void bindGroupMetaData() {
+    @Override
+    protected void bindGroupMetaData() {
         if (mGroupMetaData == null) {
             return;
         }
@@ -1028,138 +708,6 @@
     }
 
     @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 doneMenu = menu.findItem(R.id.menu_done);
-        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 discardMenu = menu.findItem(R.id.menu_discard);
-        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);
-        deleteMenu.setShowAsAction(MenuItem.SHOW_AS_ACTION_ALWAYS);
-        deleteMenu.setIcon(R.drawable.ic_delete_white_24dp);
-
-        // Set visibility of menus
-        doneMenu.setVisible(false);
-
-        // Discard menu is only available if at least one raw contact is editable
-        discardMenu.setVisible(mState != null &&
-                mState.getFirstWritableRawContact(mContext) != null);
-
-        // help menu depending on whether this is inserting or editing
-        if (Intent.ACTION_INSERT.equals(mAction)) {
-            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);
-            // Split only if more than one raw profile and not a user profile
-            splitMenu.setVisible(mState.size() > 1 && !isEditingUserProfile());
-            // Cannot join a user profile
-            joinMenu.setVisible(!isEditingUserProfile());
-            deleteMenu.setVisible(!mDisableDeleteMenuOption);
-        } else {
-            // something else, so don't show the help menu
-            helpMenu.setVisible(false);
-        }
-
-        // 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) {
-        switch (item.getItemId()) {
-            case android.R.id.home:
-            case R.id.menu_done:
-                return save(SaveMode.CLOSE);
-            case R.id.menu_discard:
-                return revert();
-            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;
-    }
-
-    private boolean doSplitContactAction() {
-        if (!hasValidState()) return false;
-
-        final SplitContactConfirmationDialogFragment dialog =
-                new SplitContactConfirmationDialogFragment();
-        dialog.setTargetFragment(this, 0);
-        dialog.show(getFragmentManager(), SplitContactConfirmationDialogFragment.TAG);
-        return true;
-    }
-
-    private boolean doJoinContactAction() {
-        if (!hasValidState()) {
-            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;
-        }
-
-        return save(SaveMode.JOIN);
-    }
-
-    /**
-     * Check if our internal {@link #mState} is valid, usually checked before
-     * performing user actions.
-     */
-    private boolean hasValidState() {
-        return mState.size() > 0;
-    }
-
-    /**
-     * Return true if there are any edits to the current contact which need to
-     * be saved.
-     */
-    private boolean hasPendingChanges() {
-        final AccountTypeManager accountTypes = AccountTypeManager.getInstance(mContext);
-        return RawContactModifier.hasChanges(mState, accountTypes);
-    }
-
-    @Override
     public boolean save(int saveMode) {
         if (!hasValidState() || mStatus != Status.EDITING) {
             return false;
@@ -1201,96 +749,12 @@
         return true;
     }
 
-    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;
-        if (mCustomRingtone != null) {
-            ringtoneUri = Uri.parse(mCustomRingtone);
-        } else {
-            // Otherwise pick default ringtone Uri so that something is selected.
-            ringtoneUri = RingtoneManager.getDefaultUri(RingtoneManager.TYPE_RINGTONE);
-        }
-
-        // 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();
-        }
-    }
-
-    private void handleRingtonePicked(Uri pickedUri) {
-        if (pickedUri == null || RingtoneManager.isDefault(pickedUri)) {
-            mCustomRingtone = null;
-        } else {
-            mCustomRingtone = pickedUri.toString();
-        }
-        Intent intent = ContactSaveService.createSetRingtone(
-                mContext, mLookupUri, mCustomRingtone);
-        mContext.startService(intent);
-    }
-
-    public static class CancelEditDialogFragment extends DialogFragment {
-
-        public static void show(ContactEditorFragment fragment) {
-            CancelEditDialogFragment dialog = new CancelEditDialogFragment();
-            dialog.setTargetFragment(fragment, 0);
-            dialog.show(fragment.getFragmentManager(), "cancelEditor");
-        }
-
-        @Override
-        public Dialog onCreateDialog(Bundle savedInstanceState) {
-            AlertDialog dialog = new AlertDialog.Builder(getActivity())
-                    .setIconAttribute(android.R.attr.alertDialogIcon)
-                    .setMessage(R.string.cancel_confirmation_dialog_message)
-                    .setPositiveButton(android.R.string.ok,
-                        new DialogInterface.OnClickListener() {
-                            @Override
-                            public void onClick(DialogInterface dialogInterface, int whichButton) {
-                                ((ContactEditorFragment)getTargetFragment()).doRevertAction();
-                            }
-                        }
-                    )
-                    .setNegativeButton(android.R.string.cancel, null)
-                    .create();
-            return dialog;
-        }
-    }
-
-    private boolean revert() {
-        if (mState.isEmpty() || !hasPendingChanges()) {
-            doRevertAction();
-        } else {
-            CancelEditDialogFragment.show(this);
-        }
-        return true;
-    }
-
-    private void doRevertAction() {
-        // When this Fragment is closed we don't want it to auto-save
-        mStatus = Status.CLOSING;
-        if (mListener != null) mListener.onReverted();
-    }
-
-    public void doSaveAction() {
-        save(SaveMode.CLOSE);
-    }
-
+    @Override
     public void onJoinCompleted(Uri uri) {
         onSaveCompleted(false, SaveMode.RELOAD, uri != null, uri);
     }
 
+    @Override
     public void onSaveCompleted(boolean hadChanges, int saveMode, boolean saveSucceeded,
             Uri contactLookupUri) {
         if (hadChanges) {
@@ -1389,10 +853,6 @@
         return false;
     }
 
-    private boolean isEditingUserProfile() {
-        return mNewLocalProfile || mIsUserProfile;
-    }
-
     /**
      * Returns the contact ID for the currently edited contact or 0 if the contact is new.
      */
@@ -1584,37 +1044,21 @@
 
     @Override
     public void onSaveInstanceState(Bundle outState) {
-        outState.putParcelable(KEY_URI, mLookupUri);
-        outState.putString(KEY_ACTION, mAction);
-
-        if (hasValidState()) {
-            // Store entities with modifications
-            outState.putParcelable(KEY_EDIT_STATE, mState);
-        }
-        outState.putLong(KEY_RAW_CONTACT_ID_REQUESTING_PHOTO, mRawContactIdRequestingPhoto);
-        outState.putParcelable(KEY_VIEW_ID_GENERATOR, mViewIdGenerator);
-        outState.putParcelable(KEY_CURRENT_PHOTO_URI, mCurrentPhotoUri);
+        // Joins
         outState.putLong(KEY_CONTACT_ID_FOR_JOIN, mContactIdForJoin);
         outState.putBoolean(KEY_CONTACT_WRITABLE_FOR_JOIN, mContactWritableForJoin);
-        outState.putLong(KEY_SHOW_JOIN_SUGGESTIONS, mAggregationSuggestionsRawContactId);
-        outState.putBoolean(KEY_ENABLED, mEnabled);
-        outState.putBoolean(KEY_NEW_LOCAL_PROFILE, mNewLocalProfile);
-        outState.putBoolean(KEY_DISABLE_DELETE_MENU_OPTION, mDisableDeleteMenuOption);
-        outState.putBoolean(KEY_IS_USER_PROFILE, mIsUserProfile);
-        outState.putInt(KEY_STATUS, mStatus);
-        outState.putParcelable(KEY_UPDATED_PHOTOS, mUpdatedPhotos);
-        outState.putBoolean(KEY_HAS_NEW_CONTACT, mHasNewContact);
-        outState.putBoolean(KEY_IS_EDIT, mIsEdit);
-        outState.putBoolean(KEY_NEW_CONTACT_READY, mNewContactDataReady);
-        outState.putBoolean(KEY_EXISTING_CONTACT_READY, mExistingContactDataReady);
-        outState.putParcelableArrayList(KEY_RAW_CONTACTS,
-                mRawContacts == null ?
-                Lists.<RawContact>newArrayList() : Lists.newArrayList(mRawContacts));
-        outState.putBoolean(KEY_SEND_TO_VOICE_MAIL_STATE, mSendToVoicemailState);
-        outState.putString(KEY_CUSTOM_RINGTONE, mCustomRingtone);
-        outState.putBoolean(KEY_ARE_PHONE_OPTIONS_CHANGEABLE, mArePhoneOptionsChangable);
+
         outState.putSerializable(KEY_EXPANDED_EDITORS, mExpandedEditors);
 
+        // Photos
+        outState.putLong(KEY_RAW_CONTACT_ID_REQUESTING_PHOTO, mRawContactIdRequestingPhoto);
+        outState.putParcelable(KEY_CURRENT_PHOTO_URI, mCurrentPhotoUri);
+        outState.putParcelable(KEY_UPDATED_PHOTOS, mUpdatedPhotos);
+
+        // Aggregations
+        outState.putLong(KEY_AGGREGATION_SUGGESTIONS_RAW_CONTACT_ID,
+                mAggregationSuggestionsRawContactId);
+
         super.onSaveInstanceState(outState);
     }
 
@@ -1640,33 +1084,8 @@
                 }
                 break;
             }
-            case REQUEST_CODE_ACCOUNTS_CHANGED: {
-                // Bail if the account selector was not successful.
-                if (resultCode != Activity.RESULT_OK) {
-                    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);
-                    handleRingtonePicked(pickedUri);
-                }
+            default: {
+                super.onActivityResult(requestCode, resultCode, data);
                 break;
             }
         }
@@ -1751,68 +1170,6 @@
         return false;
     }
 
-    /**
-     * The listener for the data loader
-     */
-    private final LoaderManager.LoaderCallbacks<Contact> mDataLoaderListener =
-            new LoaderCallbacks<Contact>() {
-
-        private 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 data) {
-            final long loaderCurrentTime = SystemClock.elapsedRealtime();
-            Log.v(TAG, "Time needed for loading: " + (loaderCurrentTime-mLoaderStartTime));
-            if (!data.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 = data.getLookupUri();
-            final long setDataStartTime = SystemClock.elapsedRealtime();
-            setData(data);
-            final long setDataEndTime = SystemClock.elapsedRealtime();
-
-            Log.v(TAG, "Time needed for setting UI: " + (setDataEndTime-setDataStartTime));
-        }
-
-        @Override
-        public void onLoaderReset(Loader<Contact> loader) {
-        }
-    };
-
-    /**
-     * The listener for the group meta data loader for all groups.
-     */
-    private final LoaderManager.LoaderCallbacks<Cursor> mGroupLoaderListener =
-            new LoaderCallbacks<Cursor>() {
-
-        @Override
-        public CursorLoader onCreateLoader(int id, Bundle args) {
-            return new GroupMetaDataLoader(mContext, Groups.CONTENT_URI);
-        }
-
-        @Override
-        public void onLoadFinished(Loader<Cursor> loader, Cursor data) {
-            mGroupMetaData = data;
-            bindGroupMetaData();
-        }
-
-        @Override
-        public void onLoaderReset(Loader<Cursor> loader) {
-        }
-    };
-
     @Override
     public void onSplitContactConfirmed() {
         if (mState.isEmpty()) {
diff --git a/src/com/android/contacts/editor/SplitContactConfirmationDialogFragment.java b/src/com/android/contacts/editor/SplitContactConfirmationDialogFragment.java
index c790e0b..b4181b8 100644
--- a/src/com/android/contacts/editor/SplitContactConfirmationDialogFragment.java
+++ b/src/com/android/contacts/editor/SplitContactConfirmationDialogFragment.java
@@ -34,7 +34,11 @@
 public class SplitContactConfirmationDialogFragment extends DialogFragment {
     public static final String TAG = "SplitContactConfirmationDialog";
 
-    public SplitContactConfirmationDialogFragment() {
+    public static void show(ContactEditorBaseFragment fragment) {
+        SplitContactConfirmationDialogFragment dialog = new
+                SplitContactConfirmationDialogFragment();
+        dialog.setTargetFragment(fragment, 0);
+        dialog.show(fragment.getFragmentManager(), "splitContact");
     }
 
     @Override