Merge "Don't use hidden AggregationSuggestions.builder"
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/CompactContactEditorFragment.java b/src/com/android/contacts/editor/CompactContactEditorFragment.java
index 5361155..46f50d2 100644
--- a/src/com/android/contacts/editor/CompactContactEditorFragment.java
+++ b/src/com/android/contacts/editor/CompactContactEditorFragment.java
@@ -16,15 +16,19 @@
 
 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.Contact;
+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;
+import android.util.Log;
 import android.view.LayoutInflater;
 import android.view.View;
 import android.view.ViewGroup;
@@ -36,21 +40,6 @@
 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;
@@ -64,6 +53,36 @@
         return view;
     }
 
+    //
+    // 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() {
+    }
+
+    @Override
+    protected void bindMenuItemsForPhone(Contact contact) {
+    }
+
+    //
+    // ContactEditor
+    //
+
     @Override
     public void load(String action, Uri lookupUri, Bundle intentExtras) {
         mAction = action;
diff --git a/src/com/android/contacts/editor/ContactEditorBaseFragment.java b/src/com/android/contacts/editor/ContactEditorBaseFragment.java
index 240a42f..70d5be2 100644
--- a/src/com/android/contacts/editor/ContactEditorBaseFragment.java
+++ b/src/com/android/contacts/editor/ContactEditorBaseFragment.java
@@ -16,28 +16,81 @@
 
 package com.android.contacts.editor;
 
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.Lists;
+
+import com.android.contacts.GroupMetaDataLoader;
+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.account.AccountType;
 import com.android.contacts.common.model.account.AccountWithDataSet;
 import com.android.contacts.quickcontact.QuickContactActivity;
 
+import android.accounts.Account;
+import android.app.Activity;
 import android.app.Fragment;
+import android.app.LoaderManager;
 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.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.widget.LinearLayout;
 
 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 {
 
-    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";
+
+    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;
 
     /**
      * Callbacks for Activities that host contact editors Fragments.
@@ -98,6 +151,384 @@
         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;
+
+    /**
+     * 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);
+        }
+
+        // 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);
+
+        super.onSaveInstanceState(outState);
+    }
+
+    /**
+     * Check if our internal {@link #mState} is valid, usually checked before
+     * performing user actions.
+     */
+    protected boolean hasValidState() {
+        return mState.size() > 0;
+    }
+
+    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);
+    }
+
+    //
+    // 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);
+        }
+    }
+
+    // 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();
+    // TODO: should be removed after options menu is moved to base
+    abstract protected void bindMenuItemsForPhone(Contact contact);
+
     /**
      * Returns a legacy version of the given contactLookupUri if a legacy Uri was originally
      * passed to the contact editor.
@@ -106,6 +537,7 @@
      * @param requestLookupUri The lookup Uri originally passed to the contact editor
      *                         (via Intent data), may be null.
      */
+    // TODO: move to ContactEditorUtils?
     protected static Uri maybeConvertToLegacyLookupUri(Context context, Uri contactLookupUri,
             Uri requestLookupUri) {
         final String legacyAuthority = "contacts";
@@ -126,6 +558,7 @@
      * Creates the result Intent for the given contactLookupUri that should started after a
      * successful saving a contact.
      */
+    // TODO: move to ContactEditorUtils?
     protected static Intent composeQuickContactsIntent(Context context, Uri contactLookupUri) {
         final Intent intent = QuickContact.composeQuickContactsIntent(
                 context, (Rect) null, contactLookupUri, QuickContactActivity.MODE_FULLY_EXPANDED,
diff --git a/src/com/android/contacts/editor/ContactEditorFragment.java b/src/com/android/contacts/editor/ContactEditorFragment.java
index 0a8a967..6639e1b 100644
--- a/src/com/android/contacts/editor/ContactEditorFragment.java
+++ b/src/com/android/contacts/editor/ContactEditorFragment.java
@@ -16,33 +16,25 @@
 
 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;
@@ -61,16 +53,11 @@
 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;
@@ -105,34 +92,30 @@
         AggregationSuggestionEngine.Listener, AggregationSuggestionView.Listener,
         RawContactReadOnlyEditorView.Listener {
 
-    private static final int LOADER_DATA = 1;
-    private static final int LOADER_GROUPS = 2;
+    // 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_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";
 
+    private static final String KEY_IS_USER_PROFILE = "isUserProfile";
+
+    private static final String KEY_ENABLED = "enabled";
+
+    // 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";
 
     /**
@@ -146,55 +129,15 @@
     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
+    // Phone specific option menus
     private boolean mSendToVoicemailState;
     private boolean mArePhoneOptionsChangable;
     private String mCustomRingtone;
@@ -315,14 +258,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,67 +289,6 @@
     }
 
     @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();
@@ -440,100 +314,37 @@
 
     @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) {
+            // 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);
+
+            // 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);
+
+            mIsUserProfile = savedState.getBoolean(KEY_IS_USER_PROFILE);
+
+            mEnabled = savedState.getBoolean(KEY_ENABLED);
+
+            // 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 +357,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,7 +396,8 @@
         bindEditors();
     }
 
-    private void bindMenuItemsForPhone(Contact contact) {
+    @Override
+    protected void bindMenuItemsForPhone(Contact contact) {
         mSendToVoicemailState = contact.isSendToVoicemail();
         mCustomRingtone = contact.getCustomRingtone();
         mArePhoneOptionsChangable = arePhoneOptionsChangable(contact);
@@ -614,64 +427,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 +458,8 @@
         }
     }
 
-    private void bindEditorsForNewContact(AccountWithDataSet account,
+    @Override
+    protected void bindEditorsForNewContact(AccountWithDataSet account,
             final AccountType accountType) {
         bindEditorsForNewContact(account, accountType, null, null);
     }
@@ -748,7 +504,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 +711,8 @@
         }
     }
 
-    private void bindGroupMetaData() {
+    @Override
+    protected void bindGroupMetaData() {
         if (mGroupMetaData == null) {
             return;
         }
@@ -1143,14 +901,6 @@
     }
 
     /**
-     * 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.
      */
@@ -1283,14 +1033,12 @@
         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) {
@@ -1584,36 +1332,27 @@
 
     @Override
     public void onSaveInstanceState(Bundle outState) {
-        outState.putParcelable(KEY_URI, mLookupUri);
-        outState.putString(KEY_ACTION, mAction);
+        // 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);
 
-        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);
+        outState.putBoolean(KEY_IS_USER_PROFILE, mIsUserProfile);
+        outState.putBoolean(KEY_ENABLED, mEnabled);
+
+        // 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);
     }
@@ -1751,68 +1490,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()) {