Merge "Add suggestion card for quick contact (P2)" into ub-contactsdialer-a-dev
diff --git a/res/layout-land/compact_contact_editor_fragment.xml b/res/layout-land/compact_contact_editor_fragment.xml
index 187deae..0d86f89 100644
--- a/res/layout-land/compact_contact_editor_fragment.xml
+++ b/res/layout-land/compact_contact_editor_fragment.xml
@@ -17,7 +17,7 @@
 
 <com.android.contacts.editor.CompactRawContactsEditorView
         xmlns:android="http://schemas.android.com/apk/res/android"
-        android:id="@+id/editors"
+        android:id="@+id/raw_contacts_editor_view"
         android:layout_width="match_parent"
         android:layout_height="wrap_content"
         android:background="@color/background_primary"
diff --git a/res/layout/account_type_info.xml b/res/layout/account_type_info.xml
new file mode 100644
index 0000000..64e553c
--- /dev/null
+++ b/res/layout/account_type_info.xml
@@ -0,0 +1,36 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  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.
+  -->
+
+<LinearLayout
+        xmlns:android="http://schemas.android.com/apk/res/android"
+        android:id="@+id/account_type"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:orientation="horizontal"
+        android:visibility="gone">
+
+    <ImageView
+            android:id="@+id/account_type_icon"
+            style="@style/AccountTypeIconStyle" />
+
+    <TextView
+            android:id="@+id/account_type_name"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            style="@style/AccountTypeNameStyle" />
+
+</LinearLayout>
\ No newline at end of file
diff --git a/res/layout/compact_contact_editor_fields.xml b/res/layout/compact_contact_editor_fields.xml
index 1ddd0b1..710ba94 100644
--- a/res/layout/compact_contact_editor_fields.xml
+++ b/res/layout/compact_contact_editor_fields.xml
@@ -18,41 +18,11 @@
 <merge xmlns:android="http://schemas.android.com/apk/res/android">
 
     <LinearLayout
-            android:id="@+id/names"
+            android:id="@+id/kind_section_views"
             android:layout_width="match_parent"
             android:layout_height="wrap_content"
-            android:layout_marginTop="@dimen/editor_compact_first_field_padding"
-            android:orientation="vertical"/>
-
-    <LinearLayout
-            android:id="@+id/phonetic_names"
-            android:layout_width="match_parent"
-            android:layout_height="wrap_content"
-            android:orientation="vertical"/>
-
-    <LinearLayout
-            android:id="@+id/nicknames"
-            android:layout_width="match_parent"
-            android:layout_height="wrap_content"
-            android:orientation="vertical"/>
-
-    <LinearLayout
-            android:id="@+id/phone_numbers"
-            android:layout_width="match_parent"
-            android:layout_height="wrap_content"
-            android:orientation="vertical"/>
-
-    <LinearLayout
-            android:id="@+id/emails"
-            android:layout_width="match_parent"
-            android:layout_height="wrap_content"
-            android:orientation="vertical"/>
-
-    <LinearLayout
-            android:id="@+id/other"
-            android:layout_width="match_parent"
-            android:layout_height="wrap_content"
-            android:orientation="vertical"/>
+            android:orientation="vertical"
+            android:animateLayoutChanges="true"/>
 
     <LinearLayout
             style="@style/SelectableItem"
diff --git a/res/layout/compact_contact_editor_fragment.xml b/res/layout/compact_contact_editor_fragment.xml
index a07381c..b3818c9 100644
--- a/res/layout/compact_contact_editor_fragment.xml
+++ b/res/layout/compact_contact_editor_fragment.xml
@@ -24,7 +24,7 @@
         android:fillViewport="true">
 
     <com.android.contacts.editor.CompactRawContactsEditorView
-            android:id="@+id/editors"
+            android:id="@+id/raw_contacts_editor_view"
             android:layout_width="match_parent"
             android:layout_height="wrap_content"
             android:orientation="vertical"
diff --git a/res/layout/compact_item_kind_section.xml b/res/layout/compact_item_kind_section.xml
new file mode 100644
index 0000000..771ccfe
--- /dev/null
+++ b/res/layout/compact_item_kind_section.xml
@@ -0,0 +1,36 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+     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.
+-->
+
+<!-- The body surrounding all editors for a specific kind -->
+
+<com.android.contacts.editor.CompactKindSectionView
+        xmlns:android="http://schemas.android.com/apk/res/android"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:orientation="horizontal">
+
+    <ImageView
+            android:id="@+id/kind_icon"
+            style="@style/EditKindIconStyle" />
+
+    <LinearLayout
+            android:id="@+id/kind_editors"
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:orientation="vertical" />
+
+</com.android.contacts.editor.CompactKindSectionView>
\ No newline at end of file
diff --git a/res/layout/contact_editor_accounts_changed_activity_with_picker.xml b/res/layout/contact_editor_accounts_changed_activity_with_picker.xml
index a5aab20..a4f1b0a 100644
--- a/res/layout/contact_editor_accounts_changed_activity_with_picker.xml
+++ b/res/layout/contact_editor_accounts_changed_activity_with_picker.xml
@@ -27,7 +27,12 @@
     <TextView android:id="@+id/text"
         android:layout_width="match_parent"
         android:layout_height="wrap_content"
-        android:padding="15dip"
+        android:layout_marginLeft="16dip"
+        android:layout_marginRight="16dip"
+        android:paddingTop="15dip"
+        android:paddingBottom="15dip"
+        android:paddingLeft="10dip"
+        android:paddingRight="10dip"
         android:textAppearance="?android:attr/textAppearanceMedium"/>
 
     <View
@@ -39,6 +44,7 @@
         android:layout_width="match_parent"
         android:layout_height="0dip"
         android:layout_weight="1"
+        android:layout_marginLeft="16dip"
         android:fadingEdge="none"/>
 
     <View
diff --git a/res/layout/structured_name_editor_view.xml b/res/layout/structured_name_editor_view.xml
index 2b0ee7b..830f4d8 100644
--- a/res/layout/structured_name_editor_view.xml
+++ b/res/layout/structured_name_editor_view.xml
@@ -27,27 +27,27 @@
     <include
         android:id="@+id/spinner"
         layout="@layout/edit_spinner"
-        android:visibility="gone" />
+        android:visibility="gone"/>
 
     <ImageView
         android:id="@+id/kind_icon"
         android:src="@drawable/ic_person_black_24dp"
         android:contentDescription="@string/header_name_entry"
-        style="@style/EditKindIconStyle" />
+        style="@style/EditKindIconStyle"/>
 
     <include
-        layout="@layout/edit_field_list_with_anchor_view" />
+        layout="@layout/edit_field_list_with_anchor_view"/>
 
     <include
         android:id="@+id/expansion_view_container"
         layout="@layout/name_edit_expansion_view"
-        android:visibility="gone" />
+        android:visibility="gone"/>
 
     <!-- This isn't used in StructuredNameEditorView. It is only included so that
         StructuredNameEditorView's base classes don't need extra null checks. -->
     <include
         android:id="@+id/delete_button_container"
         layout="@layout/edit_delete_button"
-        android:visibility="gone" />
+        android:visibility="gone"/>
 
 </com.android.contacts.editor.StructuredNameEditorView>
diff --git a/res/layout/structured_name_readonly_editor_view.xml b/res/layout/structured_name_readonly_editor_view.xml
new file mode 100644
index 0000000..4778570
--- /dev/null
+++ b/res/layout/structured_name_readonly_editor_view.xml
@@ -0,0 +1,38 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  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.
+  -->
+
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:minHeight="@dimen/editor_min_line_item_height"
+        android:orientation="vertical">
+
+    <TextView
+            android:id="@+id/display_name"
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:layout_marginStart="4dp"
+            android:layout_marginBottom="6dp"
+            android:singleLine="true"
+            android:textSize="@dimen/editor_form_text_size"
+            android:textColor="?android:attr/textColorSecondary"
+            android:enabled="false"/>
+
+    <include layout="@layout/account_type_info"
+            android:layout_marginStart="12dp" />
+
+</LinearLayout>
diff --git a/res/values/strings.xml b/res/values/strings.xml
index d4979f4..1104a24 100644
--- a/res/values/strings.xml
+++ b/res/values/strings.xml
@@ -768,6 +768,9 @@
     <!-- Content description for the compact contact editor photo overlay which, when clicked, shows a dialog with the options for changing the contact photo. [CHAR LIMIT=30] -->
     <string name="compact_editor_change_photo_content_description">Change photo</string>
 
+    <!-- Toast message displayed when the editor fails to load for a contacts. [CHAR LIMIT=NONE] -->
+    <string name="compact_editor_failed_to_load">Failed to open editor.</string>
+
     <!-- Quick contact display name with phonetic name -->
     <string name="quick_contact_display_name_with_phonetic"><xliff:g id="display_name">%s</xliff:g> (<xliff:g id="phonetic_name">%s</xliff:g>)</string>
 
diff --git a/res/values/styles.xml b/res/values/styles.xml
index 8fce0f1..14dfee2 100644
--- a/res/values/styles.xml
+++ b/res/values/styles.xml
@@ -325,4 +325,17 @@
         <item name="android:layout_marginEnd">28dp</item>
         <item name="android:layout_marginTop">@dimen/editor_kind_icon_top_margin</item>
     </style>
+
+    <style name="AccountTypeIconStyle">
+        <item name="android:layout_width">12dp</item>
+        <item name="android:layout_height">12dp</item>
+        <item name="android:layout_marginStart">4dp</item>
+        <item name="android:layout_marginEnd">4dp</item>
+    </style>
+
+    <style name="AccountTypeNameStyle">
+        <item name="android:textSize">10sp</item>
+        <item name="android:textColor">#363636</item>
+        <item name="android:fontFamily">sans-serif</item>
+    </style>
 </resources>
diff --git a/src/com/android/contacts/ContactSaveService.java b/src/com/android/contacts/ContactSaveService.java
index 634e29a..1989606 100755
--- a/src/com/android/contacts/ContactSaveService.java
+++ b/src/com/android/contacts/ContactSaveService.java
@@ -313,7 +313,7 @@
         Bundle bundle = new Bundle();
         bundle.putParcelable(String.valueOf(rawContactId), updatedPhotoPath);
         return createSaveContactIntent(context, state, saveModeExtraKey, saveMode, isProfile,
-                callbackActivity, callbackAction, bundle, /* backPressed =*/ false);
+                callbackActivity, callbackAction, bundle);
     }
 
     /**
@@ -322,13 +322,11 @@
      * This variant is used when multiple contacts' photos may be updated, as in the
      * Contact Editor.
      * @param updatedPhotos maps each raw-contact's ID to the file-path of the new photo.
-     * @param backPressed whether the save was initiated as a result of a back button press
-     *         or because the framework stopped the editor Activity
      */
     public static Intent createSaveContactIntent(Context context, RawContactDeltaList state,
             String saveModeExtraKey, int saveMode, boolean isProfile,
             Class<? extends Activity> callbackActivity, String callbackAction,
-            Bundle updatedPhotos, boolean backPressed) {
+            Bundle updatedPhotos) {
         Intent serviceIntent = new Intent(
                 context, ContactSaveService.class);
         serviceIntent.setAction(ContactSaveService.ACTION_SAVE_CONTACT);
@@ -347,11 +345,6 @@
             Intent callbackIntent = new Intent(context, callbackActivity);
             callbackIntent.putExtra(saveModeExtraKey, saveMode);
             callbackIntent.setAction(callbackAction);
-            if (updatedPhotos != null) {
-                callbackIntent.putExtra(EXTRA_UPDATED_PHOTOS, (Parcelable) updatedPhotos);
-            }
-            callbackIntent.putExtra(ContactEditorFragment.INTENT_EXTRA_SAVE_BACK_PRESSED,
-                    backPressed);
             serviceIntent.putExtra(ContactSaveService.EXTRA_CALLBACK_INTENT, callbackIntent);
         }
         return serviceIntent;
diff --git a/src/com/android/contacts/activities/CompactContactEditorActivity.java b/src/com/android/contacts/activities/CompactContactEditorActivity.java
index 082bb74..28dd8a5 100644
--- a/src/com/android/contacts/activities/CompactContactEditorActivity.java
+++ b/src/com/android/contacts/activities/CompactContactEditorActivity.java
@@ -56,11 +56,4 @@
         final Uri uri = Intent.ACTION_EDIT.equals(action) ? getIntent().getData() : null;
         mFragment.load(action, uri, getIntent().getExtras());
     }
-
-    @Override
-    public void onBackPressed() {
-        if (mFragment != null) {
-            mFragment.revert();
-        }
-    }
 }
diff --git a/src/com/android/contacts/activities/ContactEditorActivity.java b/src/com/android/contacts/activities/ContactEditorActivity.java
index 800a267..293e8c0 100644
--- a/src/com/android/contacts/activities/ContactEditorActivity.java
+++ b/src/com/android/contacts/activities/ContactEditorActivity.java
@@ -50,11 +50,4 @@
                 || Intent.ACTION_EDIT.equals(action) ? getIntent().getData() : null;
         mFragment.load(action, uri, getIntent().getExtras());
     }
-
-    @Override
-    public void onBackPressed() {
-        if (mFragment != null) {
-            mFragment.save(ContactEditor.SaveMode.COMPACT, /* backPressed =*/ true);
-        }
-    }
 }
diff --git a/src/com/android/contacts/activities/ContactEditorBaseActivity.java b/src/com/android/contacts/activities/ContactEditorBaseActivity.java
index 12f1e96..a86196b 100644
--- a/src/com/android/contacts/activities/ContactEditorBaseActivity.java
+++ b/src/com/android/contacts/activities/ContactEditorBaseActivity.java
@@ -155,11 +155,8 @@
         /**
          * Saves or creates the contact based on the mode, and if successful
          * finishes the activity.
-         *
-         * @param backPressed whether the save was initiated as a result of a back button press
-         *         or because the framework stopped the editor Activity
          */
-        boolean save(int saveMode, boolean backPressed);
+        boolean save(int saveMode);
 
         /**
          * If there are no unsaved changes, just close the editor, otherwise the user is prompted
@@ -171,8 +168,7 @@
          * Invoked after the contact is saved.
          */
         void onSaveCompleted(boolean hadChanges, int saveMode, boolean saveSucceeded,
-                Uri contactLookupUri, Bundle updatedPhotos, boolean backPressed, long photoId,
-                long nameId);
+                Uri contactLookupUri);
 
         /**
          * Invoked after the contact is joined.
@@ -259,12 +255,7 @@
                     intent.getIntExtra(ContactEditorFragment.SAVE_MODE_EXTRA_KEY,
                             ContactEditor.SaveMode.CLOSE),
                     intent.getBooleanExtra(ContactSaveService.EXTRA_SAVE_SUCCEEDED, false),
-                    intent.getData(),
-                    (Bundle) intent.getParcelableExtra(ContactSaveService.EXTRA_UPDATED_PHOTOS),
-                    intent.getBooleanExtra(ContactEditorFragment.INTENT_EXTRA_SAVE_BACK_PRESSED,
-                            false),
-                    intent.getLongExtra(ContactEditorFragment.INTENT_EXTRA_PHOTO_ID, -1),
-                    intent.getLongExtra(ContactEditorFragment.INTENT_EXTRA_NAME_ID, -1));
+                    intent.getData());
         } else if (ACTION_JOIN_COMPLETED.equals(action)) {
             mFragment.onJoinCompleted(intent.getData());
         }
@@ -279,6 +270,13 @@
         return null;
     }
 
+    @Override
+    public void onBackPressed() {
+        if (mFragment != null) {
+            mFragment.revert();
+        }
+    }
+
     protected final ContactEditorBaseFragment.Listener  mFragmentListener =
             new ContactEditorBaseFragment.Listener() {
 
@@ -294,15 +292,11 @@
 
         @Override
         public void onSaveFinished(Intent resultIntent) {
-            final boolean backPressed = resultIntent == null ? false : resultIntent.getBooleanExtra(
-                    ContactEditorBaseFragment.INTENT_EXTRA_SAVE_BACK_PRESSED, false);
             if (mFinishActivityOnSaveCompleted) {
                 setResult(resultIntent == null ? RESULT_CANCELED : RESULT_OK, resultIntent);
             } else if (resultIntent != null) {
-                if (backPressed) {
-                    ImplicitIntentsUtil.startActivityInApp(ContactEditorBaseActivity.this,
-                            resultIntent);
-                }
+                ImplicitIntentsUtil.startActivityInApp(ContactEditorBaseActivity.this,
+                        resultIntent);
             }
             finish();
         }
diff --git a/src/com/android/contacts/activities/ContactSelectionActivity.java b/src/com/android/contacts/activities/ContactSelectionActivity.java
index 53bfce0..ee6173e 100644
--- a/src/com/android/contacts/activities/ContactSelectionActivity.java
+++ b/src/com/android/contacts/activities/ContactSelectionActivity.java
@@ -84,8 +84,6 @@
     private ContactsIntentResolver mIntentResolver;
     protected ContactEntryListFragment<?> mListFragment;
 
-    private boolean mIsVisible;
-
     private int mActionCode = -1;
     private boolean mIsSearchMode;
     private boolean mIsSearchSupported;
@@ -139,25 +137,6 @@
         prepareSearchViewAndActionBar();
     }
 
-    @Override
-    protected void onStart() {
-        super.onStart();
-        mIsVisible = true;
-    }
-
-    @Override
-    protected void onStop() {
-        mIsVisible = false;
-        super.onStop();
-    }
-
-    /**
-     * Returns true when the Activity is currently visible (between onStart and onStop).
-     */
-    /* package */ boolean isVisible() {
-        return mIsVisible;
-    }
-
     private void prepareSearchViewAndActionBar() {
         final ActionBar actionBar = getActionBar();
         mSearchViewContainer = LayoutInflater.from(actionBar.getThemedContext())
@@ -233,7 +212,6 @@
 
     @Override
     protected void onSaveInstanceState(Bundle outState) {
-        mIsVisible = false;
         super.onSaveInstanceState(outState);
         outState.putInt(KEY_ACTION_CODE, mActionCode);
         outState.putBoolean(KEY_SEARCH_MODE, mIsSearchMode);
@@ -449,8 +427,7 @@
             } else {
                 // Otherwise launch the full contact editor.
                 startActivityAndForwardResult(EditorIntents.createEditContactIntent(
-                        contactLookupUri, /* materialPalette =*/ null, /* photoId =*/ -1,
-                        /* nameId =*/ -1));
+                        contactLookupUri, /* materialPalette =*/ null, /* photoId =*/ -1));
             }
         }
 
