Merge "Add edit contact menu options to compact editor"
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 46f50d2..aee1f73 100644
--- a/src/com/android/contacts/editor/CompactContactEditorFragment.java
+++ b/src/com/android/contacts/editor/CompactContactEditorFragment.java
@@ -20,7 +20,6 @@
 
 import com.android.contacts.R;
 import com.android.contacts.activities.ContactEditorBaseActivity.ContactEditor;
-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;
@@ -28,7 +27,6 @@
 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;
@@ -41,18 +39,23 @@
         implements ContactEditor {
 
     @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);
         return view;
     }
 
+    @Override
+    public void onActivityResult(int requestCode, int resultCode, Intent data) {
+        if (mStatus == Status.SUB_ACTIVITY) {
+            mStatus = Status.EDITING;
+        }
+        super.onActivityResult(requestCode, resultCode, data);
+    }
+
     //
     // ContactEditorBaseFragment
     //
@@ -75,22 +78,11 @@
     protected void bindGroupMetaData() {
     }
 
-    @Override
-    protected void bindMenuItemsForPhone(Contact contact) {
-    }
-
     //
     // ContactEditor
     //
 
     @Override
-    public void load(String action, Uri lookupUri, Bundle intentExtras) {
-        mAction = action;
-        mLookupUri = lookupUri;
-        mIntentExtras = intentExtras;
-    }
-
-    @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 70d5be2..4dcacca 100644
--- a/src/com/android/contacts/editor/ContactEditorBaseFragment.java
+++ b/src/com/android/contacts/editor/ContactEditorBaseFragment.java
@@ -19,7 +19,9 @@
 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;
@@ -28,14 +30,18 @@
 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;
@@ -44,6 +50,7 @@
 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;
@@ -53,7 +60,11 @@
 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;
@@ -62,7 +73,7 @@
  * Base Fragment for contact editors.
  */
 abstract public class ContactEditorBaseFragment extends Fragment implements
-        ContactEditor {
+        ContactEditor, SplitContactConfirmationDialogFragment.Listener {
 
     protected static final String TAG = "ContactEditor";
 
@@ -88,11 +99,31 @@
     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.
      */
     public interface Listener {
@@ -194,6 +225,16 @@
     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.
      */
@@ -297,6 +338,15 @@
 
             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
@@ -374,8 +424,7 @@
     }
 
     @Override
-    public void
-    onSaveInstanceState(Bundle outState) {
+    public void onSaveInstanceState(Bundle outState) {
         outState.putString(KEY_ACTION, mAction);
         outState.putParcelable(KEY_URI, mLookupUri);
         outState.putBoolean(KEY_AUTO_ADD_TO_DEFAULT_GROUP, mAutoAddToDefaultGroup);
@@ -398,9 +447,220 @@
         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.
@@ -409,52 +669,17 @@
         return mState.size() > 0;
     }
 
-    private void setData(Contact contact) {
+    protected boolean isEditingUserProfile() {
+        return mNewLocalProfile || mIsUserProfile;
+    }
 
-        // 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);
+    /**
+     * 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);
     }
 
     //
@@ -519,6 +744,67 @@
         }
     }
 
+    //
+    // 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);
@@ -526,8 +812,31 @@
             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);
+
+    //
+    // 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
@@ -537,7 +846,6 @@
      * @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";
@@ -558,7 +866,6 @@
      * 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 6639e1b..7fb4378 100644
--- a/src/com/android/contacts/editor/ContactEditorFragment.java
+++ b/src/com/android/contacts/editor/ContactEditorFragment.java
@@ -20,7 +20,6 @@
 import android.app.AlertDialog;
 import android.app.Dialog;
 import android.app.DialogFragment;
-import android.content.ActivityNotFoundException;
 import android.content.ContentUris;
 import android.content.Context;
 import android.content.DialogInterface;
@@ -40,9 +39,6 @@
 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;
@@ -57,7 +53,6 @@
 import com.android.contacts.activities.ContactEditorActivity;
 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.RawContact;
 import com.android.contacts.common.model.RawContactDelta;
 import com.android.contacts.common.model.RawContactDeltaList;
@@ -72,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;
@@ -92,21 +85,12 @@
         AggregationSuggestionEngine.Listener, AggregationSuggestionView.Listener,
         RawContactReadOnlyEditorView.Listener {
 
-    // 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";
-
     // 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_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";
@@ -118,17 +102,6 @@
 
     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";
-
     //
     // Helpers
     //
@@ -137,11 +110,6 @@
     //
     // Contact editor state
     //
-    // Phone specific option menus
-    private boolean mSendToVoicemailState;
-    private boolean mArePhoneOptionsChangable;
-    private String mCustomRingtone;
-
     // Joins
     private long mContactIdForJoin;
     private boolean mContactWritableForJoin;
@@ -149,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;
 
@@ -295,33 +258,10 @@
     }
 
     @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) {
         super.onCreate(savedState);
 
         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);
@@ -329,10 +269,6 @@
             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
@@ -397,20 +333,6 @@
     }
 
     @Override
-    protected 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.
-     */
     public void setIntentExtras(Bundle extras) {
         if (extras == null || extras.size() == 0) {
             return;
@@ -786,130 +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);
-    }
-
-    /**
-     * 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;
@@ -951,88 +749,6 @@
         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();
-    }
-
     @Override
     public void onJoinCompleted(Uri uri) {
         onSaveCompleted(false, SaveMode.RELOAD, uri != null, uri);
@@ -1137,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.
      */
@@ -1332,18 +1044,11 @@
 
     @Override
     public void onSaveInstanceState(Bundle outState) {
-        // 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);
-
         // Joins
         outState.putLong(KEY_CONTACT_ID_FOR_JOIN, mContactIdForJoin);
         outState.putBoolean(KEY_CONTACT_WRITABLE_FOR_JOIN, mContactWritableForJoin);
 
         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);
@@ -1379,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;
             }
         }
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