@@ -506,13 +483,13 @@
     private final class PhoneNumberPickerActionListener implements
             OnPhoneNumberPickerActionListener {
         @Override
-        public void onPickPhoneNumberAction(Uri dataUri, int callInitiationType) {
+        public void onPickDataUri(Uri dataUri, int callInitiationType) {
             returnPickerResult(dataUri);
         }
 
         @Override
-        public void onCallNumberDirectly(String phoneNumber, boolean isVideoCall,
-                int callInitiationType) {
+        public void onPickPhoneNumber(String phoneNumber, boolean isVideoCall,
+                                      int callInitiationType) {
             Log.w(TAG, "Unsupported call.");
         }
 
@@ -691,7 +668,7 @@
 
     @Override
     public void onBackPressed() {
-        if (!isVisible()) {
+        if (!isSafeToCommitTransactions()) {
             return;
         }
 
diff --git a/src/com/android/contacts/activities/PeopleActivity.java b/src/com/android/contacts/activities/PeopleActivity.java
index ec78b4d..26add9d 100644
--- a/src/com/android/contacts/activities/PeopleActivity.java
+++ b/src/com/android/contacts/activities/PeopleActivity.java
@@ -142,8 +142,6 @@
     private MultiSelectContactsListFragment mAllFragment;
     private ContactTileListFragment mFavoritesFragment;
 
-    private boolean mIsVisible;
-
     /** ViewPager for swipe */
     private ViewPager mTabPager;
     private ViewPagerTabs mViewPagerTabs;
@@ -412,14 +410,6 @@
             configureFragments(!mIsRecreatedInstance);
         }
         super.onStart();
-
-        mIsVisible = true;
-    }
-
-    @Override
-    protected void onStop() {
-        mIsVisible = false;
-        super.onStop();
     }
 
     @Override
@@ -464,13 +454,6 @@
         super.onDestroy();
     }
 
-    /**
-     * Returns true when the Activity is currently visible (between onStart and onStop).
-     */
-    /* package */ boolean isVisible() {
-        return mIsVisible;
-    }
-
     private void configureFragments(boolean fromRequest) {
         if (fromRequest) {
             ContactListFilter filter = null;
@@ -1034,8 +1017,7 @@
 
         @Override
         public void onImportContactsFromFileAction() {
-            ImportExportDialogFragment.show(getFragmentManager(), areContactsAvailable(),
-                    PeopleActivity.class);
+            showImportExportDialogFragment();
         }
     }
 
@@ -1212,8 +1194,7 @@
                 deleteSelectedContacts();
                 return true;
             case R.id.menu_import_export: {
-                ImportExportDialogFragment.show(getFragmentManager(), areContactsAvailable(),
-                        PeopleActivity.class);
+                showImportExportDialogFragment();
                 return true;
             }
             case R.id.menu_clear_frequents: {
@@ -1242,6 +1223,17 @@
         return false;
     }
 
+    private void showImportExportDialogFragment(){
+        final boolean isOnFavoriteTab = mTabPagerAdapter.mCurrentPrimaryItem == mFavoritesFragment;
+        if (isOnFavoriteTab) {
+            ImportExportDialogFragment.show(getFragmentManager(), areContactsAvailable(),
+                    PeopleActivity.class, ImportExportDialogFragment.EXPORT_MODE_FAVORITES);
+        } else {
+            ImportExportDialogFragment.show(getFragmentManager(), areContactsAvailable(),
+                    PeopleActivity.class, ImportExportDialogFragment.EXPORT_MODE_ALL_CONTACTS);
+        }
+    }
+
     @Override
     public boolean onSearchRequested() { // Search key pressed.
         if (!mActionBarAdapter.isSelectionMode()) {
@@ -1355,7 +1347,7 @@
 
     @Override
     public void onBackPressed() {
-        if (!isVisible()) {
+        if (!isSafeToCommitTransactions()) {
             return;
         }
 
@@ -1371,8 +1363,6 @@
 
     @Override
     protected void onSaveInstanceState(Bundle outState) {
-        mIsVisible = false;
-
         super.onSaveInstanceState(outState);
         mActionBarAdapter.onSaveInstanceState(outState);
 
diff --git a/src/com/android/contacts/editor/CompactContactEditorFragment.java b/src/com/android/contacts/editor/CompactContactEditorFragment.java
index 287d814..e98a8de 100644
--- a/src/com/android/contacts/editor/CompactContactEditorFragment.java
+++ b/src/com/android/contacts/editor/CompactContactEditorFragment.java
@@ -25,7 +25,6 @@
 import com.android.contacts.common.model.ValuesDelta;
 import com.android.contacts.common.model.account.AccountType;
 import com.android.contacts.common.model.account.AccountWithDataSet;
-import com.android.contacts.common.util.ImplicitIntentsUtil;
 import com.android.contacts.detail.PhotoSelectionHandler;
 import com.android.contacts.util.ContactPhotoUtils;
 
@@ -41,6 +40,7 @@
 import android.view.View;
 import android.view.ViewGroup;
 import android.widget.LinearLayout;
+import android.widget.Toast;
 
 import java.io.FileNotFoundException;
 
@@ -52,6 +52,7 @@
 
     private static final String KEY_PHOTO_URI = "photo_uri";
     private static final String KEY_PHOTO_RAW_CONTACT_ID = "photo_raw_contact_id";
+    private static final String KEY_UPDATED_PHOTOS = "updated_photos";
 
     /**
      * Displays a PopupWindow with photo edit options.
@@ -80,10 +81,6 @@
                 }
                 getContent().setPhoto(bitmap);
 
-                // Clear any previously saved full resolution photos under negative raw contact IDs
-                // so that we will use the newly selected photo, instead of an old one on rotations.
-                removeNewRawContactPhotos();
-
                 // If a new photo was chosen but not yet saved,
                 // we need to update the UI immediately
                 mUpdatedPhotos.putParcelable(String.valueOf(mPhotoRawContactId), uri);
@@ -142,6 +139,7 @@
     private PhotoHandler mPhotoHandler;
     private Uri mPhotoUri;
     private long mPhotoRawContactId;
+    private Bundle mUpdatedPhotos = new Bundle();
 
     @Override
     public void onCreate(Bundle savedState) {
@@ -150,6 +148,7 @@
         if (savedState != null) {
             mPhotoUri = savedState.getParcelable(KEY_PHOTO_URI);
             mPhotoRawContactId = savedState.getLong(KEY_PHOTO_RAW_CONTACT_ID);
+            mUpdatedPhotos = savedState.getParcelable(KEY_UPDATED_PHOTOS);
         }
     }
 
@@ -159,7 +158,7 @@
 
         final View view = inflater.inflate(
                 R.layout.compact_contact_editor_fragment, container, false);
-        mContent = (LinearLayout) view.findViewById(R.id.editors);
+        mContent = (LinearLayout) view.findViewById(R.id.raw_contacts_editor_view);
         return view;
     }
 
@@ -167,6 +166,7 @@
     public void onSaveInstanceState(Bundle outState) {
         outState.putParcelable(KEY_PHOTO_URI, mPhotoUri);
         outState.putLong(KEY_PHOTO_RAW_CONTACT_ID, mPhotoRawContactId);
+        outState.putParcelable(KEY_UPDATED_PHOTOS, mUpdatedPhotos);
         super.onSaveInstanceState(outState);
     }
 
@@ -188,7 +188,7 @@
 
         // If anything was left unsaved, save it now
         if (!getActivity().isChangingConfigurations() && mStatus == Status.EDITING) {
-            save(SaveMode.RELOAD, /* backPressed =*/ false);
+            save(SaveMode.RELOAD);
         }
     }
 
@@ -209,28 +209,13 @@
         // Add input fields for the loaded Contact
         final CompactRawContactsEditorView editorView = getContent();
         editorView.setListener(this);
-        editorView.setState(mState, getMaterialPalette(), mViewIdGenerator, mPhotoId, mNameId,
-                mReadOnlyDisplayName, mHasNewContact, mIsUserProfile, mAccountWithDataSet);
-        if (mReadOnlyDisplayName != null) {
-            mReadOnlyNameEditorView = editorView.getDefaultNameEditorView();
-        }
+        editorView.setState(mState, getMaterialPalette(), mViewIdGenerator, mPhotoId,
+                mHasNewContact, mIsUserProfile, mAccountWithDataSet);
 
         // Set up the photo widget
         mPhotoHandler = createPhotoHandler();
         mPhotoRawContactId = editorView.getPhotoRawContactId();
-        if (mPhotoRawContactId < 0) {
-            // Since the raw contact IDs for new contacts are random negative numbers
-            // we consider any negative key a match
-            for (String key : mUpdatedPhotos.keySet()) {
-                try {
-                    if (Integer.parseInt(key) < 0) {
-                        editorView.setFullSizePhoto((Uri) mUpdatedPhotos.getParcelable(key));
-                        break;
-                    }
-                } catch (NumberFormatException ignored) {
-                }
-            }
-        } else if (mUpdatedPhotos.containsKey(String.valueOf(mPhotoRawContactId))) {
+        if (mUpdatedPhotos.containsKey(String.valueOf(mPhotoRawContactId))) {
             editorView.setFullSizePhoto((Uri) mUpdatedPhotos.getParcelable(
                     String.valueOf(mPhotoRawContactId)));
         }
@@ -305,16 +290,17 @@
 
     @Override
     protected void setGroupMetaData() {
-        // The compact editor does not support groups.
+        if (mGroupMetaData != null) {
+            getContent().setGroupMetaData(mGroupMetaData);
+        }
     }
 
     @Override
-    protected boolean doSaveAction(int saveMode, boolean backPressed) {
-        // Save contact. No need to pass the palette since we are finished editing after the save.
+    protected boolean doSaveAction(int saveMode) {
         final Intent intent = ContactSaveService.createSaveContactIntent(mContext, mState,
                 SAVE_MODE_EXTRA_KEY, saveMode, isEditingUserProfile(),
                 ((Activity) mContext).getClass(),
-                CompactContactEditorActivity.ACTION_SAVE_COMPLETED, mUpdatedPhotos, backPressed);
+                CompactContactEditorActivity.ACTION_SAVE_COMPLETED, mUpdatedPhotos);
         mContext.startService(intent);
 
         return true;
@@ -350,30 +336,6 @@
     }
 
     @Override
-    public void onExpandEditor() {
-        // Determine if this is an insert (new contact) or edit
-        final boolean isInsert = isInsert(getActivity().getIntent());
-
-        if (isInsert) {
-            // For inserts, prevent any changes from being saved when the base fragment is destroyed
-            mStatus = Status.CLOSING;
-        } else if (hasPendingRawContactChanges()) {
-            // Save whatever is in the form
-            save(SaveMode.CLOSE, /* backPressed =*/ false);
-        }
-
-        // Prepare an Intent to start the expanded editor
-        final Intent intent = isInsert
-                ? EditorIntents.createInsertContactIntent(mState, getDisplayName(),
-                        getPhoneticName(), mUpdatedPhotos, mNewLocalProfile)
-                : EditorIntents.createEditContactIntent(mLookupUri, getMaterialPalette(),
-                        mPhotoId, mNameId);
-        ImplicitIntentsUtil.startActivityInApp(getActivity(), intent);
-
-        getActivity().finish();
-    }
-
-    @Override
     public void onNameFieldChanged(long rawContactId, ValuesDelta valuesDelta) {
         final Activity activity = getActivity();
         if (activity == null || activity.isFinishing()) {
@@ -393,19 +355,14 @@
     }
 
     @Override
-    public String getDisplayName() {
-        final StructuredNameEditorView structuredNameEditorView =
-                getContent().getStructuredNameEditorView();
-        return structuredNameEditorView == null
-                ? null : structuredNameEditorView.getDisplayName();
-    }
-
-    @Override
-    public String getPhoneticName() {
-        final PhoneticNameEditorView phoneticNameEditorView =
-                getContent().getFirstPhoneticNameEditorView();
-        return phoneticNameEditorView == null
-                ? null : phoneticNameEditorView.getPhoneticName();
+    public void onBindEditorsFailed() {
+        final Activity activity = getActivity();
+        if (activity != null && !activity.isFinishing()) {
+            Toast.makeText(activity, R.string.compact_editor_failed_to_load,
+                    Toast.LENGTH_SHORT).show();
+            activity.setResult(Activity.RESULT_CANCELED);
+            activity.finish();
+        }
     }
 
     private CompactRawContactsEditorView getContent() {
diff --git a/src/com/android/contacts/editor/CompactKindSectionView.java b/src/com/android/contacts/editor/CompactKindSectionView.java
new file mode 100644
index 0000000..a5f159c
--- /dev/null
+++ b/src/com/android/contacts/editor/CompactKindSectionView.java
@@ -0,0 +1,502 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.contacts.editor;
+
+import android.content.Context;
+import android.database.Cursor;
+import android.provider.ContactsContract.CommonDataKinds.GroupMembership;
+import android.provider.ContactsContract.CommonDataKinds.StructuredName;
+import android.util.AttributeSet;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.ImageView;
+import android.widget.LinearLayout;
+import android.widget.TextView;
+
+import com.android.contacts.R;
+import com.android.contacts.common.model.RawContactDelta;
+import com.android.contacts.common.model.RawContactModifier;
+import com.android.contacts.common.model.ValuesDelta;
+import com.android.contacts.common.model.account.AccountType;
+import com.android.contacts.common.model.dataitem.DataKind;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Version of {@link KindSectionView} that supports multiple RawContactDeltas.
+ */
+public class CompactKindSectionView extends LinearLayout {
+
+    /**
+     * Marks a name as super primary when it is changed.
+     *
+     * This is for the case when two or more raw contacts with names are joined where neither is
+     * marked as super primary.
+     */
+    private static final class StructuredNameEditorListener implements Editor.EditorListener {
+
+        private final ValuesDelta mValuesDelta;
+        private final long mRawContactId;
+        private final CompactRawContactsEditorView.Listener mListener;
+
+        public StructuredNameEditorListener(ValuesDelta valuesDelta, long rawContactId,
+                CompactRawContactsEditorView.Listener listener) {
+            mValuesDelta = valuesDelta;
+            mRawContactId = rawContactId;
+            mListener = listener;
+        }
+
+        @Override
+        public void onRequest(int request) {
+            if (request == Editor.EditorListener.FIELD_CHANGED) {
+                mValuesDelta.setSuperPrimary(true);
+                if (mListener != null) {
+                    mListener.onNameFieldChanged(mRawContactId, mValuesDelta);
+                }
+            } else if (request == Editor.EditorListener.FIELD_TURNED_EMPTY) {
+                mValuesDelta.setSuperPrimary(false);
+            }
+        }
+
+        @Override
+        public void onDeleteRequested(Editor editor) {
+            editor.clearAllFields();
+        }
+    }
+
+    /**
+     * Clears fields when deletes are requested (on phonetic and nickename fields);
+     * does not change the number of editors.
+     */
+    private static final class OtherNameKindEditorListener implements Editor.EditorListener {
+
+        @Override
+        public void onRequest(int request) {
+        }
+
+        @Override
+        public void onDeleteRequested(Editor editor) {
+            editor.clearAllFields();
+        }
+    }
+
+    /**
+     * Updates empty fields when fields are deleted or turns empty.
+     * Whether a new empty editor is added is controlled by {@link #setShowOneEmptyEditor} and
+     * {@link #setHideWhenEmpty}.
+     */
+    private final class NonNameEditorListener implements Editor.EditorListener {
+
+        @Override
+        public void onRequest(int request) {
+            // If a field has become empty or non-empty, then check if another row
+            // can be added dynamically.
+            if (request == FIELD_TURNED_EMPTY || request == FIELD_TURNED_NON_EMPTY) {
+                updateEmptyEditors(/* shouldAnimate = */ true);
+            }
+        }
+
+        @Override
+        public void onDeleteRequested(Editor editor) {
+            if (mShowOneEmptyEditor && mEditors.getChildCount() == 1) {
+                // If there is only 1 editor in the section, then don't allow the user to
+                // delete it.  Just clear the fields in the editor.
+                editor.clearAllFields();
+            } else {
+                editor.deleteEditor();
+            }
+        }
+    }
+
+    private List<KindSectionData> mKindSectionDataList;
+    private ViewIdGenerator mViewIdGenerator;
+    private CompactRawContactsEditorView.Listener mListener;
+
+    private boolean mShowOneEmptyEditor = false;
+    private boolean mHideIfEmpty = true;
+
+    private LayoutInflater mLayoutInflater;
+    private ViewGroup mEditors;
+    private ImageView mIcon;
+
+    public CompactKindSectionView(Context context) {
+        this(context, /* attrs =*/ null);
+    }
+
+    public CompactKindSectionView(Context context, AttributeSet attrs) {
+        super(context, attrs);
+    }
+
+    @Override
+    public void setEnabled(boolean enabled) {
+        super.setEnabled(enabled);
+        if (mEditors != null) {
+            int childCount = mEditors.getChildCount();
+            for (int i = 0; i < childCount; i++) {
+                mEditors.getChildAt(i).setEnabled(enabled);
+            }
+        }
+    }
+
+    @Override
+    protected void onFinishInflate() {
+        setDrawingCacheEnabled(true);
+        setAlwaysDrawnWithCacheEnabled(true);
+
+        mLayoutInflater = (LayoutInflater) getContext().getSystemService(
+                Context.LAYOUT_INFLATER_SERVICE);
+
+        mEditors = (ViewGroup) findViewById(R.id.kind_editors);
+        mIcon = (ImageView) findViewById(R.id.kind_icon);
+    }
+
+    /**
+     * @param showOneEmptyEditor If true, we will always show one empty editor, otherwise an empty
+     *         editor will not be shown until the user enters a value.  Note, this does not apply
+     *         to name editors since those are always displayed.
+     */
+    public void setShowOneEmptyEditor(boolean showOneEmptyEditor) {
+        mShowOneEmptyEditor = showOneEmptyEditor;
+    }
+
+    /**
+     * @param hideWhenEmpty If true, the entire section will be hidden if all inputs are empty,
+     *         otherwise one empty input will always be displayed.  Note, this does not apply
+     *         to name editors since those are always displayed.
+     */
+    public void setHideWhenEmpty(boolean hideWhenEmpty) {
+        mHideIfEmpty = hideWhenEmpty;
+    }
+
+    /** Binds the given group data to every {@link GroupMembershipView}. */
+    public void setGroupMetaData(Cursor cursor) {
+        for (int i = 0; i < mEditors.getChildCount(); i++) {
+            final View view = mEditors.getChildAt(i);
+            if (view instanceof GroupMembershipView) {
+                ((GroupMembershipView) view).setGroupMetaData(cursor);
+            }
+        }
+    }
+
+    /**
+     * Binds views for the given {@link KindSectionData} list.
+     *
+     * We create a structured name and phonetic name editor for each {@link DataKind} with a
+     * {@link }StructuredName#CONTENT_ITEM_TYPE} mime type.  The number and order of editors are
+     * rendered as they are given to {@link #setState}.
+     *
+     * Empty name editors are never added and at least one structured name editor is always
+     * displayed, even if it is empty.
+     */
+    public void setState(List<KindSectionData> kindSectionDataList,
+            ViewIdGenerator viewIdGenerator, CompactRawContactsEditorView.Listener listener) {
+        mKindSectionDataList = kindSectionDataList;
+        mViewIdGenerator = viewIdGenerator;
+        mListener = listener;
+
+        // Set the icon using the first DataKind
+        final DataKind dataKind = mKindSectionDataList.isEmpty()
+                ? null : mKindSectionDataList.get(0).getDataKind();
+        if (dataKind != null) {
+            mIcon.setImageDrawable(EditorUiUtils.getMimeTypeDrawable(getContext(),
+                    dataKind.mimeType));
+            if (mIcon.getDrawable() != null) {
+                mIcon.setContentDescription(dataKind.titleRes == -1 || dataKind.titleRes == 0
+                        ? "" : getResources().getString(dataKind.titleRes));
+            }
+        }
+
+        rebuildFromState();
+
+        updateEmptyEditors(/* shouldAnimate = */ false);
+    }
+
+    private void rebuildFromState() {
+        mEditors.removeAllViews();
+
+        for (KindSectionData kindSectionData : mKindSectionDataList) {
+            final String mimeType = kindSectionData.getDataKind().mimeType;
+            if (StructuredName.CONTENT_ITEM_TYPE.equals(mimeType)) {
+                for (ValuesDelta valuesDelta : kindSectionData.getValuesDeltas()) {
+                    addNameEditorViews(kindSectionData.getAccountType(),
+                            valuesDelta, kindSectionData.getRawContactDelta());
+                }
+            } else if (GroupMembership.CONTENT_ITEM_TYPE.equals(mimeType)) {
+                addGroupEditorView(kindSectionData.getRawContactDelta(),
+                        kindSectionData.getDataKind());
+            } else {
+                final Editor.EditorListener editorListener = kindSectionData.isNicknameDataKind()
+                        ? new OtherNameKindEditorListener() : new NonNameEditorListener();
+                for (ValuesDelta valuesDelta : kindSectionData.getValuesDeltas()) {
+                    addNonNameEditorView(kindSectionData.getRawContactDelta(),
+                            kindSectionData.getDataKind(), valuesDelta, editorListener);
+                }
+            }
+        }
+    }
+
+    private void addNameEditorViews(AccountType accountType,
+            ValuesDelta valuesDelta, RawContactDelta rawContactDelta) {
+        final boolean readOnly = !accountType.areContactsWritable();
+
+        if (readOnly) {
+            final View nameView = mLayoutInflater.inflate(
+                    R.layout.structured_name_readonly_editor_view, mEditors,
+                    /* attachToRoot =*/ false);
+
+            // Display name
+            ((TextView) nameView.findViewById(R.id.display_name))
+                    .setText(valuesDelta.getDisplayName());
+
+            // Account type info
+            final LinearLayout accountTypeLayout = (LinearLayout)
+                    nameView.findViewById(R.id.account_type);
+            accountTypeLayout.setVisibility(View.VISIBLE);
+            ((ImageView) accountTypeLayout.findViewById(R.id.account_type_icon))
+                    .setImageDrawable(accountType.getDisplayIcon(getContext()));
+            ((TextView) accountTypeLayout.findViewById(R.id.account_type_name))
+                    .setText(accountType.getDisplayLabel(getContext()));
+
+            mEditors.addView(nameView);
+            return;
+        }
+
+        // Structured name
+        final StructuredNameEditorView nameView = (StructuredNameEditorView) mLayoutInflater
+                .inflate(R.layout.structured_name_editor_view, mEditors, /* attachToRoot =*/ false);
+        nameView.setEditorListener(new StructuredNameEditorListener(valuesDelta,
+                rawContactDelta.getRawContactId(), mListener));
+        nameView.setDeletable(false);
+        nameView.setValues(
+                accountType.getKindForMimetype(DataKind.PSEUDO_MIME_TYPE_DISPLAY_NAME),
+                valuesDelta, rawContactDelta, readOnly, mViewIdGenerator);
+
+        // Correct start margin since there is a second icon in the structured name layout
+        nameView.findViewById(R.id.kind_icon).setVisibility(View.GONE);
+        mEditors.addView(nameView);
+
+        // Phonetic name
+        final PhoneticNameEditorView phoneticNameView = (PhoneticNameEditorView) mLayoutInflater
+                .inflate(R.layout.phonetic_name_editor_view, mEditors, /* attachToRoot =*/ false);
+        phoneticNameView.setEditorListener(new OtherNameKindEditorListener());
+        phoneticNameView.setDeletable(false);
+        phoneticNameView.setValues(
+                accountType.getKindForMimetype(DataKind.PSEUDO_MIME_TYPE_PHONETIC_NAME),
+                valuesDelta, rawContactDelta, readOnly, mViewIdGenerator);
+
+        // Fix the start margin for phonetic name views
+        final LinearLayout.LayoutParams layoutParams = new LinearLayout.LayoutParams(
+                LinearLayout.LayoutParams.MATCH_PARENT, LinearLayout.LayoutParams.WRAP_CONTENT);
+        layoutParams.setMargins(0, 0, 0, 0);
+        phoneticNameView.setLayoutParams(layoutParams);
+        mEditors.addView(phoneticNameView);
+    }
+
+    private void addGroupEditorView(RawContactDelta rawContactDelta, DataKind dataKind) {
+        final GroupMembershipView view = (GroupMembershipView) mLayoutInflater.inflate(
+                R.layout.item_group_membership, mEditors, /* attachToRoot =*/ false);
+        view.setKind(dataKind);
+        view.setEnabled(isEnabled());
+        view.setState(rawContactDelta);
+
+        // Correct start margin since there is a second icon in the group layout
+        view.findViewById(R.id.kind_icon).setVisibility(View.GONE);
+
+        mEditors.addView(view);
+    }
+
+    private View addNonNameEditorView(RawContactDelta rawContactDelta, DataKind dataKind,
+            ValuesDelta valuesDelta, Editor.EditorListener editorListener) {
+        // Inflate the layout
+        final View view = mLayoutInflater.inflate(
+                EditorUiUtils.getLayoutResourceId(dataKind.mimeType), mEditors, false);
+        view.setEnabled(isEnabled());
+
+        // Hide the types drop downs until the associated edit field is focused
+        if (view instanceof LabeledEditorView) {
+            ((LabeledEditorView) view).setHideTypeInitially(true);
+        }
+
+        if (view instanceof Editor) {
+            final Editor editor = (Editor) view;
+            editor.setDeletable(true);
+            editor.setEditorListener(editorListener);
+            editor.setValues(dataKind, valuesDelta, rawContactDelta, !dataKind.editable,
+                    mViewIdGenerator);
+        }
+        mEditors.addView(view);
+
+        return view;
+    }
+
+    /**
+     * Updates the editors being displayed to the user removing extra empty
+     * {@link Editor}s, so there is only max 1 empty {@link Editor} view at a time.
+     * If there is only 1 empty editor and {@link #setHideWhenEmpty} was set to true,
+     * then the entire section is hidden.
+     */
+    public void updateEmptyEditors(boolean shouldAnimate) {
+        final boolean isNameKindSection = mKindSectionDataList.get(0).isNameDataKind();
+        final boolean isGroupKindSection = GroupMembership.CONTENT_ITEM_TYPE.equals(
+                mKindSectionDataList.get(0).getDataKind().mimeType);
+
+        if (isNameKindSection) {
+            // The name kind section is always visible
+            setVisibility(VISIBLE);
+
+            updateEmptyNameEditors(shouldAnimate);
+        } else if (isGroupKindSection) {
+            // Check whether metadata has been bound for all group views
+            for (int i = 0; i < mEditors.getChildCount(); i++) {
+                final View view = mEditors.getChildAt(i);
+                if (view instanceof GroupMembershipView
+                        && !((GroupMembershipView) view).wasGroupMetaDataBound()) {
+                    setVisibility(GONE);
+                    return;
+                }
+            }
+            // Check that the user has selected to display all fields
+            if (mHideIfEmpty) {
+                setVisibility(GONE);
+                return;
+            }
+            setVisibility(VISIBLE);
+
+            // We don't check the emptiness of the group views
+        } else {
+            // Determine if the entire kind section should be visible
+            final int editorCount = mEditors.getChildCount();
+            final List<View> emptyEditors = getEmptyEditors();
+            if (editorCount == emptyEditors.size() && mHideIfEmpty) {
+                setVisibility(GONE);
+                return;
+            }
+            setVisibility(VISIBLE);
+
+            updateEmptyNonNameEditors(shouldAnimate);
+        }
+    }
+
+    private void updateEmptyNameEditors(boolean shouldAnimate) {
+        boolean isEmptyNameEditorVisible = false;
+
+        for (int i = 0; i < mEditors.getChildCount(); i++) {
+            final View view = mEditors.getChildAt(i);
+            if (!(view instanceof Editor)) continue; // Skip read-only names
+            final Editor editor = (Editor) view;
+            if (view instanceof StructuredNameEditorView) {
+                // We always show one empty structured name view
+                if (editor.isEmpty()) {
+                    if (isEmptyNameEditorVisible) {
+                        // If we're already showing an empty editor then hide any other empties
+                        if (mHideIfEmpty) {
+                            view.setVisibility(View.GONE);
+                        }
+                    } else {
+                        isEmptyNameEditorVisible = true;
+                    }
+                } else {
+                    showView(view, shouldAnimate);
+                    isEmptyNameEditorVisible = true;
+                }
+            } else {
+                // For phonetic names and nicknames, which can't be added, just show or hide them
+                if (mHideIfEmpty && editor.isEmpty()) {
+                    hideView(view);
+                } else {
+                    showView(view, /* shouldAnimate =*/ false); // Animation here causes jank
+                }
+            }
+        }
+    }
+
+    private void updateEmptyNonNameEditors(boolean shouldAnimate) {
+        // Prune excess empty editors
+        final List<View> emptyEditors = getEmptyEditors();
+        if (emptyEditors.size() > 1) {
+            // If there is more than 1 empty editor, then remove it from the list of editors.
+            int deleted = 0;
+            for (final View view : emptyEditors) {
+                // If no child {@link View}s are being focused on within this {@link View}, then
+                // remove this empty editor. We can assume that at least one empty editor has
+                // focus. One way to get two empty editors is by deleting characters from a
+                // non-empty editor, in which case this editor has focus.  Another way is if
+                // there is more values delta so we must also count number of editors deleted.
+                if (view.findFocus() == null) {
+                    deleteView(view, shouldAnimate);
+                    deleted++;
+                    if (deleted == emptyEditors.size() - 1) break;
+                }
+            }
+            return;
+        }
+        // Determine if we should add a new empty editor
+        final DataKind dataKind = mKindSectionDataList.get(0).getDataKind();
+        if (dataKind == null // There is nothing we can do.
+                // We have already reached the maximum number of editors, don't add any more.
+                || (dataKind.typeOverallMax == mEditors.getChildCount()
+                        && dataKind.typeOverallMax != 0)
+                // We have already reached the maximum number of empty editors, don't add any more.
+                || emptyEditors.size() == 1) {
+            return;
+        }
+        // Add a new empty editor
+        if (mShowOneEmptyEditor) {
+            final RawContactDelta rawContactDelta =
+                    mKindSectionDataList.get(0).getRawContactDelta();
+            final ValuesDelta values = RawContactModifier.insertChild(rawContactDelta, dataKind);
+            final View view = addNonNameEditorView(rawContactDelta, dataKind, values,
+                    new NonNameEditorListener());
+            showView(view, shouldAnimate);
+        }
+    }
+
+    private void hideView(View view) {
+        view.setVisibility(View.GONE);
+    }
+
+    private void deleteView(View view, boolean shouldAnimate) {
+        if (shouldAnimate) {
+            final Editor editor = (Editor) view;
+            editor.deleteEditor();
+        } else {
+            mEditors.removeView(view);
+        }
+    }
+
+    private void showView(View view, boolean shouldAnimate) {
+        if (shouldAnimate) {
+            view.setVisibility(View.GONE);
+            EditorAnimator.getInstance().showFieldFooter(view);
+        } else {
+            view.setVisibility(View.VISIBLE);
+        }
+    }
+
+    private List<View> getEmptyEditors() {
+        final List<View> emptyEditors = new ArrayList<>();
+        for (int i = 0; i < mEditors.getChildCount(); i++) {
+            final View view = mEditors.getChildAt(i);
+            if (view instanceof Editor && ((Editor) view).isEmpty()) {
+                emptyEditors.add(view);
+            }
+        }
+        return emptyEditors;
+    }
+}
diff --git a/src/com/android/contacts/editor/CompactPhotoEditorView.java b/src/com/android/contacts/editor/CompactPhotoEditorView.java
index 5f3e9af..783e044 100644
--- a/src/com/android/contacts/editor/CompactPhotoEditorView.java
+++ b/src/com/android/contacts/editor/CompactPhotoEditorView.java
@@ -138,7 +138,7 @@
      * RawContactDelta underlying this view is not modified in any way.  Using this method allows
      * you to show one photo (from a read-only contact, for example) and yet have a different
      * raw contact updated when a new photo is set (from the new raw contact created and attached
-     * to the read-only contact). See go/editing-read-only-contacts
+     * to the read-only contact).
      */
     public void setPhoto(ValuesDelta valuesDelta) {
         if (valuesDelta == null) {
diff --git a/src/com/android/contacts/editor/CompactRawContactsEditorView.java b/src/com/android/contacts/editor/CompactRawContactsEditorView.java
index 693b8fd..9d81276 100644
--- a/src/com/android/contacts/editor/CompactRawContactsEditorView.java
+++ b/src/com/android/contacts/editor/CompactRawContactsEditorView.java
@@ -23,7 +23,6 @@
 import com.android.contacts.common.model.RawContactModifier;
 import com.android.contacts.common.model.ValuesDelta;
 import com.android.contacts.common.model.account.AccountType;
-import com.android.contacts.common.model.account.AccountType.EditField;
 import com.android.contacts.common.model.account.AccountWithDataSet;
 import com.android.contacts.common.model.dataitem.DataKind;
 import com.android.contacts.common.util.AccountsListAdapter;
@@ -32,14 +31,25 @@
 import com.android.contacts.util.UiClosables;
 
 import android.content.Context;
+import android.database.Cursor;
 import android.graphics.Bitmap;
 import android.net.Uri;
+import android.os.Parcel;
+import android.os.Parcelable;
 import android.provider.ContactsContract.CommonDataKinds.Email;
+import android.provider.ContactsContract.CommonDataKinds.Event;
 import android.provider.ContactsContract.CommonDataKinds.GroupMembership;
+import android.provider.ContactsContract.CommonDataKinds.Im;
 import android.provider.ContactsContract.CommonDataKinds.Nickname;
+import android.provider.ContactsContract.CommonDataKinds.Note;
+import android.provider.ContactsContract.CommonDataKinds.Organization;
 import android.provider.ContactsContract.CommonDataKinds.Phone;
 import android.provider.ContactsContract.CommonDataKinds.Photo;
+import android.provider.ContactsContract.CommonDataKinds.Relation;
+import android.provider.ContactsContract.CommonDataKinds.SipAddress;
 import android.provider.ContactsContract.CommonDataKinds.StructuredName;
+import android.provider.ContactsContract.CommonDataKinds.StructuredPostal;
+import android.provider.ContactsContract.CommonDataKinds.Website;
 import android.text.TextUtils;
 import android.util.AttributeSet;
 import android.util.Log;
@@ -53,30 +63,31 @@
 import android.widget.TextView;
 
 import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.Comparator;
 import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
 import java.util.Objects;
+import java.util.TreeSet;
 
 /**
- * View to display information from multiple {@link RawContactDelta}s grouped together
- * (e.g. all the phone numbers from a {@link com.android.contacts.common.model.Contact} together.
+ * View to display information from multiple {@link RawContactDelta}s grouped together.
  */
 public class CompactRawContactsEditorView extends LinearLayout implements View.OnClickListener {
 
     private static final String TAG = "CompactEditorView";
 
+    private static final KindSectionDataMapEntryComparator
+            KIND_SECTION_DATA_MAP_ENTRY_COMPARATOR = new KindSectionDataMapEntryComparator();
+
     /**
      * Callbacks for hosts of {@link CompactRawContactsEditorView}s.
      */
     public interface Listener {
 
         /**
-         * Invoked when the compact editor should be expanded to show all fields.
-         */
-        public void onExpandEditor();
-
-        /**
          * Invoked when the structured name editor field has changed.
          *
          * @param rawContactId The raw contact ID from the underlying {@link RawContactDelta}.
@@ -93,49 +104,187 @@
          */
         public void onRebindEditorsForNewContact(RawContactDelta oldState,
                 AccountWithDataSet oldAccount, AccountWithDataSet newAccount);
+
+        /**
+         * Invoked when no editors could be bound for the contact.
+         */
+        public void onBindEditorsFailed();
+    }
+
+    /** Used to sort entire kind sections. */
+    private static final class KindSectionDataMapEntryComparator implements
+            Comparator<Map.Entry<String,List<KindSectionData>>> {
+
+        final MimeTypeComparator mMimeTypeComparator = new MimeTypeComparator();
+
+        @Override
+        public int compare(Map.Entry<String, List<KindSectionData>> entry1,
+                Map.Entry<String, List<KindSectionData>> entry2) {
+            if (entry1 == entry2) return 0;
+            if (entry1 == null) return -1;
+            if (entry2 == null) return 1;
+
+            final String mimeType1 = entry1.getKey();
+            final String mimeType2 = entry2.getKey();
+
+            return mMimeTypeComparator.compare(mimeType1, mimeType2);
+        }
     }
 
     /**
-     * Marks a name as super primary when it is changed.
-     *
-     * This is for the case when two or more raw contacts with names are joined where neither is
-     * marked as super primary.  If the user hits back (which causes a save) after changing the
-     * name that was arbitrarily displayed, we want that to be the name that is used.
-     *
-     * Should only be set when a super primary name does not already exist since we only show
-     * one name field.
+     * Sorts kinds roughly the same as quick contacts; we diverge in the following ways:
+     * <ol>
+     *     <li>All names are together at the top.</li>
+     *     <li>IM is moved up after addresses</li>
+     *     <li>SIP addresses are moved to below phone numbers</li>
+     *     <li>Group membership is placed at the end</li>
+     * </ol>
      */
-    static final class NameEditorListener implements Editor.EditorListener {
+    private static final class MimeTypeComparator implements Comparator<String> {
 
-        private final ValuesDelta mValuesDelta;
-        private final long mRawContactId;
-        private final Listener mListener;
-
-        public NameEditorListener(ValuesDelta valuesDelta, long rawContactId,
-                Listener listener) {
-            mValuesDelta = valuesDelta;
-            mRawContactId = rawContactId;
-            mListener = listener;
-        }
+        private static final List<String> MIME_TYPE_ORDER = Arrays.asList(new String[] {
+                StructuredName.CONTENT_ITEM_TYPE,
+                Nickname.CONTENT_ITEM_TYPE,
+                Organization.CONTENT_ITEM_TYPE,
+                Phone.CONTENT_ITEM_TYPE,
+                SipAddress.CONTENT_ITEM_TYPE,
+                Email.CONTENT_ITEM_TYPE,
+                StructuredPostal.CONTENT_ITEM_TYPE,
+                Im.CONTENT_ITEM_TYPE,
+                Website.CONTENT_ITEM_TYPE,
+                Event.CONTENT_ITEM_TYPE,
+                Relation.CONTENT_ITEM_TYPE,
+                Note.CONTENT_ITEM_TYPE,
+                GroupMembership.CONTENT_ITEM_TYPE
+        });
 
         @Override
-        public void onRequest(int request) {
-            if (request == Editor.EditorListener.FIELD_CHANGED) {
-                mValuesDelta.setSuperPrimary(true);
-                if (mListener != null) {
-                    mListener.onNameFieldChanged(mRawContactId, mValuesDelta);
-                }
-            } else if (request == Editor.EditorListener.FIELD_TURNED_EMPTY) {
-                mValuesDelta.setSuperPrimary(false);
-            }
-        }
+        public int compare(String mimeType1, String mimeType2) {
+            if (mimeType1 == mimeType2) return 0;
+            if (mimeType1 == null) return -1;
+            if (mimeType2 == null) return 1;
 
-        @Override
-        public void onDeleteRequested(Editor editor) {
+            int index1 = MIME_TYPE_ORDER.indexOf(mimeType1);
+            int index2 = MIME_TYPE_ORDER.indexOf(mimeType2);
+
+            // Fallback to alphabetical ordering of the mime type if both are not found
+            if (index1 < 0 && index2 < 0) return mimeType1.compareTo(mimeType2);
+            if (index1 < 0) return 1;
+            if (index2 < 0) return -1;
+
+            return index1 < index2 ? -1 : 1;
         }
     }
 
-    private Listener mListener;
+    /**
+     * Sorts primary accounts and google account types before others.
+     */
+    private static final class EditorComparator implements Comparator<KindSectionData> {
+
+        private RawContactDeltaComparator mRawContactDeltaComparator;
+
+        private EditorComparator(Context context) {
+            mRawContactDeltaComparator = new RawContactDeltaComparator(context);
+        }
+
+        @Override
+        public int compare(KindSectionData kindSectionData1, KindSectionData kindSectionData2) {
+            if (kindSectionData1 == kindSectionData2) return 0;
+            if (kindSectionData1 == null) return -1;
+            if (kindSectionData2 == null) return 1;
+
+            final RawContactDelta rawContactDelta1 = kindSectionData1.getRawContactDelta();
+            final RawContactDelta rawContactDelta2 = kindSectionData2.getRawContactDelta();
+
+            if (rawContactDelta1 == rawContactDelta2) return 0;
+            if (rawContactDelta1 == null) return -1;
+            if (rawContactDelta2 == null) return 1;
+
+            return mRawContactDeltaComparator.compare(rawContactDelta1, rawContactDelta2);
+        }
+    }
+
+    /**
+     * Sorts primary account names first, followed by google account types, and other account
+     * types last.  For names from the same account we order structured names before nicknames,
+     * but still keep names from the same account together.
+     */
+    private static final class NameEditorComparator implements Comparator<KindSectionData> {
+
+        private RawContactDeltaComparator mRawContactDeltaComparator;
+        private MimeTypeComparator mMimeTypeComparator;
+        private RawContactDelta mPrimaryRawContactDelta;
+
+        private NameEditorComparator(Context context, RawContactDelta primaryRawContactDelta) {
+            mRawContactDeltaComparator = new RawContactDeltaComparator(context);
+            mMimeTypeComparator = new MimeTypeComparator();
+            mPrimaryRawContactDelta = primaryRawContactDelta;
+        }
+
+        @Override
+        public int compare(KindSectionData kindSectionData1, KindSectionData kindSectionData2) {
+            if (kindSectionData1 == kindSectionData2) return 0;
+            if (kindSectionData1 == null) return -1;
+            if (kindSectionData2 == null) return 1;
+
+            final RawContactDelta rawContactDelta1 = kindSectionData1.getRawContactDelta();
+            final RawContactDelta rawContactDelta2 = kindSectionData2.getRawContactDelta();
+
+            if (rawContactDelta1 == rawContactDelta2) return 0;
+            if (rawContactDelta1 == null) return -1;
+            if (rawContactDelta2 == null) return 1;
+
+            final boolean isRawContactDelta1Primary =
+                mPrimaryRawContactDelta.equals(rawContactDelta1);
+            final boolean isRawContactDelta2Primary =
+                mPrimaryRawContactDelta.equals(rawContactDelta2);
+
+            // If both names are from the primary account, sort my by mime type
+            if (isRawContactDelta1Primary && isRawContactDelta2Primary) {
+                final String mimeType1 = kindSectionData1.getDataKind().mimeType;
+                final String mimeType2 = kindSectionData2.getDataKind().mimeType;
+                return mMimeTypeComparator.compare(mimeType1, mimeType2);
+            }
+
+            // The primary account name should be before all others
+            if (isRawContactDelta1Primary) return -1;
+            if (isRawContactDelta2Primary) return 1;
+
+           return mRawContactDeltaComparator.compare(rawContactDelta1, rawContactDelta2);
+        }
+    }
+
+    public static class SavedState extends BaseSavedState {
+
+        public static final Parcelable.Creator<SavedState> CREATOR =
+                new Parcelable.Creator<SavedState>() {
+                    public SavedState createFromParcel(Parcel in) {
+                        return new SavedState(in);
+                    }
+                    public SavedState[] newArray(int size) {
+                        return new SavedState[size];
+                    }
+                };
+
+        private boolean mIsExpanded;
+
+        public SavedState(Parcelable superState) {
+            super(superState);
+        }
+
+        private SavedState(Parcel in) {
+            super(in);
+            mIsExpanded = in.readInt() != 0;
+        }
+
+        @Override
+        public void writeToParcel(Parcel out, int flags) {
+            super.writeToParcel(out, flags);
+            out.writeInt(mIsExpanded ? 1 : 0);
+        }
+    }
+
+    private CompactRawContactsEditorView.Listener mListener;
 
     private AccountTypeManager mAccountTypeManager;
     private LayoutInflater mLayoutInflater;
@@ -143,12 +292,11 @@
     private ViewIdGenerator mViewIdGenerator;
     private MaterialColorMapUtils.MaterialPalette mMaterialPalette;
     private long mPhotoId;
-    private long mNameId;
-    private String mReadOnlyDisplayName;
     private boolean mHasNewContact;
     private boolean mIsUserProfile;
     private AccountWithDataSet mPrimaryAccount;
     private RawContactDelta mPrimaryRawContactDelta;
+    private Map<String,List<KindSectionData>> mKindSectionDataMap = new HashMap<>();
 
     // Account header
     private View mAccountHeaderContainer;
@@ -161,23 +309,14 @@
     private TextView mAccountSelectorType;
     private TextView mAccountSelectorName;
 
-    private CompactPhotoEditorView mPhoto;
-    private ViewGroup mNames;
-    private ViewGroup mPhoneticNames;
-    private ViewGroup mNicknames;
-    private ViewGroup mPhoneNumbers;
-    private ViewGroup mEmails;
-    private ViewGroup mOtherTypes;
-    private Map<String,LinearLayout> mOtherTypesMap = new HashMap<>();
+    private CompactPhotoEditorView mPhotoView;
+    private ViewGroup mKindSectionViews;
+    private Map<String,List<CompactKindSectionView>> mKindSectionViewsMap = new HashMap<>();
     private View mMoreFields;
 
-    // The ValuesDelta for the non super primary name that was displayed to the user.
-    private ValuesDelta mNameValuesDelta;
-
+    private boolean mIsExpanded;
     private long mPhotoRawContactId;
 
-    private StructuredNameEditorView mDefaultNameEditorView;
-
     public CompactRawContactsEditorView(Context context) {
         super(context);
     }
@@ -212,43 +351,48 @@
         mAccountSelectorType = (TextView) findViewById(R.id.account_type_selector);
         mAccountSelectorName = (TextView) findViewById(R.id.account_name_selector);
 
-        mPhoto = (CompactPhotoEditorView) findViewById(R.id.photo_editor);
-        mNames = (LinearLayout) findViewById(R.id.names);
-        mPhoneticNames = (LinearLayout) findViewById(R.id.phonetic_names);
-        mNicknames = (LinearLayout) findViewById(R.id.nicknames);
-        mPhoneNumbers = (LinearLayout) findViewById(R.id.phone_numbers);
-        mEmails = (LinearLayout) findViewById(R.id.emails);
-        mOtherTypes = (LinearLayout) findViewById(R.id.other);
+        mPhotoView = (CompactPhotoEditorView) findViewById(R.id.photo_editor);
+        mKindSectionViews = (LinearLayout) findViewById(R.id.kind_section_views);
         mMoreFields = findViewById(R.id.more_fields);
         mMoreFields.setOnClickListener(this);
     }
 
     @Override
     public void onClick(View view) {
-        if (view.getId() == R.id.more_fields && mListener != null ) {
-            mListener.onExpandEditor();
+        if (view.getId() == R.id.more_fields) {
+            showMoreFields();
+            updateMoreFieldsButton();
         }
     }
 
     @Override
     public void setEnabled(boolean enabled) {
         super.setEnabled(enabled);
-        setEnabled(enabled, mNames);
-        setEnabled(enabled, mPhoneticNames);
-        setEnabled(enabled, mNicknames);
-        setEnabled(enabled, mPhoneNumbers);
-        setEnabled(enabled, mEmails);
-        for (Map.Entry<String,LinearLayout> otherType : mOtherTypesMap.entrySet()) {
-            setEnabled(enabled, otherType.getValue());
+        final int childCount = mKindSectionViews.getChildCount();
+        for (int i = 0; i < childCount; i++) {
+            mKindSectionViews.getChildAt(i).setEnabled(enabled);
         }
     }
 
-    private void setEnabled(boolean enabled, ViewGroup viewGroup) {
-        if (viewGroup != null) {
-            final int childCount = viewGroup.getChildCount();
-            for (int i = 0; i < childCount; i++) {
-                viewGroup.getChildAt(i).setEnabled(enabled);
-            }
+    @Override
+    public Parcelable onSaveInstanceState() {
+        final Parcelable superState = super.onSaveInstanceState();
+        final SavedState savedState = new SavedState(superState);
+        savedState.mIsExpanded = mIsExpanded;
+        return savedState;
+    }
+
+    @Override
+    public void onRestoreInstanceState(Parcelable state) {
+        if(!(state instanceof SavedState)) {
+            super.onRestoreInstanceState(state);
+            return;
+        }
+        final SavedState savedState = (SavedState) state;
+        super.onRestoreInstanceState(savedState.getSuperState());
+        mIsExpanded = savedState.mIsExpanded;
+        if (mIsExpanded && !mKindSectionDataMap.isEmpty()) {
+            showMoreFields();
         }
     }
 
@@ -256,28 +400,28 @@
      * Pass through to {@link CompactPhotoEditorView#setPhotoHandler}.
      */
     public void setPhotoHandler(PhotoHandler photoHandler) {
-        mPhoto.setPhotoHandler(photoHandler);
+        mPhotoView.setPhotoHandler(photoHandler);
     }
 
     /**
      * Pass through to {@link CompactPhotoEditorView#setPhoto}.
      */
     public void setPhoto(Bitmap bitmap) {
-        mPhoto.setPhoto(bitmap);
+        mPhotoView.setPhoto(bitmap);
     }
 
     /**
      * Pass through to {@link CompactPhotoEditorView#setFullSizedPhoto(Uri)}.
      */
     public void setFullSizePhoto(Uri photoUri) {
-        mPhoto.setFullSizedPhoto(photoUri);
+        mPhotoView.setFullSizedPhoto(photoUri);
     }
 
     /**
      * Pass through to {@link CompactPhotoEditorView#isWritablePhotoSet}.
      */
     public boolean isWritablePhotoSet() {
-        return mPhoto.isWritablePhotoSet();
+        return mPhotoView.isWritablePhotoSet();
     }
 
     /**
@@ -287,58 +431,41 @@
         return mPhotoRawContactId;
     }
 
-    public StructuredNameEditorView getDefaultNameEditorView() {
-        return mDefaultNameEditorView;
-    }
-
-    public StructuredNameEditorView getStructuredNameEditorView() {
-        // We only ever show one StructuredName
-        return mNames.getChildCount() == 0
-                ? null : (StructuredNameEditorView) mNames.getChildAt(0);
-    }
-
-    public PhoneticNameEditorView getFirstPhoneticNameEditorView() {
-        // There should only ever be one phonetic name
-        return mPhoneticNames.getChildCount() == 0
-                ? null : (PhoneticNameEditorView) mPhoneticNames.getChildAt(0);
-    }
-
     public View getAggregationAnchorView() {
-        // Since there is only one structured name we can just return it as the anchor for
-        // the aggregation suggestions popup
-        if (mNames.getChildCount() == 0) {
-            return null;
+        final List<CompactKindSectionView> kindSectionViews = getKindSectionViews(
+                StructuredName.CONTENT_ITEM_TYPE);
+        if (!kindSectionViews.isEmpty()) {
+            return mKindSectionViews.getChildAt(0).findViewById(R.id.anchor_view);
         }
-        return mNames.getChildAt(0).findViewById(R.id.anchor_view);
+        return null;
     }
 
-    /**
-     * @param readOnlyDisplayName The display name to set on the new raw contact created in order
-     *         to edit a read-only contact.
-     */
+    public void setGroupMetaData(Cursor groupMetaData) {
+        final List<CompactKindSectionView> kindSectionViews = getKindSectionViews(
+                GroupMembership.CONTENT_ITEM_TYPE);
+        for (CompactKindSectionView kindSectionView : kindSectionViews) {
+            kindSectionView.setGroupMetaData(groupMetaData);
+        }
+
+        // Groups metadata may be set after we restore expansion state so just do it again
+        if (mIsExpanded) {
+            showMoreFields();
+        }
+        updateMoreFieldsButton();
+    }
+
     public void setState(RawContactDeltaList rawContactDeltas,
             MaterialColorMapUtils.MaterialPalette materialPalette, ViewIdGenerator viewIdGenerator,
-            long photoId, long nameId, String readOnlyDisplayName, boolean hasNewContact,
-            boolean isUserProfile, AccountWithDataSet primaryAccount) {
-        mNames.removeAllViews();
-        mPhoneticNames.removeAllViews();
-        mNicknames.removeAllViews();
-        mPhoneNumbers.removeAllViews();
-        mEmails.removeAllViews();
-        mOtherTypes.removeAllViews();
-        mOtherTypesMap.clear();
+            long photoId, boolean hasNewContact, boolean isUserProfile,
+            AccountWithDataSet primaryAccount) {
+        // Clear previous state and reset views
+        mKindSectionDataMap.clear();
+        mKindSectionViews.removeAllViews();
+        mMoreFields.setVisibility(View.VISIBLE);
 
-        if (rawContactDeltas == null || rawContactDeltas.isEmpty()) {
-            return;
-        }
-
-        mViewIdGenerator = viewIdGenerator;
-        setId(mViewIdGenerator.getId(rawContactDeltas.get(0), /* dataKind =*/ null,
-                /* valuesDelta =*/ null, ViewIdGenerator.NO_VIEW_INDEX));
         mMaterialPalette = materialPalette;
+        mViewIdGenerator = viewIdGenerator;
         mPhotoId = photoId;
-        mNameId = nameId;
-        mReadOnlyDisplayName = readOnlyDisplayName;
         mHasNewContact = hasNewContact;
         mIsUserProfile = isUserProfile;
         mPrimaryAccount = primaryAccount;
@@ -347,48 +474,39 @@
         }
         vlog("state: primary " + mPrimaryAccount);
 
-        vlog("state: setting compact editor state from " + rawContactDeltas);
-        parseRawContactDeltas(rawContactDeltas);
-        addAccountInfo();
-        addPhotoView(rawContactDeltas, viewIdGenerator, photoId, readOnlyDisplayName);
-        addStructuredNameView(rawContactDeltas, nameId, readOnlyDisplayName);
-        addEditorViews(rawContactDeltas);
-        updateKindEditorEmptyFields(mPhoneNumbers);
-        updateKindEditorIcons(mPhoneNumbers);
-        updateKindEditorEmptyFields(mEmails);
-        updateKindEditorIcons(mEmails);
-        for (Map.Entry<String,LinearLayout> otherTypes : mOtherTypesMap.entrySet()) {
-            updateKindEditorIcons(otherTypes.getValue());
+        // Parse the given raw contact deltas
+        if (rawContactDeltas == null || rawContactDeltas.isEmpty()) {
+            elog("No raw contact deltas");
+            if (mListener != null) mListener.onBindEditorsFailed();
+            return;
         }
+        parseRawContactDeltas(rawContactDeltas, mPrimaryAccount);
+        if (mKindSectionDataMap.isEmpty()) {
+            elog("No kind section data parsed from RawContactDelta(s)");
+            if (mListener != null) mListener.onBindEditorsFailed();
+            return;
+        }
+
+        // Setup the view
+        setId(mViewIdGenerator.getId(rawContactDeltas.get(0), /* dataKind =*/ null,
+                /* valuesDelta =*/ null, ViewIdGenerator.NO_VIEW_INDEX));
+        addAccountInfo();
+        addPhotoView();
+        addKindSectionViews();
+
+        updateMoreFieldsButton();
     }
 
-    private void parseRawContactDeltas(RawContactDeltaList rawContactDeltas) {
-        // Get the raw contact delta for the primary account (the one displayed at the top)
-        if (mPrimaryAccount == null || TextUtils.isEmpty(mPrimaryAccount.name)
-                || !TextUtils.isEmpty(mReadOnlyDisplayName)) {
-            // Use the first writable contact if this is an insert for a read-only contact.
-            // In this case we can assume the first writable raw contact is the newly created one
-            // because inserts have a raw contact delta list of size 1 and read-only contacts have
-            // a list of size 2.
-            for (RawContactDelta rawContactDelta : rawContactDeltas) {
-                if (!rawContactDelta.isVisible()) continue;
-                final AccountType accountType = rawContactDelta.getAccountType(mAccountTypeManager);
-                if (accountType != null && accountType.areContactsWritable()) {
-                    vlog("parse: using first writable raw contact as primary");
-                    mPrimaryRawContactDelta = rawContactDelta;
-                    break;
-                }
-            }
-        } else {
+    private void parseRawContactDeltas(RawContactDeltaList rawContactDeltas,
+            AccountWithDataSet primaryAccount) {
+        if (primaryAccount != null) {
             // Use the first writable contact that matches the primary account
             for (RawContactDelta rawContactDelta : rawContactDeltas) {
                 if (!rawContactDelta.isVisible()) continue;
                 final AccountType accountType = rawContactDelta.getAccountType(mAccountTypeManager);
-                if (accountType != null && accountType.areContactsWritable()
-                        && Objects.equals(mPrimaryAccount.name, rawContactDelta.getAccountName())
-                        && Objects.equals(mPrimaryAccount.type, rawContactDelta.getAccountType())
-                        && Objects.equals(mPrimaryAccount.dataSet, rawContactDelta.getDataSet())) {
-                    vlog("parse: matched the primary account raw contact");
+                if (accountType == null || !accountType.areContactsWritable()) continue;
+                if (matchesAccount(primaryAccount, rawContactDelta)) {
+                    vlog("parse: matched primary account raw contact");
                     mPrimaryRawContactDelta = rawContactDelta;
                     break;
                 }
@@ -406,140 +524,83 @@
                 }
             }
         }
+
+        if (mPrimaryRawContactDelta != null) {
+            RawContactModifier.ensureKindExists(mPrimaryRawContactDelta,
+                    mPrimaryRawContactDelta.getAccountType(mAccountTypeManager),
+                    StructuredName.CONTENT_ITEM_TYPE);
+            RawContactModifier.ensureKindExists(mPrimaryRawContactDelta,
+                    mPrimaryRawContactDelta.getAccountType(mAccountTypeManager),
+                    Photo.CONTENT_ITEM_TYPE);
+        }
+
+        // Build the kind section data list map
+        vlog("parse: " + rawContactDeltas.size() + " rawContactDelta(s)");
+        for (int j = 0; j < rawContactDeltas.size(); j++) {
+            final RawContactDelta rawContactDelta = rawContactDeltas.get(j);
+            vlog("parse: " + j + " rawContactDelta" + rawContactDelta);
+            if (rawContactDelta == null || !rawContactDelta.isVisible()) continue;
+            final AccountType accountType = rawContactDelta.getAccountType(mAccountTypeManager);
+            if (accountType == null) continue;
+            final List<DataKind> dataKinds = accountType.getSortedDataKinds();
+            final int dataKindSize = dataKinds == null ? 0 : dataKinds.size();
+            vlog("parse: " + dataKindSize + " dataKinds(s)");
+            for (int i = 0; i < dataKindSize; i++) {
+                final DataKind dataKind = dataKinds.get(i);
+                if (dataKind == null || !dataKind.editable) {
+                    vlog("parse: " + i + " " + dataKind.mimeType + " dropped read-only");
+                    continue;
+                }
+                final String mimeType = dataKind.mimeType;
+
+                // Skip psuedo mime types
+                if (DataKind.PSEUDO_MIME_TYPE_DISPLAY_NAME.equals(mimeType)
+                        || DataKind.PSEUDO_MIME_TYPE_PHONETIC_NAME.equals(mimeType)) {
+                    vlog("parse: " + i + " " + dataKind.mimeType + " dropped pseudo type");
+                    continue;
+                }
+
+                final List<KindSectionData> kindSectionDataList =
+                        getKindSectionDataList(mimeType);
+                final KindSectionData kindSectionData =
+                        new KindSectionData(accountType, dataKind, rawContactDelta);
+                kindSectionDataList.add(kindSectionData);
+
+                // Note we must create nickname entries
+                if (Nickname.CONTENT_ITEM_TYPE.equals(mimeType)
+                        && kindSectionData.getValuesDeltas().isEmpty()) {
+                    RawContactModifier.insertChild(rawContactDelta, dataKind);
+                }
+
+                vlog("parse: " + i + " " + dataKind.mimeType + " " +
+                        kindSectionData.getValuesDeltas().size() + " value(s)");
+            }
+        }
     }
 
-    private void addPhotoView(RawContactDeltaList rawContactDeltas,
-            ViewIdGenerator viewIdGenerator, long photoId, String readOnlyDisplayName) {
-        // If we're editing a read-only contact, the display name from the read-only
-        // contact is non empty and we can use it determine whether to back the photo editor with
-        // the empty new raw contact delta.  See go/editing-read-only-contacts
-        final boolean readOnlyContact = !TextUtils.isEmpty(readOnlyDisplayName);
-        if (readOnlyContact) {
-            for (RawContactDelta rawContactDelta : rawContactDeltas) {
-                if (!rawContactDelta.isVisible()) continue;
-                final AccountType accountType = rawContactDelta.getAccountType(mAccountTypeManager);
-
-                // Make sure we have a photo
-                RawContactModifier.ensureKindExists(
-                        rawContactDelta, accountType, Photo.CONTENT_ITEM_TYPE);
-
-                final DataKind dataKind = accountType.getKindForMimetype(Photo.CONTENT_ITEM_TYPE);
-                if (accountType.areContactsWritable()) {
-                    for (ValuesDelta valuesDelta : rawContactDelta.getMimeEntries(
-                            Photo.CONTENT_ITEM_TYPE)) {
-                        if (valuesDelta != null) {
-                            // Break the loop but don't return because we need to keep going to
-                            // in order to show the photo from the read-only contact.
-                            mPhotoRawContactId = rawContactDelta.getRawContactId();
-                            mPhoto.setValues(dataKind, valuesDelta, rawContactDelta,
-                                    /* readOnly =*/ false, mMaterialPalette, viewIdGenerator);
-                            break;
-                        }
-                    }
-                }
-            }
+    private List<KindSectionData> getKindSectionDataList(String mimeType) {
+        // Put structured names and nicknames together
+        mimeType = Nickname.CONTENT_ITEM_TYPE.equals(mimeType)
+                ? StructuredName.CONTENT_ITEM_TYPE : mimeType;
+        List<KindSectionData> kindSectionDataList = mKindSectionDataMap.get(mimeType);
+        if (kindSectionDataList == null) {
+            kindSectionDataList = new ArrayList<>();
+            mKindSectionDataMap.put(mimeType, kindSectionDataList);
         }
+        return kindSectionDataList;
+    }
 
-        // Look for a match for the photo ID that was passed in
-        for (RawContactDelta rawContactDelta : rawContactDeltas) {
-            if (!rawContactDelta.isVisible()) continue;
-            final AccountType accountType = rawContactDelta.getAccountType(mAccountTypeManager);
-
-            // Make sure we have a photo
-            RawContactModifier.ensureKindExists(
-                    rawContactDelta, accountType, Photo.CONTENT_ITEM_TYPE);
-
-            final DataKind dataKind = accountType.getKindForMimetype(Photo.CONTENT_ITEM_TYPE);
-            if (dataKind != null && dataKind.editable) {
-                for (ValuesDelta valuesDelta
-                        : rawContactDelta.getMimeEntries(Photo.CONTENT_ITEM_TYPE)) {
-                    if (valuesDelta != null && valuesDelta.getId() != null
-                            && valuesDelta.getId().equals(photoId)) {
-                        if (readOnlyContact) {
-                            mPhoto.setPhoto(valuesDelta);
-                        } else {
-                            mPhotoRawContactId = rawContactDelta.getRawContactId();
-                            mPhoto.setValues(dataKind, valuesDelta, rawContactDelta,
-                                    !accountType.areContactsWritable(),
-                                    mMaterialPalette, viewIdGenerator);
-                        }
-                        return;
-                    }
-                }
-            }
-        }
-
-        // Look for a non-empty super primary photo
-        for (RawContactDelta rawContactDelta : rawContactDeltas) {
-            if (!rawContactDelta.isVisible()) continue;
-            final AccountType accountType = rawContactDelta.getAccountType(mAccountTypeManager);
-            final DataKind dataKind = accountType.getKindForMimetype(Photo.CONTENT_ITEM_TYPE);
-            if (dataKind != null && dataKind.editable) {
-                final ValuesDelta valuesDelta = getNonEmptySuperPrimaryValuesDeltas(
-                        rawContactDelta, Photo.CONTENT_ITEM_TYPE, dataKind);
-                if (valuesDelta != null) {
-                    if (readOnlyContact) {
-                        mPhoto.setPhoto(valuesDelta);
-                    } else {
-                        mPhotoRawContactId = rawContactDelta.getRawContactId();
-                        mPhoto.setValues(dataKind, valuesDelta, rawContactDelta,
-                                !accountType.areContactsWritable(), mMaterialPalette,
-                                viewIdGenerator);
-                    }
-                    return;
-                }
-            }
-        }
-        // We didn't find a non-empty super primary photo, use the first non-empty one
-        for (RawContactDelta rawContactDelta : rawContactDeltas) {
-            if (!rawContactDelta.isVisible()) continue;
-            final AccountType accountType = rawContactDelta.getAccountType(mAccountTypeManager);
-            final DataKind dataKind = accountType.getKindForMimetype(Photo.CONTENT_ITEM_TYPE);
-            if (dataKind != null && dataKind.editable) {
-                final List<ValuesDelta> valuesDeltas = getNonEmptyValuesDeltas(
-                        rawContactDelta, Photo.CONTENT_ITEM_TYPE, dataKind);
-                if (valuesDeltas != null && !valuesDeltas.isEmpty()) {
-                    if (readOnlyContact) {
-                        mPhoto.setPhoto(valuesDeltas.get(0));
-                    } else {
-                        mPhotoRawContactId = rawContactDelta.getRawContactId();
-                        mPhoto.setValues(dataKind, valuesDeltas.get(0), rawContactDelta,
-                                !accountType.areContactsWritable(), mMaterialPalette,
-                                viewIdGenerator);
-                    }
-                    return;
-                }
-            }
-        }
-        // No suitable non-empty photo
-        for (RawContactDelta rawContactDelta : rawContactDeltas) {
-            if (!rawContactDelta.isVisible()) continue;
-            final AccountType accountType = rawContactDelta.getAccountType(mAccountTypeManager);
-            final DataKind dataKind = accountType.getKindForMimetype(Photo.CONTENT_ITEM_TYPE);
-            if (dataKind != null && dataKind.editable) {
-                final ValuesDelta valuesDelta = rawContactDelta.getSuperPrimaryEntry(
-                        dataKind.mimeType, /* forceSelection =*/ true);
-                if (valuesDelta != null) {
-                    if (readOnlyContact) {
-                        mPhoto.setPhoto(valuesDelta);
-                    } else {
-                        mPhotoRawContactId = rawContactDelta.getRawContactId();
-                        mPhoto.setValues(dataKind, valuesDelta, rawContactDelta,
-                                !accountType.areContactsWritable(), mMaterialPalette,
-                                viewIdGenerator);
-                    }
-                    return;
-                }
-            }
-        }
-        // Should not happen since we ensure the kind exists but if we unexpectedly get here
-        // we must remove the photo section so that it does not take up the entire view
-        mPhoto.setVisibility(View.GONE);
+    /** Whether the given RawContactDelta belong to the given account. */
+    private boolean matchesAccount(AccountWithDataSet accountWithDataSet,
+            RawContactDelta rawContactDelta) {
+        if (accountWithDataSet == null) return false;
+        return Objects.equals(accountWithDataSet.name, rawContactDelta.getAccountName())
+                && Objects.equals(accountWithDataSet.type, rawContactDelta.getAccountType())
+                && Objects.equals(accountWithDataSet.dataSet, rawContactDelta.getDataSet());
     }
 
     private void addAccountInfo() {
         if (mPrimaryRawContactDelta == null) {
-            vlog("account info: hidden because no raw contact delta");
             mAccountHeaderContainer.setVisibility(View.GONE);
             mAccountSelectorContainer.setVisibility(View.GONE);
             return;
@@ -626,384 +687,179 @@
         });
     }
 
-    private void addStructuredNameView(RawContactDeltaList rawContactDeltas, long nameId,
-            String readOnlyDisplayName) {
-        // If we're editing a read-only contact we want to display the name from the read-only
-        // contact in a structured name editor backed by the new raw contact that was created.
-        // The new raw contact is writable and merging it with the read-only contact allows us
-        // to edit the read-only contact. See go/editing-read-only-contacts
-        if (!TextUtils.isEmpty(readOnlyDisplayName)) {
-            for (RawContactDelta rawContactDelta : rawContactDeltas) {
-                if (!rawContactDelta.isVisible()) continue;
-                final AccountType accountType = rawContactDelta.getAccountType(mAccountTypeManager);
+    private void addPhotoView() {
+        // Get the kind section data and values delta that will back the photo view
+        Pair<KindSectionData,ValuesDelta> pair = getPrimaryKindSectionData(mPhotoId);
+        if (pair == null) {
+            wlog("photo: no kind section data parsed");
+            return;
+        }
+        final KindSectionData kindSectionData = pair.first;
+        final ValuesDelta valuesDelta = pair.second;
 
-                // Make sure we have a structured name
-                RawContactModifier.ensureKindExists(
-                        rawContactDelta, accountType, StructuredName.CONTENT_ITEM_TYPE);
-
-                if (accountType.areContactsWritable()) {
-                    for (ValuesDelta valuesDelta : rawContactDelta.getMimeEntries(
-                            StructuredName.CONTENT_ITEM_TYPE)) {
-                        if (valuesDelta != null) {
-                            mNameValuesDelta = valuesDelta;
-                            final NameEditorListener nameEditorListener = new NameEditorListener(
-                                    mNameValuesDelta, rawContactDelta.getRawContactId(), mListener);
-                            final StructuredNameEditorView nameEditorView =
-                                    inflateStructuredNameEditorView(mNames, accountType,
-                                            mNameValuesDelta, rawContactDelta, nameEditorListener,
-                                            !accountType.areContactsWritable());
-                            nameEditorView.setDisplayName(readOnlyDisplayName);
-                            mNames.addView(nameEditorView);
-                            mDefaultNameEditorView = nameEditorView;
-                            return;
-                        }
-                    }
-                }
-            }
+        // If we're editing a read-only contact we want to display the photo from the
+        // read-only contact in a photo editor backed by the new raw contact
+        // that was created.
+        if (mHasNewContact) {
+            mPhotoRawContactId = mPrimaryRawContactDelta == null
+                    ? null : mPrimaryRawContactDelta.getRawContactId();
         }
 
-        // Look for a match for the name ID that was passed in
-        for (RawContactDelta rawContactDelta : rawContactDeltas) {
-            if (!rawContactDelta.isVisible()) continue;
-            final AccountType accountType = rawContactDelta.getAccountType(mAccountTypeManager);
-
-            // Make sure we have a structured name
-            RawContactModifier.ensureKindExists(
-                    rawContactDelta, accountType, StructuredName.CONTENT_ITEM_TYPE);
-
-            // Note use of pseudo mime type to get the DataKind and StructuredName to get value
-            final DataKind dataKind = accountType.getKindForMimetype(
-                    DataKind.PSEUDO_MIME_TYPE_DISPLAY_NAME);
-            if (dataKind == null || !dataKind.editable) continue;
-
-            for (ValuesDelta valuesDelta : rawContactDelta.getMimeEntries(
-                    StructuredName.CONTENT_ITEM_TYPE)) {
-                if (valuesDelta != null && valuesDelta.getId() != null
-                        && valuesDelta.getId().equals(nameId)) {
-                    mNameValuesDelta = valuesDelta;
-                    final NameEditorListener nameEditorListener = new NameEditorListener(
-                            mNameValuesDelta, rawContactDelta.getRawContactId(), mListener);
-                    mNames.addView(inflateStructuredNameEditorView(mNames, accountType,
-                            mNameValuesDelta, rawContactDelta, nameEditorListener,
-                            !accountType.areContactsWritable()));
-                    return;
-                }
-            }
-        }
-        // Look for a super primary name
-        for (RawContactDelta rawContactDelta : rawContactDeltas) {
-            if (!rawContactDelta.isVisible()) continue;
-            final AccountType accountType = rawContactDelta.getAccountType(mAccountTypeManager);
-
-            final DataKind dataKind = accountType.getKindForMimetype(
-                    DataKind.PSEUDO_MIME_TYPE_DISPLAY_NAME);
-            if (dataKind == null || !dataKind.editable) continue;
-
-            final ValuesDelta superPrimaryValuesDelta = getNonEmptySuperPrimaryValuesDeltas(
-                    rawContactDelta, StructuredName.CONTENT_ITEM_TYPE, dataKind);
-            if (superPrimaryValuesDelta != null) {
-                // Our first preference is for a non-empty super primary name
-                final NameEditorListener nameEditorListener = new NameEditorListener(
-                        superPrimaryValuesDelta, rawContactDelta.getRawContactId(), mListener);
-                mNames.addView(inflateStructuredNameEditorView(mNames, accountType,
-                        superPrimaryValuesDelta, rawContactDelta, nameEditorListener,
-                        !accountType.areContactsWritable()));
-                return;
-            }
-        }
-        // We didn't find a super primary name
-        for (RawContactDelta rawContactDelta : rawContactDeltas) {
-            if (!rawContactDelta.isVisible()) continue;
-            final AccountType accountType = rawContactDelta.getAccountType(mAccountTypeManager);
-
-            final DataKind dataKind = accountType.getKindForMimetype(
-                    DataKind.PSEUDO_MIME_TYPE_DISPLAY_NAME);
-            if (dataKind == null || !dataKind.editable) continue;
-
-            final List<ValuesDelta> nonEmptyValuesDeltas = getNonEmptyValuesDeltas(
-                    rawContactDelta, StructuredName.CONTENT_ITEM_TYPE, dataKind);
-            if (nonEmptyValuesDeltas != null && !nonEmptyValuesDeltas.isEmpty()) {
-                // Take the first non-empty name
-                mNameValuesDelta = nonEmptyValuesDeltas.get(0);
-                final NameEditorListener nameEditorListener = new NameEditorListener(
-                        mNameValuesDelta, rawContactDelta.getRawContactId(), mListener);
-                mNames.addView(inflateStructuredNameEditorView(mNames, accountType,
-                        mNameValuesDelta, rawContactDelta, nameEditorListener,
-                        !accountType.areContactsWritable()));
-                return;
-            }
-        }
-        for (RawContactDelta rawContactDelta : rawContactDeltas) {
-            if (!rawContactDelta.isVisible()) continue;
-            final AccountType accountType = rawContactDelta.getAccountType(mAccountTypeManager);
-
-            final DataKind dataKind = accountType.getKindForMimetype(
-                    DataKind.PSEUDO_MIME_TYPE_DISPLAY_NAME);
-            if (dataKind == null || !dataKind.editable) continue;
-
-            // Fall back to the first entry
-            final ArrayList<ValuesDelta> valuesDeltas = rawContactDelta.getMimeEntries(
-                    StructuredName.CONTENT_ITEM_TYPE);
-            if (valuesDeltas != null && !valuesDeltas.isEmpty()) {
-                mNameValuesDelta = valuesDeltas.get(0);
-                final NameEditorListener nameEditorListener = new NameEditorListener(
-                        mNameValuesDelta, rawContactDelta.getRawContactId(), mListener);
-                mNames.addView(inflateStructuredNameEditorView(mNames, accountType,
-                        mNameValuesDelta, rawContactDelta, nameEditorListener,
-                        !accountType.areContactsWritable()));
-                return;
-            }
-        }
+        mPhotoRawContactId = kindSectionData.getRawContactDelta().getRawContactId();
+        mPhotoView.setValues(kindSectionData.getDataKind(), valuesDelta,
+                kindSectionData.getRawContactDelta(),
+                !kindSectionData.getAccountType().areContactsWritable(), mMaterialPalette,
+                mViewIdGenerator);
     }
 
-    private void addEditorViews(RawContactDeltaList rawContactDeltas) {
-        for (RawContactDelta rawContactDelta : rawContactDeltas) {
-            if (!rawContactDelta.isVisible()) continue;
-            final AccountType accountType = rawContactDelta.getAccountType(mAccountTypeManager);
+    private Pair<KindSectionData,ValuesDelta> getPrimaryKindSectionData(long id) {
+        final String mimeType = Photo.CONTENT_ITEM_TYPE;
+        final List<KindSectionData> kindSectionDataList = mKindSectionDataMap.get(mimeType);
 
-            for (DataKind dataKind : accountType.getSortedDataKinds()) {
-                if (!dataKind.editable) continue;
-
-                final String mimeType = dataKind.mimeType;
-                vlog(mimeType + " " + dataKind.fieldList.size() + " field(s)");
-                if (Photo.CONTENT_ITEM_TYPE.equals(mimeType)
-                        || StructuredName.CONTENT_ITEM_TYPE.equals(mimeType)
-                        || GroupMembership.CONTENT_ITEM_TYPE.equals(mimeType)) {
-                    // Photos and structured names are handled separately and
-                    // group membership is not supported
-                    continue;
-                } else if (DataKind.PSEUDO_MIME_TYPE_PHONETIC_NAME.equals(mimeType)) {
-                    // Only add phonetic names if there is a non-empty one. Note the use of
-                    // StructuredName mimeType below, even though we matched a pseudo mime type.
-                    final ValuesDelta valuesDelta = rawContactDelta.getSuperPrimaryEntry(
-                            StructuredName.CONTENT_ITEM_TYPE, /* forceSelection =*/ true);
-                    if (hasNonEmptyValue(dataKind, valuesDelta)) {
-                        mPhoneticNames.addView(inflatePhoneticNameEditorView(
-                                mPhoneticNames, accountType, valuesDelta, rawContactDelta));
-                    }
-                } else if (Nickname.CONTENT_ITEM_TYPE.equals(mimeType)) {
-                    // Add all non-empty nicknames
-                    final List<ValuesDelta> valuesDeltas = getNonEmptyValuesDeltas(
-                            rawContactDelta, Nickname.CONTENT_ITEM_TYPE, dataKind);
-                    if (valuesDeltas != null && !valuesDeltas.isEmpty()) {
-                        for (ValuesDelta valuesDelta : valuesDeltas) {
-                            mNicknames.addView(inflateNicknameEditorView(
-                                    mNicknames, dataKind, valuesDelta, rawContactDelta));
-                        }
-                    }
-                } else if (Phone.CONTENT_ITEM_TYPE.equals(mimeType)) {
-                    final KindSectionView kindSectionView =
-                            inflateKindSectionView(mPhoneNumbers, dataKind, rawContactDelta);
-                    kindSectionView.setListener(new KindSectionView.Listener() {
-                        @Override
-                        public void onDeleteRequested(Editor editor) {
-                            if (kindSectionView.getEditorCount() == 1) {
-                                kindSectionView.markForRemoval();
-                                EditorAnimator.getInstance().removeEditorView(kindSectionView);
-                            } else {
-                                editor.deleteEditor();
-                            }
-                            updateKindEditorEmptyFields(mPhoneNumbers);
-                            updateKindEditorIcons(mPhoneNumbers);
-                        }
-                    });
-                    mPhoneNumbers.addView(kindSectionView);
-                } else if (Email.CONTENT_ITEM_TYPE.equals(mimeType)) {
-                    final KindSectionView kindSectionView =
-                            inflateKindSectionView(mEmails, dataKind, rawContactDelta);
-                    kindSectionView.setListener(new KindSectionView.Listener() {
-                        @Override
-                        public void onDeleteRequested(Editor editor) {
-                            if (kindSectionView.getEditorCount() == 1) {
-                                kindSectionView.markForRemoval();
-                                EditorAnimator.getInstance().removeEditorView(kindSectionView);
-                            } else {
-                                editor.deleteEditor();
-                            }
-                            updateKindEditorEmptyFields(mEmails);
-                            updateKindEditorIcons(mEmails);
-                        }
-                    });
-                    mEmails.addView(kindSectionView);
-                } else if (hasNonEmptyValuesDelta(rawContactDelta, mimeType, dataKind)) {
-                    final LinearLayout otherTypeViewGroup;
-                    if (mOtherTypesMap.containsKey(mimeType)) {
-                        otherTypeViewGroup = mOtherTypesMap.get(mimeType);
-                    } else {
-                        otherTypeViewGroup = new LinearLayout(getContext());
-                        otherTypeViewGroup.setOrientation(LinearLayout.VERTICAL);
-                        mOtherTypes.addView(otherTypeViewGroup);
-                        mOtherTypesMap.put(mimeType, otherTypeViewGroup);
-                    }
-                    final KindSectionView kindSectionView =
-                            inflateKindSectionView(mOtherTypes, dataKind, rawContactDelta);
-                    kindSectionView.setListener(new KindSectionView.Listener() {
-                        @Override
-                        public void onDeleteRequested(Editor editor) {
-                            if (kindSectionView.getEditorCount() == 1) {
-                                kindSectionView.markForRemoval();
-                                EditorAnimator.getInstance().removeEditorView(kindSectionView);
-                            } else {
-                                editor.deleteEditor();
-                            }
-                            updateKindEditorIcons(otherTypeViewGroup);
-                        }
-                    });
-                    otherTypeViewGroup.addView(kindSectionView);
+        KindSectionData resultKindSectionData = null;
+        ValuesDelta resultValuesDelta = null;
+        if (id > 0) {
+            // Look for a match for the ID that was passed in
+            for (KindSectionData kindSectionData : kindSectionDataList) {
+                resultValuesDelta = kindSectionData.getValuesDeltaById(id);
+                if (resultValuesDelta != null) {
+                    vlog("photo: matched kind section data by ID");
+                    resultKindSectionData = kindSectionData;
+                    break;
                 }
             }
         }
-    }
-
-    private static void updateKindEditorEmptyFields(ViewGroup viewGroup) {
-        KindSectionView lastVisibleKindSectionView = null;
-        for (int i = 0; i < viewGroup.getChildCount(); i++) {
-            if (viewGroup.getChildAt(i).getVisibility() == View.VISIBLE) {
-                lastVisibleKindSectionView = (KindSectionView) viewGroup.getChildAt(i);
-            }
-        }
-        // Only the last editor should show an empty editor
-        if (lastVisibleKindSectionView != null) {
-            // Hide all empty kind sections except the last one
-            for (int i = 0; i < viewGroup.getChildCount(); i++) {
-                final KindSectionView kindSectionView = (KindSectionView) viewGroup.getChildAt(i);
-                if (kindSectionView != lastVisibleKindSectionView
-                        && kindSectionView.areAllEditorsEmpty()) {
-                    kindSectionView.setVisibility(View.GONE);
+        if (resultKindSectionData == null) {
+            // Look for a super primary photo
+            for (KindSectionData kindSectionData : kindSectionDataList) {
+                resultValuesDelta = kindSectionData.getSuperPrimaryValuesDelta();
+                if (resultValuesDelta != null) {
+                    wlog("photo: matched super primary kind section data");
+                    resultKindSectionData = kindSectionData;
+                    break;
                 }
             }
-            // Set the last editor to show empty editor fields
-            lastVisibleKindSectionView.setShowOneEmptyEditor(true);
-            lastVisibleKindSectionView.updateEmptyEditors(/* shouldAnimate =*/ false);
         }
+        if (resultKindSectionData == null) {
+            // Fall back to the first non-empty value
+            for (KindSectionData kindSectionData : kindSectionDataList) {
+                resultValuesDelta = kindSectionData.getFirstNonEmptyValuesDelta();
+                if (resultValuesDelta != null) {
+                    vlog("photo: using first non empty value");
+                    resultKindSectionData = kindSectionData;
+                    break;
+                }
+            }
+        }
+        if (resultKindSectionData == null || resultValuesDelta == null) {
+            final List<ValuesDelta> valuesDeltaList = kindSectionDataList.get(0).getValuesDeltas();
+            if (valuesDeltaList != null && !valuesDeltaList.isEmpty()) {
+                vlog("photo: falling back to first empty entry");
+                resultValuesDelta = valuesDeltaList.get(0);
+                resultKindSectionData = kindSectionDataList.get(0);
+            }
+        }
+        return resultKindSectionData != null && resultValuesDelta != null
+                ? new Pair<>(resultKindSectionData, resultValuesDelta) : null;
     }
 
-    private static void updateKindEditorIcons(ViewGroup viewGroup) {
-        // Show the icon on the first visible kind editor
-        boolean iconVisible = false;
-        for (int i = 0; i < viewGroup.getChildCount(); i++) {
-            final KindSectionView kindSectionView = (KindSectionView) viewGroup.getChildAt(i);
-            if (kindSectionView.getVisibility() != View.VISIBLE
-                    || kindSectionView.isMarkedForRemoval()) {
+    private void addKindSectionViews() {
+        // Sort the kinds
+        final TreeSet<Map.Entry<String,List<KindSectionData>>> entries =
+                new TreeSet<>(KIND_SECTION_DATA_MAP_ENTRY_COMPARATOR);
+        entries.addAll(mKindSectionDataMap.entrySet());
+
+        vlog("kind: " + entries.size() + " kindSection(s)");
+        int i = -1;
+        for (Map.Entry<String, List<KindSectionData>> entry : entries) {
+            i++;
+
+            final String mimeType = entry.getKey();
+            final List<KindSectionData> kindSectionDataList = entry.getValue();
+
+            // Ignore mime types that we've already handled
+            if (Photo.CONTENT_ITEM_TYPE.equals(mimeType)) {
+                vlog("kind: " + i + " " + mimeType + " dropped");
                 continue;
             }
-            if (!iconVisible) {
-                kindSectionView.setIconVisibility(true);
-                iconVisible = true;
-            } else {
-                kindSectionView.setIconVisibility(false);
+
+            if (kindSectionDataList != null && !kindSectionDataList.isEmpty()) {
+                vlog("kind: " + i + " " + mimeType + ": " + kindSectionDataList.size() +
+                        " kindSectionData(s)");
+
+                final CompactKindSectionView kindSectionView = inflateKindSectionView(
+                        mKindSectionViews, kindSectionDataList, mimeType);
+                mKindSectionViews.addView(kindSectionView);
+
+                // Keep a pointer to all the KindSectionsViews for each mimeType
+                getKindSectionViews(mimeType).add(kindSectionView);
             }
         }
     }
 
-    private static boolean hasNonEmptyValuesDelta(RawContactDelta rawContactDelta,
-            String mimeType, DataKind dataKind) {
-        return !getNonEmptyValuesDeltas(rawContactDelta, mimeType, dataKind).isEmpty();
+    private List<CompactKindSectionView> getKindSectionViews(String mimeType) {
+        List<CompactKindSectionView> kindSectionViews = mKindSectionViewsMap.get(mimeType);
+        if (kindSectionViews == null) {
+            kindSectionViews = new ArrayList<>();
+            mKindSectionViewsMap.put(mimeType, kindSectionViews);
+        }
+        return kindSectionViews;
     }
 
-    private static ValuesDelta getNonEmptySuperPrimaryValuesDeltas(RawContactDelta rawContactDelta,
-            String mimeType, DataKind dataKind) {
-        for (ValuesDelta valuesDelta : getNonEmptyValuesDeltas(
-                rawContactDelta, mimeType, dataKind)) {
-            if (valuesDelta.isSuperPrimary()) {
-                return valuesDelta;
+    private CompactKindSectionView inflateKindSectionView(ViewGroup viewGroup,
+            List<KindSectionData> kindSectionDataList, String mimeType) {
+        final CompactKindSectionView kindSectionView = (CompactKindSectionView)
+                mLayoutInflater.inflate(R.layout.compact_item_kind_section, viewGroup,
+                        /* attachToRoot =*/ false);
+
+        if (Phone.CONTENT_ITEM_TYPE.equals(mimeType)
+                || Email.CONTENT_ITEM_TYPE.equals(mimeType)) {
+            // Phone numbers and email addresses are always displayed,
+            // even if they are empty
+            kindSectionView.setHideWhenEmpty(false);
+        }
+
+        // Since phone numbers and email addresses displayed even if they are empty,
+        // they will be the only types you add new values to initially for new contacts
+        kindSectionView.setShowOneEmptyEditor(true);
+
+        // Sort so the editors wind up in the order we want
+        if (StructuredName.CONTENT_ITEM_TYPE.equals(mimeType)) {
+            Collections.sort(kindSectionDataList, new NameEditorComparator(getContext(),
+                    mPrimaryRawContactDelta));
+        } else {
+            Collections.sort(kindSectionDataList, new EditorComparator(getContext()));
+        }
+
+        kindSectionView.setState(kindSectionDataList, mViewIdGenerator, mListener);
+
+        return kindSectionView;
+    }
+
+    private void showMoreFields() {
+        // Stop hiding empty editors and allow the user to enter values for all kinds now
+        for (int i = 0; i < mKindSectionViews.getChildCount(); i++) {
+            final CompactKindSectionView kindSectionView =
+                    (CompactKindSectionView) mKindSectionViews.getChildAt(i);
+            kindSectionView.setHideWhenEmpty(false);
+            kindSectionView.updateEmptyEditors(/* shouldAnimate =*/ true);
+        }
+        mIsExpanded = true;
+    }
+
+    private void updateMoreFieldsButton() {
+        // If any kind section views are hidden then show the link
+        for (int i = 0; i < mKindSectionViews.getChildCount(); i++) {
+            final CompactKindSectionView kindSectionView =
+                    (CompactKindSectionView) mKindSectionViews.getChildAt(i);
+            if (kindSectionView.getVisibility() == View.GONE) {
+                // Show the more fields button
+                mMoreFields.setVisibility(View.VISIBLE);
+                return;
             }
         }
-        return null;
-    }
-
-    static List<ValuesDelta> getNonEmptyValuesDeltas(RawContactDelta rawContactDelta,
-            String mimeType, DataKind dataKind) {
-        final List<ValuesDelta> result = new ArrayList<>();
-        if (rawContactDelta == null) {
-            vlog("Null RawContactDelta");
-            return result;
-        }
-        if (!rawContactDelta.hasMimeEntries(mimeType)) {
-            vlog("No ValueDeltas");
-            return result;
-        }
-        for (ValuesDelta valuesDelta : rawContactDelta.getMimeEntries(mimeType)) {
-            if (hasNonEmptyValue(dataKind, valuesDelta)) {
-                result.add(valuesDelta);
-            }
-        }
-        return result;
-    }
-
-    private static boolean hasNonEmptyValue(DataKind dataKind, ValuesDelta valuesDelta) {
-        if (valuesDelta == null) {
-            vlog("Null valuesDelta");
-            return false;
-        }
-        for (EditField editField : dataKind.fieldList) {
-            final String column = editField.column;
-            final String value = valuesDelta == null ? null : valuesDelta.getAsString(column);
-            vlog("Field " + column + " empty=" + TextUtils.isEmpty(value) + " value=" + value);
-            if (!TextUtils.isEmpty(value)) {
-                return true;
-            }
-        }
-        return false;
-    }
-
-    private StructuredNameEditorView inflateStructuredNameEditorView(ViewGroup viewGroup,
-            AccountType accountType, ValuesDelta valuesDelta, RawContactDelta rawContactDelta,
-            NameEditorListener nameEditorListener, boolean readOnly) {
-        final StructuredNameEditorView result = (StructuredNameEditorView) mLayoutInflater.inflate(
-                R.layout.structured_name_editor_view, viewGroup, /* attachToRoot =*/ false);
-        if (nameEditorListener != null) {
-            result.setEditorListener(nameEditorListener);
-        }
-        result.setDeletable(false);
-        result.setValues(
-                accountType.getKindForMimetype(DataKind.PSEUDO_MIME_TYPE_DISPLAY_NAME),
-                valuesDelta,
-                rawContactDelta,
-                readOnly,
-                mViewIdGenerator);
-        return result;
-    }
-
-    private PhoneticNameEditorView inflatePhoneticNameEditorView(ViewGroup viewGroup,
-            AccountType accountType, ValuesDelta valuesDelta, RawContactDelta rawContactDelta) {
-        final PhoneticNameEditorView result = (PhoneticNameEditorView) mLayoutInflater.inflate(
-                R.layout.phonetic_name_editor_view, viewGroup, /* attachToRoot =*/ false);
-        result.setDeletable(false);
-        result.setValues(
-                accountType.getKindForMimetype(DataKind.PSEUDO_MIME_TYPE_PHONETIC_NAME),
-                valuesDelta,
-                rawContactDelta,
-                /* readOnly =*/ false,
-                mViewIdGenerator);
-        return result;
-    }
-
-    private TextFieldsEditorView inflateNicknameEditorView(ViewGroup viewGroup, DataKind dataKind,
-            ValuesDelta valuesDelta, RawContactDelta rawContactDelta) {
-        final TextFieldsEditorView result = (TextFieldsEditorView) mLayoutInflater.inflate(
-                R.layout.nick_name_editor_view, viewGroup, /* attachToRoot =*/ false);
-        result.setDeletable(false);
-        result.setValues(
-                dataKind,
-                valuesDelta,
-                rawContactDelta,
-                /* readOnly =*/ false,
-                mViewIdGenerator);
-        return result;
-    }
-
-
-    private KindSectionView inflateKindSectionView(ViewGroup viewGroup, DataKind dataKind,
-            RawContactDelta rawContactDelta) {
-        final KindSectionView result = (KindSectionView) mLayoutInflater.inflate(
-                R.layout.item_kind_section, viewGroup, /* attachToRoot =*/ false);
-        result.setState(
-                dataKind,
-                rawContactDelta,
-                /* readOnly =*/ false,
-                mViewIdGenerator);
-        return result;
+        // Hide the more fields button
+        mMoreFields.setVisibility(View.GONE);
     }
 
     private static void vlog(String message) {
@@ -1011,4 +867,14 @@
             Log.v(TAG, message);
         }
     }
+
+    private static void wlog(String message) {
+        if (Log.isLoggable(TAG, Log.WARN)) {
+            Log.w(TAG, message);
+        }
+    }
+
+    private static void elog(String message) {
+        Log.e(TAG, message);
+    }
 }
diff --git a/src/com/android/contacts/editor/ContactEditorBaseFragment.java b/src/com/android/contacts/editor/ContactEditorBaseFragment.java
index 4e5fff4..5b341a0 100644
--- a/src/com/android/contacts/editor/ContactEditorBaseFragment.java
+++ b/src/com/android/contacts/editor/ContactEditorBaseFragment.java
@@ -113,7 +113,6 @@
     private static final String KEY_NEW_LOCAL_PROFILE = "newLocalProfile";
     private static final String KEY_MATERIAL_PALETTE = "materialPalette";
     private static final String KEY_PHOTO_ID = "photoId";
-    private static final String KEY_NAME_ID = "nameId";
 
     private static final String KEY_VIEW_ID_GENERATOR = "viewidgenerator";
 
@@ -145,12 +144,12 @@
     // Join Activity
     private static final String KEY_CONTACT_ID_FOR_JOIN = "contactidforjoin";
 
-    private static final String KEY_UPDATED_PHOTOS = "updatedPhotos";
-
     protected static final int REQUEST_CODE_JOIN = 0;
     protected static final int REQUEST_CODE_ACCOUNTS_CHANGED = 1;
     protected static final int REQUEST_CODE_PICK_RINGTONE = 2;
 
+    private static final int CURRENT_API_VERSION = android.os.Build.VERSION.SDK_INT;
+
     /**
      * An intent extra that forces the editor to add the edited contact
      * to the default group (e.g. "My Contacts").
@@ -179,33 +178,16 @@
             "material_palette_secondary_color";
 
     /**
-     * Intent key to pass a Bundle of raw contact IDs to photos URIs between the compact editor
-     * and the fully expanded one.
-     */
-    public static final String INTENT_EXTRA_UPDATED_PHOTOS = "updated_photos";
-
-    /**
      * Intent key to pass the ID of the photo to display on the editor.
      */
     public static final String INTENT_EXTRA_PHOTO_ID = "photo_id";
 
     /**
-     * Intent key to pass the ID of the name to display on the editor.
-     */
-    public static final String INTENT_EXTRA_NAME_ID = "name_id";
-
-    /**
      * Intent extra to specify a {@link ContactEditor.SaveMode}.
      */
     public static final String SAVE_MODE_EXTRA_KEY = "saveMode";
 
     /**
-     * Intent extra to specify whether the save was initiated as a result of a back button press
-     * or because the framework stopped the editor Activity.
-     */
-    public static final String INTENT_EXTRA_SAVE_BACK_PRESSED = "saveBackPressed";
-
-    /**
      * Callbacks for Activities that host contact editors Fragments.
      */
     public interface Listener {
@@ -331,7 +313,6 @@
     protected boolean mNewLocalProfile;
     protected MaterialColorMapUtils.MaterialPalette mMaterialPalette;
     protected long mPhotoId = -1;
-    protected long mNameId = -1;
 
     //
     // Helpers
@@ -382,20 +363,6 @@
     // Join Activity
     protected long mContactIdForJoin;
 
-    // Full resolution photo URIs
-    protected Bundle mUpdatedPhotos = new Bundle();
-
-    //
-    // Not saved/restored on rotates
-    //
-
-    // Used to pre-populate the editor with a display name when a user edits a read-only contact.
-    protected String mReadOnlyDisplayName;
-
-    // The name editor view for the new raw contact that was created so that the user can
-    // edit a read-only contact (to which the new raw contact was joined)
-    protected StructuredNameEditorView mReadOnlyNameEditorView;
-
     /**
      * The contact data loader listener.
      */
@@ -488,7 +455,6 @@
             mNewLocalProfile = savedState.getBoolean(KEY_NEW_LOCAL_PROFILE);
             mMaterialPalette = savedState.getParcelable(KEY_MATERIAL_PALETTE);
             mPhotoId = savedState.getLong(KEY_PHOTO_ID);
-            mNameId = savedState.getLong(KEY_NAME_ID);
 
             mRawContacts = ImmutableList.copyOf(savedState.<RawContact>getParcelableArrayList(
                     KEY_RAW_CONTACTS));
@@ -520,9 +486,6 @@
 
             // Join Activity
             mContactIdForJoin = savedState.getLong(KEY_CONTACT_ID_FOR_JOIN);
-
-            // Full resolution photo URIs
-            mUpdatedPhotos = savedState.getParcelable(KEY_UPDATED_PHOTOS);
         }
 
         // mState can still be null because it may not have have finished loading before
@@ -613,7 +576,6 @@
             outState.putParcelable(KEY_MATERIAL_PALETTE, mMaterialPalette);
         }
         outState.putLong(KEY_PHOTO_ID, mPhotoId);
-        outState.putLong(KEY_NAME_ID, mNameId);
 
         outState.putParcelable(KEY_VIEW_ID_GENERATOR, mViewIdGenerator);
 
@@ -648,9 +610,6 @@
         // Join Activity
         outState.putLong(KEY_CONTACT_ID_FOR_JOIN, mContactIdForJoin);
 
-        // Full resolution photo URIs
-        outState.putParcelable(KEY_UPDATED_PHOTOS, mUpdatedPhotos);
-
         super.onSaveInstanceState(outState);
     }
 
@@ -659,6 +618,11 @@
         super.onStop();
 
         UiClosables.closeQuietly(mAggregationSuggestionPopup);
+
+        // If anything was left unsaved, save it now but keep the editor open.
+        if (!getActivity().isChangingConfigurations() && mStatus == Status.EDITING) {
+            save(SaveMode.RELOAD);
+        }
     }
 
     @Override
@@ -716,13 +680,7 @@
     }
 
     private void onRingtonePicked(Uri pickedUri) {
-        if (pickedUri == null) {
-            mCustomRingtone = ""; // silent ringtone
-        } else if (RingtoneManager.isDefault(pickedUri)){
-            mCustomRingtone = null; // default ringtone
-        } else {
-            mCustomRingtone = pickedUri.toString();
-        }
+        mCustomRingtone = EditorUiUtils.getRingtoneStringFromUri(pickedUri, CURRENT_API_VERSION);
         Intent intent = ContactSaveService.createSetRingtone(
                 mContext, mLookupUri, mCustomRingtone);
         mContext.startService(intent);
@@ -824,7 +782,7 @@
 
         switch (item.getItemId()) {
             case R.id.menu_save:
-                return save(SaveMode.CLOSE, /* backPressed =*/ true);
+                return save(SaveMode.CLOSE);
             case R.id.menu_discard:
                 return revert();
             case R.id.menu_delete:
@@ -881,7 +839,7 @@
         }
 
         mState.markRawContactsForSplitting();
-        save(SaveMode.SPLIT, /* backPressed =*/ false);
+        save(SaveMode.SPLIT);
     }
 
     private boolean doSplitContactAction() {
@@ -899,13 +857,13 @@
         // 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()
-                && !hasPendingRawContactChanges()) {
+                && !hasPendingChanges()) {
             Toast.makeText(mContext, R.string.toast_join_with_empty_contact,
                     Toast.LENGTH_LONG).show();
             return true;
         }
 
-        return save(SaveMode.JOIN, /* backPressed =*/ false);
+        return save(SaveMode.JOIN);
     }
 
     private void doPickRingtone() {
@@ -917,17 +875,8 @@
         // Allow the user to pick a silent ringtone
         intent.putExtra(RingtoneManager.EXTRA_RINGTONE_SHOW_SILENT, true);
 
-        final Uri ringtoneUri;
-        if (mCustomRingtone != null) {
-            if ("".equals(mCustomRingtone)) { // select silent ringtone in RingtonePickerActivity
-                ringtoneUri = null;
-            } else {
-                ringtoneUri = Uri.parse(mCustomRingtone);
-            }
-        } else {
-            // Otherwise pick default ringtone Uri so that something is selected.
-            ringtoneUri = RingtoneManager.getDefaultUri(RingtoneManager.TYPE_RINGTONE);
-        }
+        final Uri ringtoneUri = EditorUiUtils.getRingtoneUriFromString(mCustomRingtone,
+                CURRENT_API_VERSION);
 
         // Put checkmark next to the current ringtone for this contact
         intent.putExtra(RingtoneManager.EXTRA_RINGTONE_EXISTING_URI, ringtoneUri);
@@ -941,7 +890,7 @@
     }
 
     @Override
-    public boolean save(int saveMode, boolean backPressed) {
+    public boolean save(int saveMode) {
         if (!hasValidState() || mStatus != Status.EDITING) {
             return false;
         }
@@ -965,31 +914,19 @@
                 return true;
             }
             onSaveCompleted(/* hadChanges =*/ false, saveMode,
-                    /* saveSucceeded =*/ mLookupUri != null, mLookupUri,
-                    /* updatedPhotos =*/ null, backPressed, mPhotoId, mNameId);
+                    /* saveSucceeded =*/ mLookupUri != null, mLookupUri);
             return true;
         }
 
         setEnabled(false);
 
-        if (isInsert(getActivity().getIntent()) && saveMode == SaveMode.COMPACT
-                && mListener != null && backPressed) {
-            // If we're coming back from the fully expanded editor and this is an insert, just
-            // pass any values entered by the user back to the compact editor without doing a save
-            final Intent resultIntent = EditorIntents.createCompactInsertContactIntent(
-                    mState, getDisplayName(), getPhoneticName(), mUpdatedPhotos, mNewLocalProfile);
-            resultIntent.putExtra(INTENT_EXTRA_SAVE_BACK_PRESSED, backPressed);
-            mListener.onSaveFinished(resultIntent);
-            return true;
-        }
-        // Otherwise this is an edit or a back press so do an actual save
-        return doSaveAction(saveMode, backPressed);
+        return doSaveAction(saveMode);
     }
 
     /**
      * Persist the accumulated editor deltas.
      */
-    abstract protected boolean doSaveAction(int saveMode, boolean backPressed);
+    abstract protected boolean doSaveAction(int saveMode);
 
     //
     // State accessor methods
@@ -1011,42 +948,12 @@
      * Return true if there are any edits to the current contact which need to
      * be saved.
      */
-    protected boolean hasPendingRawContactChanges() {
+    protected boolean hasPendingChanges() {
         final AccountTypeManager accountTypes = AccountTypeManager.getInstance(mContext);
         return RawContactModifier.hasChanges(mState, accountTypes);
     }
 
     /**
-     * Determines if changes were made in the editor that need to be saved, while taking into
-     * account that name changes are not realfor read-only contacts.
-     * See go/editing-read-only-contacts
-     */
-    protected boolean hasPendingChanges() {
-        if (mReadOnlyNameEditorView == null || mReadOnlyDisplayName == null) {
-            return hasPendingRawContactChanges();
-        }
-        // We created a new raw contact delta with a default display name.
-        // We must test for pending changes while ignoring the default display name.
-        final String displayName = mReadOnlyNameEditorView.getDisplayName();
-        if (mReadOnlyDisplayName.equals(displayName)) {
-            // The user did not modify the default display name, erase it and
-            // check if the user made any other changes
-            mReadOnlyNameEditorView.setDisplayName(null);
-            if (hasPendingRawContactChanges()) {
-                // Other changes were made to the aggregate contact, restore
-                // the display name and proceed.
-                mReadOnlyNameEditorView.setDisplayName(displayName);
-                return true;
-            } else {
-                // No other changes were made to the aggregate contact. Don't add back
-                // the displayName so that a "bogus" contact is not created.
-                return false;
-            }
-        }
-        return true;
-    }
-
-    /**
      * Whether editor inputs and the options menu should be enabled.
      */
     protected boolean isEnabled() {
@@ -1060,16 +967,6 @@
         return mMaterialPalette;
     }
 
-    /**
-     * Returns the currently displayed display name.
-     */
-    abstract protected String getDisplayName();
-
-    /**
-     * Returns the currently displayed phonetic name.
-     */
-    abstract protected String getPhoneticName();
-
     //
     // Account creation
     //
@@ -1166,7 +1063,6 @@
             }
         }
 
-        String readOnlyDisplayName = null;
         // Check for writable raw contacts.  If there are none, then we need to create one so user
         // can edit.  For the user profile case, there is already an editable contact.
         if (!contact.isUserProfile() && !contact.isWritableContact(mContext)) {
@@ -1174,13 +1070,11 @@
 
             // This is potentially an asynchronous call and will add deltas to list.
             selectAccountAndCreateContact();
-
-            readOnlyDisplayName = contact.getDisplayName();
         }
 
         // This also adds deltas to list.  If readOnlyDisplayName is null at this point it is
         // simply ignored later on by the editor.
-        setStateForExistingContact(readOnlyDisplayName, contact.isUserProfile(), mRawContacts);
+        setStateForExistingContact(contact.isUserProfile(), mRawContacts);
     }
 
     /**
@@ -1251,10 +1145,9 @@
     /**
      * Prepare {@link #mState} for an existing contact.
      */
-    protected void setStateForExistingContact(String readOnlyDisplayName, boolean isUserProfile,
+    protected void setStateForExistingContact(boolean isUserProfile,
             ImmutableList<RawContact> rawContacts) {
         setEnabled(true);
-        mReadOnlyDisplayName = readOnlyDisplayName;
 
         mState.addAll(rawContacts.iterator());
         setIntentExtras(mIntentExtras);
@@ -1366,8 +1259,7 @@
             setStateForNewContact(newAccount, newAccountType, oldState, oldAccountType,
                     isEditingUserProfile());
             if (mIsEdit) {
-                setStateForExistingContact(mReadOnlyDisplayName, isEditingUserProfile(),
-                        mRawContacts);
+                setStateForExistingContact(isEditingUserProfile(), mRawContacts);
             }
         }
     }
@@ -1400,11 +1292,7 @@
                         mIntentExtras.getInt(INTENT_EXTRA_MATERIAL_PALETTE_PRIMARY_COLOR),
                         mIntentExtras.getInt(INTENT_EXTRA_MATERIAL_PALETTE_SECONDARY_COLOR));
             }
-            if (mIntentExtras.containsKey(INTENT_EXTRA_UPDATED_PHOTOS)) {
-                mUpdatedPhotos = mIntentExtras.getParcelable(INTENT_EXTRA_UPDATED_PHOTOS);
-            }
             mPhotoId = mIntentExtras.getLong(INTENT_EXTRA_PHOTO_ID);
-            mNameId = mIntentExtras.getLong(INTENT_EXTRA_NAME_ID);
         }
     }
 
@@ -1427,14 +1315,12 @@
 
     @Override
     public void onJoinCompleted(Uri uri) {
-        onSaveCompleted(false, SaveMode.RELOAD, uri != null, uri, /* updatedPhotos =*/ null,
-                /* backPressed =*/ false, mPhotoId, mNameId);
+        onSaveCompleted(false, SaveMode.RELOAD, uri != null, uri);
     }
 
     @Override
     public void onSaveCompleted(boolean hadChanges, int saveMode, boolean saveSucceeded,
-            Uri contactLookupUri, Bundle updatedPhotos, boolean backPressed, long photoId,
-            long nameId) {
+            Uri contactLookupUri) {
         if (hadChanges) {
             if (saveSucceeded) {
                 if (saveMode != SaveMode.JOIN) {
@@ -1445,43 +1331,22 @@
             }
         }
         switch (saveMode) {
-            case SaveMode.CLOSE: {
+            case SaveMode.CLOSE:
+            case SaveMode.COMPACT: {
                 final Intent resultIntent;
                 if (saveSucceeded && contactLookupUri != null) {
                     final Uri lookupUri = maybeConvertToLegacyLookupUri(
                             mContext, contactLookupUri, mLookupUri);
                     resultIntent = ImplicitIntentsUtil.composeQuickContactIntent(lookupUri,
                             QuickContactActivity.MODE_FULLY_EXPANDED);
-                    resultIntent.putExtra(INTENT_EXTRA_SAVE_BACK_PRESSED, backPressed);
                 } else {
                     resultIntent = null;
                 }
+                // It is already saved, so prevent it from being saved again
                 mStatus = Status.CLOSING;
                 if (mListener != null) mListener.onSaveFinished(resultIntent);
                 break;
             }
-            case SaveMode.COMPACT: {
-                if (!hadChanges && !backPressed && isInsert(getActivity().getIntent())) {
-                    // Reload the empty editor when the Contacts app is resumed
-                    mStatus = Status.EDITING;
-                } else if (backPressed) {
-                    final Uri lookupUri = maybeConvertToLegacyLookupUri(
-                            mContext, contactLookupUri, mLookupUri);
-                    final Intent resultIntent = isInsert(getActivity().getIntent())
-                            ? EditorIntents.createCompactInsertContactIntent(
-                                    mState, getDisplayName(), getPhoneticName(), updatedPhotos,
-                                    mNewLocalProfile)
-                            : EditorIntents.createCompactEditContactIntent(
-                                    lookupUri, getMaterialPalette(), updatedPhotos, photoId,
-                                    nameId);
-                    resultIntent.putExtra(INTENT_EXTRA_SAVE_BACK_PRESSED, true);
-                    mStatus = Status.CLOSING;
-                    if (mListener != null) mListener.onSaveFinished(resultIntent);
-                } else {
-                    reloadFullEditor(contactLookupUri);
-                }
-                break;
-            }
             case SaveMode.RELOAD:
             case SaveMode.JOIN:
                 if (saveSucceeded && contactLookupUri != null) {
@@ -1489,7 +1354,13 @@
                     if (saveMode == SaveMode.JOIN && hasValidState()) {
                         showJoinAggregateActivity(contactLookupUri);
                     }
-                    reloadFullEditor(contactLookupUri);
+
+                    // If this was in INSERT, we are changing into an EDIT now.
+                    // If it already was an EDIT, we are changing to the new Uri now
+                    mState = new RawContactDeltaList();
+                    load(Intent.ACTION_EDIT, contactLookupUri, null);
+                    mStatus = Status.LOADING;
+                    getLoaderManager().restartLoader(LOADER_DATA, null, mDataLoaderListener);
                 }
                 break;
 
@@ -1504,13 +1375,6 @@
         }
     }
 
-    private void reloadFullEditor(Uri contactLookupUri) {
-        mState = new RawContactDeltaList();
-        load(ContactEditorBaseActivity.ACTION_EDIT, contactLookupUri, null);
-        mStatus = Status.LOADING;
-        getLoaderManager().restartLoader(LOADER_DATA, null, mDataLoaderListener);
-    }
-
     /**
      * Shows a list of aggregates that can be joined into the currently viewed aggregate.
      *
@@ -1646,7 +1510,7 @@
         }
 
         mState.setJoinWithRawContacts(rawContactIds);
-        save(SaveMode.RELOAD, /* backPressed =*/ false);
+        save(SaveMode.RELOAD);
     }
 
     @Override
@@ -1677,25 +1541,6 @@
     abstract protected void joinAggregate(long contactId);
 
     //
-    // Photos
-    //
-
-    /**
-     * Removes the full resolution photo URIs for new raw contacts (identified by negative raw
-     * contact IDs) from the member Bundle of updated photos.
-     */
-    protected void removeNewRawContactPhotos() {
-        for (String key : mUpdatedPhotos.keySet()) {
-            try {
-                if (Integer.parseInt(key) < 0) {
-                    mUpdatedPhotos.remove(key);
-                }
-            } catch (NumberFormatException ignored) {
-            }
-        }
-    }
-
-    //
     // Utility methods
     //
 
diff --git a/src/com/android/contacts/editor/ContactEditorFragment.java b/src/com/android/contacts/editor/ContactEditorFragment.java
index 186640a..ccbf615 100644
--- a/src/com/android/contacts/editor/ContactEditorFragment.java
+++ b/src/com/android/contacts/editor/ContactEditorFragment.java
@@ -26,7 +26,6 @@
 import android.text.TextUtils;
 import android.util.Log;
 import android.view.LayoutInflater;
-import android.view.MenuItem;
 import android.view.View;
 import android.view.ViewGroup;
 import android.widget.AdapterView;
@@ -64,6 +63,7 @@
 
     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";
 
     // Used to store which raw contact editors have been expanded. Keyed on raw contact ids.
     private HashMap<Long, Boolean> mExpandedEditors = new HashMap<Long, Boolean>();
@@ -84,6 +84,7 @@
      */
     private PhotoHandler mCurrentPhotoHandler;
     private Uri mCurrentPhotoUri;
+    private Bundle mUpdatedPhotos = new Bundle();
 
     public ContactEditorFragment() {
     }
@@ -109,16 +110,7 @@
             mRawContactIdRequestingPhoto = savedState.getLong(
                     KEY_RAW_CONTACT_ID_REQUESTING_PHOTO);
             mCurrentPhotoUri = savedState.getParcelable(KEY_CURRENT_PHOTO_URI);
-        }
-    }
-
-    @Override
-    public void onStop() {
-        super.onStop();
-
-        // If anything was left unsaved, save it now and return to the compact editor.
-        if (!getActivity().isChangingConfigurations() && mStatus == Status.EDITING) {
-            save(SaveMode.COMPACT, /* backPressed =*/ false);
+            mUpdatedPhotos = savedState.getParcelable(KEY_UPDATED_PHOTOS);
         }
     }
 
@@ -130,14 +122,6 @@
     }
 
     @Override
-    public boolean onOptionsItemSelected(MenuItem item) {
-        if (item.getItemId() == android.R.id.home) {
-            return save(SaveMode.COMPACT, /* backPressed =*/ true);
-        }
-        return super.onOptionsItemSelected(item);
-    }
-
-    @Override
     public void onEditorExpansionChanged() {
         updatedExpandedEditorsMap();
     }
@@ -261,11 +245,6 @@
 
                 final StructuredNameEditorView nameEditor = rawContactEditor.getNameEditor();
                 nameEditor.setEditorListener(structuredNameListener);
-                if (TextUtils.isEmpty(nameEditor.getDisplayName()) &&
-                        !TextUtils.isEmpty(mReadOnlyDisplayName)) {
-                    nameEditor.setDisplayName(mReadOnlyDisplayName);
-                    mReadOnlyNameEditorView = nameEditor;
-                }
 
                 rawContactEditor.setAutoAddToDefaultGroup(mAutoAddToDefaultGroup);
 
@@ -306,60 +285,6 @@
         }
     }
 
-    @Override
-    public String getDisplayName() {
-        // Return the super primary name if it is non-empty
-        for (int i = 0; i < mContent.getChildCount(); i++) {
-            final View view = mContent.getChildAt(i);
-            if (view instanceof RawContactEditorView) {
-                final RawContactEditorView rawContactEditorView = (RawContactEditorView) view;
-                final StructuredNameEditorView nameEditorView =
-                        rawContactEditorView.getNameEditor();
-                if (nameEditorView != null) {
-                    final String displayName = nameEditorView.getDisplayName();
-                    if (!TextUtils.isEmpty(displayName)) {
-                        return displayName;
-                    }
-                }
-            }
-        }
-        // Return the first non-empty name
-        for (int i = 0; i < mContent.getChildCount(); i++) {
-            final View view = mContent.getChildAt(i);
-            if (view instanceof RawContactEditorView) {
-                final RawContactEditorView rawContactEditorView = (RawContactEditorView) view;
-                final StructuredNameEditorView nameEditorView =
-                        rawContactEditorView.getNameEditor();
-                if (nameEditorView != null) {
-                    final String displayName = nameEditorView.getDisplayName();
-                    if (!TextUtils.isEmpty(displayName)) {
-                        return displayName;
-                    }
-                }
-            }
-        }
-        return null;
-    }
-
-    @Override
-    public String getPhoneticName() {
-        for (int i = 0; i < mContent.getChildCount(); i++) {
-            final View view = mContent.getChildAt(i);
-            if (view instanceof RawContactEditorView) {
-                final RawContactEditorView rawContactEditorView = (RawContactEditorView) view;
-                final PhoneticNameEditorView phoneticNameEditorView =
-                        (PhoneticNameEditorView) rawContactEditorView.getPhoneticNameEditor();
-                if (phoneticNameEditorView != null) {
-                    final String phoneticName = phoneticNameEditorView.getPhoneticName();
-                    if (!TextUtils.isEmpty(phoneticName)) {
-                        return phoneticName;
-                    }
-                }
-            }
-        }
-        return null;
-    }
-
     /**
      * Update the values in {@link #mExpandedEditors}.
      */
@@ -457,21 +382,12 @@
     }
 
     @Override
-    protected boolean doSaveAction(int saveMode, boolean backPressed) {
-        // Save contact and reload the compact editor after saving.
-        // Note, the full resolution photos Bundle must be passed to the ContactSaveService
-        // and then passed along in the result Intent in order for the compact editor to
-        // receive it, instead of mUpdatedPhotos being accessed directly in onSaveCompleted,
-        // because we clear mUpdatedPhotos after starting the save service below.
-        Intent intent = ContactSaveService.createSaveContactIntent(mContext, mState,
+    protected boolean doSaveAction(int saveMode) {
+        final Intent intent = ContactSaveService.createSaveContactIntent(mContext, mState,
                 SAVE_MODE_EXTRA_KEY, saveMode, isEditingUserProfile(),
                 ((Activity) mContext).getClass(), ContactEditorActivity.ACTION_SAVE_COMPLETED,
-                mUpdatedPhotos, backPressed);
+                mUpdatedPhotos);
         mContext.startService(intent);
-
-        // Don't try to save the same photos twice.
-        mUpdatedPhotos = new Bundle();
-
         return true;
     }
 
@@ -480,6 +396,7 @@
         outState.putSerializable(KEY_EXPANDED_EDITORS, mExpandedEditors);
         outState.putLong(KEY_RAW_CONTACT_ID_REQUESTING_PHOTO, mRawContactIdRequestingPhoto);
         outState.putParcelable(KEY_CURRENT_PHOTO_URI, mCurrentPhotoUri);
+        outState.putParcelable(KEY_UPDATED_PHOTOS, mUpdatedPhotos);
         super.onSaveInstanceState(outState);
     }
 
@@ -534,12 +451,6 @@
             Log.w(TAG, "The contact that requested the photo is no longer present.");
         }
 
-        // For inserts where the raw contact ID is a negative number, we must clear any previously
-        // saved full resolution photos under negative raw contact IDs so that the compact editor
-        // will use the newly selected photo, instead of an old one.
-        if (isInsert(getActivity().getIntent()) && rawContact < 0) {
-            removeNewRawContactPhotos();
-        }
         mUpdatedPhotos.putParcelable(String.valueOf(rawContact), photoUri);
     }
 
diff --git a/src/com/android/contacts/editor/EditorIntents.java b/src/com/android/contacts/editor/EditorIntents.java
index 68ce76f..26279df 100644
--- a/src/com/android/contacts/editor/EditorIntents.java
+++ b/src/com/android/contacts/editor/EditorIntents.java
@@ -44,12 +44,10 @@
      * existing contact.
      */
     public static Intent createCompactEditContactIntent(Uri contactLookupUri,
-            MaterialPalette materialPalette, Bundle updatedPhotos, long photoId, long nameId) {
+            MaterialPalette materialPalette, long photoId) {
         final Intent intent = new Intent(Intent.ACTION_EDIT, contactLookupUri);
         putMaterialPalette(intent, materialPalette);
-        putUpdatedPhotos(intent, updatedPhotos);
         putPhotoId(intent, photoId);
-        putNameId(intent, nameId);
         return intent;
     }
 
@@ -58,7 +56,7 @@
      */
     public static Intent createCompactInsertContactIntent() {
         return createCompactInsertContactIntent(/* rawContactDeltaList =*/ null,
-                /* displayName =*/ null, /* phoneticName =*/ null, /* updatedPhotos =*/ null,
+                /* displayName =*/ null, /* phoneticName =*/ null,
                 /* isNewLocalProfile =*/ false);
     }
 
@@ -67,14 +65,13 @@
      * the field values specified by rawContactDeltaList pre-populate in the form.
      */
     public static Intent createCompactInsertContactIntent(RawContactDeltaList rawContactDeltaList,
-            String displayName, String phoneticName, Bundle updatedPhotos,
+            String displayName, String phoneticName, /* Bundle updatedPhotos, */
             boolean isNewLocalProfile) {
         final Intent intent = new Intent(Intent.ACTION_INSERT, Contacts.CONTENT_URI);
         intent.putExtra(ContactEditorFragment.INTENT_EXTRA_NEW_LOCAL_PROFILE, isNewLocalProfile);
         if (rawContactDeltaList != null || displayName != null || phoneticName != null) {
             putRawContactDeltaValues(intent, rawContactDeltaList, displayName, phoneticName);
         }
-        putUpdatedPhotos(intent, updatedPhotos);
         return intent;
     }
 
@@ -97,36 +94,32 @@
     }
 
     /**
-     * Returns an Intent to start the fully expanded {@link ContactEditorActivity} for a
-     * new contact.
+     * Returns an Intent to start the fully expanded {@link ContactEditorActivity} for an
+     * existing contact.
      */
     public static Intent createEditContactIntent(Uri contactLookupUri,
-            MaterialPalette materialPalette, long photoId, long nameId) {
+            MaterialPalette materialPalette, long photoId) {
         final Intent intent = new Intent(ContactEditorBaseActivity.ACTION_EDIT, contactLookupUri);
         addContactIntentFlags(intent);
         putMaterialPalette(intent, materialPalette);
         putPhotoId(intent, photoId);
-        putNameId(intent, nameId);
         return intent;
     }
 
     /**
-     * Returns an Intent to start the fully expanded {@link ContactEditorActivity} for an
-     * existing contact.
+     * Returns an Intent to start the fully expanded {@link ContactEditorActivity} for a
+     * new contact.
      */
     public static Intent createInsertContactIntent(RawContactDeltaList rawContactDeltaList,
-            String displayName, String phoneticName, Bundle updatedPhotos,
-            boolean isNewLocalProfile) {
+            String displayName, String phoneticName, boolean isNewLocalProfile) {
         final Intent intent = new Intent(ContactEditorBaseActivity.ACTION_INSERT,
                 Contacts.CONTENT_URI);
         intent.putExtra(ContactEditorFragment.INTENT_EXTRA_NEW_LOCAL_PROFILE, isNewLocalProfile);
         addContactIntentFlags(intent);
         putRawContactDeltaValues(intent, rawContactDeltaList, displayName, phoneticName);
-        putUpdatedPhotos(intent, updatedPhotos);
         return intent;
     }
 
-
     private static void addContactIntentFlags(Intent intent) {
         intent.addFlags(Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS
                 | Intent.FLAG_ACTIVITY_FORWARD_RESULT);
@@ -141,24 +134,12 @@
         }
     }
 
-    private static void putUpdatedPhotos(Intent intent, Bundle updatedPhotos) {
-        if (updatedPhotos != null && !updatedPhotos.isEmpty()) {
-            intent.putExtra(ContactEditorBaseFragment.INTENT_EXTRA_UPDATED_PHOTOS, updatedPhotos);
-        }
-    }
-
     private static void putPhotoId(Intent intent, long photoId) {
         if (photoId >= 0) {
             intent.putExtra(ContactEditorBaseFragment.INTENT_EXTRA_PHOTO_ID, photoId);
         }
     }
 
-    private static void putNameId(Intent intent, long nameId) {
-        if (nameId >= 0) {
-            intent.putExtra(ContactEditorBaseFragment.INTENT_EXTRA_NAME_ID, nameId);
-        }
-    }
-
     private static void putRawContactDeltaValues(Intent intent,
             RawContactDeltaList rawContactDeltaList, String displayName, String phoneticName) {
         // Pass on all the data that has been entered so far
diff --git a/src/com/android/contacts/editor/EditorUiUtils.java b/src/com/android/contacts/editor/EditorUiUtils.java
index 56be3c0..106c5e3 100644
--- a/src/com/android/contacts/editor/EditorUiUtils.java
+++ b/src/com/android/contacts/editor/EditorUiUtils.java
@@ -16,12 +16,25 @@
 
 package com.android.contacts.editor;
 
-import static android.provider.ContactsContract.CommonDataKinds.Event;
 import static android.provider.ContactsContract.CommonDataKinds.GroupMembership;
-import static android.provider.ContactsContract.CommonDataKinds.Photo;
 import static android.provider.ContactsContract.CommonDataKinds.StructuredName;
 
 import android.content.Context;
+import android.graphics.drawable.Drawable;
+import android.provider.ContactsContract.CommonDataKinds.Email;
+import android.provider.ContactsContract.CommonDataKinds.Event;
+import android.provider.ContactsContract.CommonDataKinds.Im;
+import android.provider.ContactsContract.CommonDataKinds.Note;
+import android.provider.ContactsContract.CommonDataKinds.Organization;
+import android.provider.ContactsContract.CommonDataKinds.Phone;
+import android.provider.ContactsContract.CommonDataKinds.Photo;
+import android.provider.ContactsContract.CommonDataKinds.Relation;
+import android.provider.ContactsContract.CommonDataKinds.SipAddress;
+import android.provider.ContactsContract.CommonDataKinds.StructuredPostal;
+import android.provider.ContactsContract.CommonDataKinds.Website;
+import android.media.RingtoneManager;
+import android.net.Uri;
+import android.os.Build;
 import android.text.TextUtils;
 import android.util.Pair;
 import com.android.contacts.R;
@@ -142,4 +155,68 @@
         }
         return builder.toString();
     }
+
+    /**
+     * Return an icon that represents {@param mimeType}.
+     */
+    public static Drawable getMimeTypeDrawable(Context context, String mimeType) {
+        switch (mimeType) {
+            case StructuredName.CONTENT_ITEM_TYPE:
+                return context.getResources().getDrawable(R.drawable.ic_person_black_24dp);
+            case StructuredPostal.CONTENT_ITEM_TYPE:
+                return context.getResources().getDrawable(R.drawable.ic_place_24dp);
+            case SipAddress.CONTENT_ITEM_TYPE:
+                return context.getResources().getDrawable(R.drawable.ic_dialer_sip_black_24dp);
+            case Phone.CONTENT_ITEM_TYPE:
+                return context.getResources().getDrawable(R.drawable.ic_phone_24dp);
+            case Im.CONTENT_ITEM_TYPE:
+                return context.getResources().getDrawable(R.drawable.ic_message_24dp);
+            case Event.CONTENT_ITEM_TYPE:
+                return context.getResources().getDrawable(R.drawable.ic_event_24dp);
+            case Email.CONTENT_ITEM_TYPE:
+                return context.getResources().getDrawable(R.drawable.ic_email_24dp);
+            case Website.CONTENT_ITEM_TYPE:
+                return context.getResources().getDrawable(R.drawable.ic_public_black_24dp);
+            case Photo.CONTENT_ITEM_TYPE:
+                return context.getResources().getDrawable(R.drawable.ic_camera_alt_black_24dp);
+            case GroupMembership.CONTENT_ITEM_TYPE:
+                return context.getResources().getDrawable(R.drawable.ic_people_black_24dp);
+            case Organization.CONTENT_ITEM_TYPE:
+                return context.getResources().getDrawable(R.drawable.ic_business_black_24dp);
+            case Note.CONTENT_ITEM_TYPE:
+                return context.getResources().getDrawable(R.drawable.ic_insert_comment_black_24dp);
+            case Relation.CONTENT_ITEM_TYPE:
+                return context.getResources().getDrawable(
+                        R.drawable.ic_circles_extended_black_24dp);
+            default:
+                return null;
+        }
+    }
+
+    /**
+     * Returns a ringtone string based on the ringtone URI and version #.
+     */
+    public static String getRingtoneStringFromUri(Uri pickedUri, int currentVersion) {
+        if (isNewerThanM(currentVersion)) {
+            if (pickedUri == null) return ""; // silent ringtone
+            if (RingtoneManager.isDefault(pickedUri)) return null; // default ringtone
+        }
+        if (pickedUri == null || RingtoneManager.isDefault(pickedUri)) return null;
+        return pickedUri.toString();
+    }
+
+    /**
+     * Returns a ringtone URI, based on the string and version #.
+     */
+    public static Uri getRingtoneUriFromString(String str, int currentVersion) {
+        if (str != null) {
+            if (isNewerThanM(currentVersion) && TextUtils.isEmpty(str)) return null;
+            return Uri.parse(str);
+        }
+        return RingtoneManager.getDefaultUri(RingtoneManager.TYPE_RINGTONE);
+    }
+
+    private static boolean isNewerThanM(int currentVersion) {
+        return currentVersion > Build.VERSION_CODES.M;
+    }
 }
diff --git a/src/com/android/contacts/editor/GroupMembershipView.java b/src/com/android/contacts/editor/GroupMembershipView.java
index f1d9db9..b13da62 100644
--- a/src/com/android/contacts/editor/GroupMembershipView.java
+++ b/src/com/android/contacts/editor/GroupMembershipView.java
@@ -202,6 +202,11 @@
         }
     }
 
+    /** Whether {@link #setGroupMetaData} has been invoked yet. */
+    public boolean wasGroupMetaDataBound() {
+        return mGroupMetaData != null;
+    }
+
     public void setState(RawContactDelta state) {
         mState = state;
         mAccountType = mState.getAccountType();
diff --git a/src/com/android/contacts/editor/KindSectionData.java b/src/com/android/contacts/editor/KindSectionData.java
new file mode 100644
index 0000000..d46001a
--- /dev/null
+++ b/src/com/android/contacts/editor/KindSectionData.java
@@ -0,0 +1,119 @@
+/*
+ * 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.common.model.RawContactDelta;
+import com.android.contacts.common.model.ValuesDelta;
+import com.android.contacts.common.model.account.AccountType;
+import com.android.contacts.common.model.account.AccountType.EditField;
+import com.android.contacts.common.model.dataitem.DataKind;
+
+import android.provider.ContactsContract.CommonDataKinds.Nickname;
+import android.provider.ContactsContract.CommonDataKinds.StructuredName;
+import android.text.TextUtils;
+
+import java.util.Collections;
+import java.util.List;
+
+/**
+ * Holder for the multi account raw contact data needed to back an editor input field.
+ */
+public final class KindSectionData {
+
+    private final AccountType mAccountType;
+    private final List<ValuesDelta> mValuesDeltas;
+    private final DataKind mDataKind;
+    private final RawContactDelta mRawContactDelta;
+
+    public KindSectionData(AccountType accountType, DataKind dataKind,
+            RawContactDelta rawContactDelta) {
+        mAccountType = accountType;
+        mDataKind = dataKind;
+        mRawContactDelta = rawContactDelta;
+        mValuesDeltas = mRawContactDelta.getMimeEntries(dataKind.mimeType, /* lazyCreate= */ true);
+    }
+
+    public AccountType getAccountType() {
+        return mAccountType;
+    }
+
+    public List<ValuesDelta> getValuesDeltas() {
+        return mValuesDeltas;
+    }
+
+    /** Returns the super primary ValuesDelta for the data kind this section represents. */
+    public ValuesDelta getSuperPrimaryValuesDelta() {
+        for (ValuesDelta valuesDelta : mValuesDeltas) {
+            if (valuesDelta.isSuperPrimary()) return valuesDelta;
+        }
+        return null;
+    }
+
+    /** Returns the ValuesDelta with the given ID. */
+    public ValuesDelta getValuesDeltaById(Long id) {
+        for (ValuesDelta valuesDelta : mValuesDeltas) {
+            if (valuesDelta.getId().equals(id)) return valuesDelta;
+        }
+        return null;
+    }
+
+    /** Returns the first non empty ValuesDelta for the data kind this section represents. */
+    public ValuesDelta getFirstNonEmptyValuesDelta() {
+        for (ValuesDelta valuesDelta : mValuesDeltas) {
+            if (!isEmpty(valuesDelta)) return valuesDelta;
+        }
+        return null;
+    }
+
+    /** Whether the given ValuesDelta is empty for the data kind this section represents. */
+    public boolean isEmpty(ValuesDelta valuesDelta) {
+        if (valuesDelta.isNoop()) return true;
+
+        if (mDataKind.fieldList != null) {
+            for (EditField editField : mDataKind.fieldList) {
+                final String column = editField.column;
+                final String value = valuesDelta.getAsString(column);
+                if (TextUtils.isEmpty(value)) return true;
+            }
+        }
+        return false;
+    }
+
+    public DataKind getDataKind() {
+        return mDataKind;
+    }
+
+    public boolean isNameDataKind() {
+        return StructuredName.CONTENT_ITEM_TYPE.equals(mDataKind.mimeType);
+    }
+
+    public boolean isNicknameDataKind() {
+        return Nickname.CONTENT_ITEM_TYPE.equals(mDataKind.mimeType);
+    }
+
+    public RawContactDelta getRawContactDelta() {
+        return mRawContactDelta;
+    }
+
+    public String toString() {
+        return String.format("%s<accountType=%s dataSet=%s values=%s>",
+                KindSectionData.class.getSimpleName(),
+                mAccountType.accountType,
+                mAccountType.dataSet,
+                getValuesDeltas().size());
+    }
+}
\ No newline at end of file
diff --git a/src/com/android/contacts/editor/KindSectionView.java b/src/com/android/contacts/editor/KindSectionView.java
index 26ef058..23b0c21 100644
--- a/src/com/android/contacts/editor/KindSectionView.java
+++ b/src/com/android/contacts/editor/KindSectionView.java
@@ -17,19 +17,6 @@
 package com.android.contacts.editor;
 
 import android.content.Context;
-import android.graphics.drawable.Drawable;
-import android.provider.Contacts.GroupMembership;
-import android.provider.ContactsContract.CommonDataKinds.Email;
-import android.provider.ContactsContract.CommonDataKinds.Event;
-import android.provider.ContactsContract.CommonDataKinds.Im;
-import android.provider.ContactsContract.CommonDataKinds.Note;
-import android.provider.ContactsContract.CommonDataKinds.Organization;
-import android.provider.ContactsContract.CommonDataKinds.Phone;
-import android.provider.ContactsContract.CommonDataKinds.Photo;
-import android.provider.ContactsContract.CommonDataKinds.Relation;
-import android.provider.ContactsContract.CommonDataKinds.SipAddress;
-import android.provider.ContactsContract.CommonDataKinds.StructuredPostal;
-import android.provider.ContactsContract.CommonDataKinds.Website;
 import android.provider.ContactsContract.Data;
 import android.text.TextUtils;
 import android.util.AttributeSet;
@@ -70,15 +57,6 @@
     private DataKind mKind;
     private RawContactDelta mState;
     private boolean mReadOnly;
-    private boolean mShowOneEmptyEditor;
-
-    /**
-     * Whether this KindSectionView will be removed from the layout.
-     * We need this because we want to animate KindSectionViews away (which takes time),
-     * but calculate which KindSectionViews will be visible immediately after starting removal
-     * animations.
-     */
-    private boolean mMarkedForRemoval;
 
     private ViewIdGenerator mViewIdGenerator;
 
@@ -125,7 +103,7 @@
 
     @Override
     public void onDeleteRequested(Editor editor) {
-        if (mShowOneEmptyEditor && getEditorCount() == 1) {
+        if (getEditorCount() == 1) {
             // If there is only 1 editor in the section, then don't allow the user to delete it.
             // Just clear the fields in the editor.
             editor.clearAllFields();
@@ -141,25 +119,6 @@
         }
     }
 
-    /**
-     * Calling this signifies that this entire section view is intended to be removed from the
-     * layout. Note, calling this does not change the deleted state of any underlying
-     * {@link Editor}, i.e. {@link com.android.contacts.common.model.ValuesDelta#markDeleted()}
-     * is not invoked on any editor in this section.  It is purely marked for higher level UI
-     * layers to manipulate the layout w/o introducing jank.
-     * See b/22228718 for context.
-     */
-    public void markForRemoval() {
-        mMarkedForRemoval = true;
-    }
-
-    /**
-     * Whether the entire section view is intended to be removed from the layout.
-     */
-    public boolean isMarkedForRemoval() {
-        return mMarkedForRemoval;
-    }
-
     @Override
     public void onRequest(int request) {
         // If a field has become empty or non-empty, then check if another row
@@ -169,22 +128,10 @@
         }
     }
 
-    /**
-     * @param showOneEmptyEditor If true, one empty input will always be displayed,
-     *         otherwise an empty input will only be displayed if there is no non-empty value.
-     */
-    public void setShowOneEmptyEditor(boolean showOneEmptyEditor) {
-        mShowOneEmptyEditor = showOneEmptyEditor;
-    }
-
     public void setListener(Listener listener) {
         mListener = listener;
     }
 
-    public void setIconVisibility(boolean visible) {
-        mIcon.setVisibility(visible ? View.VISIBLE : View.INVISIBLE);
-    }
-
     public void setState(DataKind kind, RawContactDelta state, boolean readOnly,
             ViewIdGenerator vig) {
         mKind = kind;
@@ -200,7 +147,7 @@
                 : getResources().getString(kind.titleRes);
         mIcon.setContentDescription(titleString);
 
-        mIcon.setImageDrawable(getMimeTypeDrawable(kind.mimeType));
+        mIcon.setImageDrawable(EditorUiUtils.getMimeTypeDrawable(getContext(), kind.mimeType));
         if (mIcon.getDrawable() == null) {
             mIcon.setContentDescription(null);
         }
@@ -314,7 +261,7 @@
         } else if (emptyEditors.size() == 1) {
             // We have already reached the maximum number of empty editors. Lets not add any more.
             return;
-        } else if (mShowOneEmptyEditor) {
+        } else {
             final ValuesDelta values = RawContactModifier.insertChild(mState, mKind);
             final View newField = createEditorView(values);
             if (shouldAnimate) {
@@ -338,16 +285,6 @@
         return emptyEditorViews;
     }
 
-    public boolean areAllEditorsEmpty() {
-        for (int i = 0; i < mEditors.getChildCount(); i++) {
-            final View view = mEditors.getChildAt(i);
-            if (!((Editor) view).isEmpty()) {
-                return false;
-            }
-        }
-        return true;
-    }
-
     public int getEditorCount() {
         return mEditors.getChildCount();
     }
@@ -355,38 +292,4 @@
     public DataKind getKind() {
         return mKind;
     }
-
-    /**
-     * Return an icon that represents {@param mimeType}.
-     */
-    private Drawable getMimeTypeDrawable(String mimeType) {
-        switch (mimeType) {
-            case StructuredPostal.CONTENT_ITEM_TYPE:
-                return getResources().getDrawable(R.drawable.ic_place_24dp);
-            case SipAddress.CONTENT_ITEM_TYPE:
-                return getResources().getDrawable(R.drawable.ic_dialer_sip_black_24dp);
-            case Phone.CONTENT_ITEM_TYPE:
-                return getResources().getDrawable(R.drawable.ic_phone_24dp);
-            case Im.CONTENT_ITEM_TYPE:
-                return getResources().getDrawable(R.drawable.ic_message_24dp);
-            case Event.CONTENT_ITEM_TYPE:
-                return getResources().getDrawable(R.drawable.ic_event_24dp);
-            case Email.CONTENT_ITEM_TYPE:
-                return getResources().getDrawable(R.drawable.ic_email_24dp);
-            case Website.CONTENT_ITEM_TYPE:
-                return getResources().getDrawable(R.drawable.ic_public_black_24dp);
-            case Photo.CONTENT_ITEM_TYPE:
-                return getResources().getDrawable(R.drawable.ic_camera_alt_black_24dp);
-            case GroupMembership.CONTENT_ITEM_TYPE:
-                return getResources().getDrawable(R.drawable.ic_people_black_24dp);
-            case Organization.CONTENT_ITEM_TYPE:
-                return getResources().getDrawable(R.drawable.ic_business_black_24dp);
-            case Note.CONTENT_ITEM_TYPE:
-                return getResources().getDrawable(R.drawable.ic_insert_comment_black_24dp);
-            case Relation.CONTENT_ITEM_TYPE:
-                return getResources().getDrawable(R.drawable.ic_circles_extended_black_24dp);
-            default:
-                return null;
-        }
-    }
 }
diff --git a/src/com/android/contacts/editor/LabeledEditorView.java b/src/com/android/contacts/editor/LabeledEditorView.java
index 931d7cf..244b682 100644
--- a/src/com/android/contacts/editor/LabeledEditorView.java
+++ b/src/com/android/contacts/editor/LabeledEditorView.java
@@ -378,6 +378,9 @@
                 if (mIsDeletable) mDeleteContainer.setVisibility(View.VISIBLE);
             }
             mWasEmpty = isEmpty;
+
+            // Update the label text color
+            mEditTypeAdapter.notifyDataSetChanged();
         }
     }
 
diff --git a/src/com/android/contacts/editor/PhoneticNameEditorView.java b/src/com/android/contacts/editor/PhoneticNameEditorView.java
index f094d55..420575c 100644
--- a/src/com/android/contacts/editor/PhoneticNameEditorView.java
+++ b/src/com/android/contacts/editor/PhoneticNameEditorView.java
@@ -151,8 +151,4 @@
         return !TextUtils.isEmpty(family) || !TextUtils.isEmpty(middle)
                 || !TextUtils.isEmpty(given);
     }
-
-    public String getPhoneticName() {
-        return getEntry().getAsString(DataKind.PSEUDO_COLUMN_PHONETIC_NAME);
-    }
 }
diff --git a/src/com/android/contacts/editor/RawContactEditorView.java b/src/com/android/contacts/editor/RawContactEditorView.java
index f021ec7..1832ce2 100644
--- a/src/com/android/contacts/editor/RawContactEditorView.java
+++ b/src/com/android/contacts/editor/RawContactEditorView.java
@@ -282,7 +282,6 @@
                 if (kind.fieldList == null) continue;
                 final KindSectionView section = (KindSectionView)mInflater.inflate(
                         R.layout.item_kind_section, mFields, false);
-                section.setShowOneEmptyEditor(true);
                 section.setEnabled(isEnabled());
                 section.setState(kind, state, /* readOnly =*/ false, vig);
                 mFields.addView(section);
diff --git a/src/com/android/contacts/editor/RawContactReadOnlyEditorView.java b/src/com/android/contacts/editor/RawContactReadOnlyEditorView.java
index fd99ddc..76fa097 100644
--- a/src/com/android/contacts/editor/RawContactReadOnlyEditorView.java
+++ b/src/com/android/contacts/editor/RawContactReadOnlyEditorView.java
@@ -170,7 +170,7 @@
                         phoneNumber, phone.getPhoneNormalizedNumber(),
                         GeoUtil.getCurrentCountryIso(getContext()));
                 CharSequence phoneType = null;
-                if (phone.phoneHasType()) {
+                if (phone.hasPhoneType()) {
                     phoneType = Phone.getTypeLabel(
                             res, phone.getPhoneType(), phone.getPhoneLabel());
                 }
@@ -192,7 +192,7 @@
                     continue;
                 }
                 CharSequence emailType = null;
-                if (email.emailHasType()) {
+                if (email.hasEmailType()) {
                     emailType = Email.getTypeLabel(
                             res, email.getEmailType(), email.getEmailLabel());
                 }
diff --git a/src/com/android/contacts/editor/StructuredNameEditorView.java b/src/com/android/contacts/editor/StructuredNameEditorView.java
index c0463b0..4cc8003 100644
--- a/src/com/android/contacts/editor/StructuredNameEditorView.java
+++ b/src/com/android/contacts/editor/StructuredNameEditorView.java
@@ -16,6 +16,7 @@
 
 package com.android.contacts.editor;
 
+import com.android.contacts.R;
 import android.content.ContentValues;
 import android.content.Context;
 import android.os.Parcel;
@@ -23,9 +24,14 @@
 import android.provider.ContactsContract.CommonDataKinds.StructuredName;
 import android.text.TextUtils;
 import android.util.AttributeSet;
+import android.view.View;
+import android.widget.ImageView;
+import android.widget.LinearLayout;
+import android.widget.TextView;
 
 import com.android.contacts.common.model.RawContactDelta;
 import com.android.contacts.common.model.ValuesDelta;
+import com.android.contacts.common.model.account.AccountType;
 import com.android.contacts.common.model.dataitem.DataItem;
 import com.android.contacts.common.model.dataitem.DataKind;
 import com.android.contacts.common.util.NameConverter;
@@ -75,6 +81,18 @@
         updateEmptiness();
     }
 
+    /**
+     * Displays the icon and name for the given account under the name name input fields.
+     */
+    public void setAccountType(AccountType accountType) {
+        final LinearLayout layout = (LinearLayout) findViewById(R.id.account_type);
+        layout.setVisibility(View.VISIBLE);
+        final ImageView imageView = (ImageView) layout.findViewById(R.id.account_type_icon);
+        imageView.setImageDrawable(accountType.getDisplayIcon(getContext()));
+        final TextView textView = (TextView) layout.findViewById(R.id.account_type_name);
+        textView.setText(accountType.getDisplayLabel(getContext()));
+    }
+
     @Override
     public void onFieldChanged(String column, String value) {
         if (!isFieldChanged(column, value)) {
@@ -207,34 +225,6 @@
         super.setValue(0, name);
     }
 
-    /**
-     * Returns the display name currently displayed in the editor.
-     */
-    public String getDisplayName() {
-        final ValuesDelta valuesDelta = getValues();
-        if (hasShortAndLongForms()) {
-            if (areOptionalFieldsVisible()) {
-                final Map<String, String> structuredNameMap = valuesToStructuredNameMap(valuesDelta);
-                final String displayName = NameConverter.structuredNameToDisplayName(
-                        getContext(), structuredNameMap);
-                if (!TextUtils.isEmpty(displayName)) {
-                    return displayName;
-                }
-            } else {
-                final String displayName = valuesDelta.getDisplayName();
-                if (!TextUtils.isEmpty(displayName)) {
-                    return displayName;
-                }
-            }
-        }
-        return valuesDelta.getDisplayName();
-    }
-
-    @Override
-    public boolean isEmpty() {
-        return TextUtils.isEmpty(getDisplayName());
-    }
-
     @Override
     protected Parcelable onSaveInstanceState() {
         SavedState state = new SavedState(super.onSaveInstanceState());
diff --git a/src/com/android/contacts/editor/TextFieldsEditorView.java b/src/com/android/contacts/editor/TextFieldsEditorView.java
index fe476ed..51c9d94 100644
--- a/src/com/android/contacts/editor/TextFieldsEditorView.java
+++ b/src/com/android/contacts/editor/TextFieldsEditorView.java
@@ -213,7 +213,7 @@
         }
         boolean hidePossible = false;
 
-        int fieldCount = kind.fieldList.size();
+        int fieldCount = kind.fieldList == null ? 0 : kind.fieldList.size();
         mFieldEditTexts = new EditText[fieldCount];
         for (int index = 0; index < fieldCount; index++) {
             final EditField field = kind.fieldList.get(index);
diff --git a/src/com/android/contacts/quickcontact/InvisibleContactUtil.java b/src/com/android/contacts/quickcontact/InvisibleContactUtil.java
index de70424..3609fbc 100644
--- a/src/com/android/contacts/quickcontact/InvisibleContactUtil.java
+++ b/src/com/android/contacts/quickcontact/InvisibleContactUtil.java
@@ -94,7 +94,7 @@
         final Intent intent = ContactSaveService.createSaveContactIntent(
                 context,
                 contactDeltaList, "", 0, false, QuickContactActivity.class,
-                Intent.ACTION_VIEW, null, /* backPressed =*/ false);
+                Intent.ACTION_VIEW, null);
         context.startService(intent);
     }
 
diff --git a/src/com/android/contacts/quickcontact/QuickContactActivity.java b/src/com/android/contacts/quickcontact/QuickContactActivity.java
index c89c5f4..3ac9472 100644
--- a/src/com/android/contacts/quickcontact/QuickContactActivity.java
+++ b/src/com/android/contacts/quickcontact/QuickContactActivity.java
@@ -2525,9 +2525,7 @@
                 mContactData.getLookupUri(),
                 mHasComputedThemeColor
                         ? new MaterialPalette(mColorFilterColor, mStatusBarColor) : null,
-                /* updatedPhotos =*/ null,
-                mContactData.getPhotoId(),
-                mContactData.getNameRawContactId());
+                mContactData.getPhotoId());
     }
 
     private void editContact() {
diff --git a/tests/src/com/android/contacts/editor/EditorUiUtilsTest.java b/tests/src/com/android/contacts/editor/EditorUiUtilsTest.java
index 58e6f26..4a180ef 100644
--- a/tests/src/com/android/contacts/editor/EditorUiUtilsTest.java
+++ b/tests/src/com/android/contacts/editor/EditorUiUtilsTest.java
@@ -21,6 +21,10 @@
 import com.android.contacts.common.model.account.GoogleAccountType;
 
 import android.content.Context;
+import android.media.RingtoneManager;
+import android.net.Uri;
+import android.os.Build;
+import android.provider.Settings;
 import android.test.AndroidTestCase;
 import android.test.suitebuilder.annotation.SmallTest;
 import android.util.Pair;
@@ -37,6 +41,8 @@
     private static final String GOOGLE_ACCOUNT_NAME = "somebody@gmail.com";
     private static final String GOOGLE_DISPLAY_LABEL = "Google";
 
+    private static final String RINGTONE = "content://media/external/audio/media/31";
+
     private static final class MockAccountType extends AccountType {
 
         private final String mDisplayLabel;
@@ -140,4 +146,41 @@
                         getContext().getString(R.string.account_phone)),
                 pair.second); // "Phone-only, unsynced contact"
     }
+
+    public void testGetRingtongStrFromUri_lessThanOrEqualsToM() {
+        final int currentVersion = Build.VERSION_CODES.M;
+        assertNull(EditorUiUtils.getRingtoneStringFromUri(null, currentVersion));
+        assertNull(EditorUiUtils.getRingtoneStringFromUri(Settings.System.DEFAULT_RINGTONE_URI,
+                currentVersion));
+        assertEquals(RINGTONE, EditorUiUtils.getRingtoneStringFromUri(Uri.parse(RINGTONE),
+                        currentVersion));
+    }
+
+    public void testGetRingtongStrFromUri_nOrGreater() {
+        final int currentVersion = Build.VERSION_CODES.M + 1;
+        assertEquals("", EditorUiUtils.getRingtoneStringFromUri(null, currentVersion));
+        assertNull(EditorUiUtils.getRingtoneStringFromUri(Settings.System.DEFAULT_RINGTONE_URI,
+                currentVersion));
+        assertEquals(RINGTONE, EditorUiUtils.getRingtoneStringFromUri(Uri.parse(RINGTONE),
+                        currentVersion));
+    }
+
+    public void testGetRingtongUriFromStr_lessThanOrEqualsToM() {
+        final int currentVersion = Build.VERSION_CODES.M;
+        assertEquals(RingtoneManager.getDefaultUri(RingtoneManager.TYPE_RINGTONE), EditorUiUtils
+                        .getRingtoneUriFromString(null, currentVersion));
+        assertEquals(Uri.parse(""), EditorUiUtils.getRingtoneUriFromString("", currentVersion));
+        assertEquals(Uri.parse(RINGTONE), EditorUiUtils.getRingtoneUriFromString(RINGTONE,
+                currentVersion));
+    }
+
+    public void testGetRingtongUriFromStr_nOrGreater() {
+        final int currentVersion = Build.VERSION_CODES.M + 1;
+        assertEquals(RingtoneManager.getDefaultUri(RingtoneManager.TYPE_RINGTONE), EditorUiUtils
+                        .getRingtoneUriFromString(null, currentVersion));
+        assertNull(EditorUiUtils.getRingtoneUriFromString("", currentVersion));
+        assertEquals(Uri.parse(RINGTONE), EditorUiUtils.getRingtoneUriFromString(RINGTONE,
+                currentVersion));
+    }
+
 }